import { MovieClip as AwayMovieClip, DisplayObject as AwayDisplayObject, IMovieClipAdapter, FrameScriptManager, Timeline, IFrameScript } from '@awayjs/scene'; import { Sprite } from './Sprite'; import { AssetBase, Debug } from '@awayjs/core'; import { constructClassFromSymbol } from '@awayfl/avm2'; import { Event } from '../events/Event'; import { FrameLabel } from './FrameLabel'; import { SecurityDomain } from '../SecurityDomain'; /** * The MovieClip class inherits from the following classes: Sprite, DisplayObjectContainer, * InteractiveObject, DisplayObject, and EventDispatcher. * *

Unlike the Sprite object, a MovieClip object has a timeline.

> * In Flash Professional, the methods for the MovieClip class provide the same functionality * as actions that target movie clips. Some additional methods do not have equivalent * actions in the Actions toolbox in the Actions panel in the Flash authoring tool.

*

* Children instances placed on the Stage in Flash Professional * cannot be accessed by code from within the constructor of a parent instance * since they have not been created at that ponumber in code execution. * Before accessing the child, the parent must instead either create the child instance * by code or delay access to a callback that listens for the child to dispatch * its Event.ADDED_TO_STAGE event. *

* If you modify any of the following properties of a MovieClip object that contains a motion tween, * the playhead is stopped in that MovieClip object: * alpha, * blendMode, * filters, * height, * opaqueBackground, * rotation, * scaleX, * scaleY, * scale9Grid, * scrollRect, * transform, * visible, * width, * x, * or y. * However, it does not stop the playhead in any child MovieClip objects of that * MovieClip object.

* Note:Flash Lite 4 supports the MovieClip.opaqueBackground property only if * FEATURE_BITMAPCACHE is defined. The default configuration of Flash Lite 4 does not define * FEATURE_BITMAPCACHE. To enable the MovieClip.opaqueBackground property for a suitable device, * define FEATURE_BITMAPCACHE in your project.

*/ export class MovieClip extends Sprite implements IMovieClipAdapter { private static _movieClips: Array = new Array(); private static current_script_scope: MovieClip=null; private _framescripts: IFrameScript[]; // executed directly after a MC has been constructed via Object.create, // befre the actual constructors have been run public applySymbol() {} public static getNewMovieClip(adaptee: AwayMovieClip): MovieClip { if (MovieClip._movieClips.length) { const movieClip: MovieClip = MovieClip._movieClips.pop(); movieClip.adaptee = adaptee; return movieClip; } return new MovieClip(); } // call this after you call scripts public queuedNavigationAction: Function; public allowScript: boolean; public executeScript(scripts: any) { if (!this._framescripts || !this.allowScript && ( this.sec).swfVersion > 9) { return; } const script: any = this._framescripts[( this.adaptee).currentFrameIndex]; if (!script) { return; } this.allowScript = false; const prev_script_scope = MovieClip.current_script_scope; MovieClip.current_script_scope = this; script.axCall(this); MovieClip.current_script_scope = prev_script_scope; if (this.queuedNavigationAction) { // execute any pending FrameNavigation for this mc const queuedNavigationAction = this.queuedNavigationAction; this.queuedNavigationAction = null; queuedNavigationAction(); } } public initAdapter(): void {} /** * Creates a new MovieClip instance. After creating the MovieClip, call the * addChild() or addChildAt() method of a * display object container that is onstage. */ constructor() { super(); } protected createAdaptee(): AwayDisplayObject { const adaptee = AwayMovieClip.getNewMovieClip(new Timeline(this.sec.player.factory)); adaptee.reset(); //console.log("createAdaptee AwayMovieClip"); //FrameScriptManager.execute_queue(); return adaptee; } public freeFromScript(): void { //this.stopAllSounds(); super.freeFromScript(); } public clone(): MovieClip { const anyThis: AssetBase = this; if (!anyThis._symbol) { throw ('_symbol not defined when cloning movieclip'); } const newMC: MovieClip = constructClassFromSymbol(anyThis._symbol, anyThis._symbol.symbolClass); const adaptee = new AwayMovieClip(( this.adaptee).timeline); this.adaptee.copyTo(adaptee); newMC.adaptee = adaptee; newMC._stage = this.activeStage; (newMC).executeConstructor = () => { //adaptee.timeline.resetScripts(); (newMC).axInitializer(); (newMC).constructorHasRun = true; }; return newMC; } /** * @inheritDoc */ public dispose(): void { this.disposeValues(); //MovieClip._movieClips.push(this); } //---------------------------original as3 properties / methods: /** * Specifies the number of the frame in which the playhead is located in the timeline of * the MovieClip instance. If the movie clip has multiple scenes, this value is the * frame number in the current scene. */ public get currentFrame(): number { const adaptee = ( this._adaptee); return adaptee.currentFrameIndexRelative + 1; } /** * The label at the current frame in the timeline of the MovieClip instance. * If the current frame has no label, currentLabel is null. */ public get currentFrameLabel(): string { return ( this.adaptee).timeline.getCurrentFrameLabel( this.adaptee); } /** * The current label in which the playhead is located in the timeline of the MovieClip instance. * If the current frame has no label, currentLabel is set to the name of the previous frame * that includes a label. If the current frame and previous frames do not include a label, * currentLabel returns null. */ public get currentLabel(): string { return ( this.adaptee).timeline.getCurrentLabel( this.adaptee); } /** * Returns an array of FrameLabel objects from the current scene. If the MovieClip instance does * not use scenes, the array includes all frame labels from the entire MovieClip instance. */ public get currentLabels(): FrameLabel[] { // this is called quite frequently in some games // we do not 100% support scenes, yet // as long as we do not have ull scene-support, we can cache the result if (this._currentLabels) return this._currentLabels; const labels = ( this.adaptee).timeline._labels; const keyframe_firstframes = ( this.adaptee).timeline.keyframe_firstframes; this._currentLabels = []; for (const key in labels) { this._currentLabels.push(new ( this.sec).flash.display.FrameLabel( labels[key].name, keyframe_firstframes[labels[key].keyFrameIndex] + 1)); } return this._currentLabels; } private _currentLabels: FrameLabel[]; /** * The current scene in which the playhead is located in the timeline of the MovieClip instance. */ public get currentScene(): any { //todo //console.log('currentScene not implemented yet in flash/MovieClip'); const scene = ( this.adaptee).currentScene; const newLabels = { value:[] }; for (let i = 0; i < scene.labels.length; i++) { newLabels.value.push(new ( this.sec).flash.display.FrameLabel( scene.labels[i].name, scene.labels[i].frame )); } const as3Scene = new ( this.sec).flash.display.Scene( scene.name, newLabels, scene.offset, scene.numFrames ); return as3Scene; } /** * A boolean value that indicates whether a movie clip is enabled. The default value of enabled * is true. If enabled is set to false, the movie clip's * Over, Down, and Up frames are disabled. The movie clip * continues to receive events (for example, mouseDown, * mouseUp, keyDown, and keyUp). * * The enabled property governs only the button-like properties of a movie clip. You * can change the enabled property at any time; the modified movie clip is immediately * enabled or disabled. If enabled is set to false, the object is not * included in automatic tab ordering. */ public get enabled(): boolean { return ( this.adaptee).buttonEnabled; } public set enabled(value: boolean) { ( this.adaptee).buttonEnabled = value; } /** * The number of frames that are loaded from a streaming SWF file. You can use the framesLoaded * property to determine whether the contents of a specific frame and all the frames before it * loaded and are available locally in the browser. You can also use it to monitor the downloading * of large SWF files. For example, you might want to display a message to users indicating that * the SWF file is loading until a specified frame in the SWF file finishes loading. * * If the movie clip contains multiple scenes, the framesLoaded property returns the number * of frames loaded for all scenes in the movie clip. */ public get framesLoaded(): number { return this.totalFrames; } public get isPlaying(): boolean { return ( this.adaptee).isPlaying; } /** * An array of Scene objects, each listing the name, the number of frames, * and the frame labels for a scene in the MovieClip instance. */ public get scenes(): any[] { // @todo Debug.throwPIR('playerglobals/display/MovieClip', 'get scenes', ''); return []; } /** * The total number of frames in the MovieClip instance. * * If the movie clip contains multiple frames, the totalFrames property returns * the total number of frames in all scenes in the movie clip. */ public get totalFrames(): number { return ( this._adaptee).numFrames; } /** * Indicates whether other display objects that are SimpleButton or MovieClip objects can receive * mouse release events or other user input release events. The trackAsMenu property lets you create menus. You * can set the trackAsMenu property on any SimpleButton or MovieClip object. * The default value of the trackAsMenu property is false. * * You can change the trackAsMenu property at any time; the modified movie * clip immediately uses the new behavior. */ public get trackAsMenu(): boolean { // @todo Debug.throwPIR('playerglobals/display/MovieClip', 'get trackAsMenu', ''); return false; } public set trackAsMenu(value: boolean) { // @todo Debug.throwPIR('playerglobals/display/MovieClip', 'set trackAsMenu', ''); } public addFrameScript(...args) { // arguments are pairs of frameIndex and script/function // frameIndex is in range 0..totalFrames-1 const numArgs = arguments.length; if (numArgs & 1) { this.sec.throwError('ArgumentError TooFewArgumentsError', numArgs, numArgs + 1); } //addFrameScript can be called before constructor if (!this._framescripts) this._framescripts = []; for (let i = 0; i < numArgs; i += 2) { const frameNum = (args[i] | 0); const fn = args[i + 1]; this._framescripts[frameNum] = fn; // newly registered scripts get queued in FrameScriptManager.execute-as3constructor //console.log("add framescript", frameNum, this.adaptee, this.adaptee.parent); // if the mc was already added to scene before the construcor was run, // no framescript was defined, and therefore we might need to add scripts for the current frame manually // todo: make sure that this is correctly behaving in case constructor navigates the mc to another frame //if((this.adaptee).currentFrameIndex==frameNum){ // FrameScriptManager.add_script_to_queue_pass2(this.adaptee, [fn]); //} } ( this).constructorHasRun = true; } /** * Starts playing the SWF file at the specified frame. This happens after all * remaining actions in the frame have finished executing. To specify a scene * as well as a frame, specify a value for the scene parameter. * @param frame A number representing the frame number, or a string representing the label of the * frame, to which the playhead is sent. If you specify a number, it is relative to the * scene you specify. If you do not specify a scene, * the current scene determines the global frame number to play. If you do specify a scene, the playhead * jumps to the frame number in the specified scene. * @param scene The name of the scene to play. This parameter is optional. */ public gotoAndPlay(frame: any, scene: string = null, force: boolean = false) { this._gotoInternal(frame, scene, force, false); } /** * Brings the playhead to the specified frame of the movie clip and stops it there. This happens after all * remaining actions in the frame have finished executing. If you want to specify a scene in addition to a frame, * specify a scene parameter. * @param frame A number representing the frame number, or a string representing the label of the * frame, to which the playhead is sent. If you specify a number, it is relative to the * scene you specify. If you do not specify a scene, the current scene determines * the global frame number at which to go to and stop. If you do specify a scene, * the playhead goes to the frame number in the specified scene and stops. * @param scene The name of the scene. This parameter is optional. * @throws ArgumentError If the scene or frame specified are * not found in this movie clip. */ public gotoAndStop(frame: any, scene: string = null, force: boolean = false) { this._gotoInternal(frame, scene, force, true); } private _gotoInternal( frame: string | number, scene: string = null, force: boolean = false, stop: boolean = false ) { if (!force && MovieClip.current_script_scope == this) { this.queuedNavigationAction = () => this._gotoInternal(frame, scene, true, stop); return; } // in FP for frame==null, we need to stop the mc if (frame == null) { stop && this.stop(); return; } const adaptee = this.adaptee; scene = scene || ( adaptee).currentSceneName; if (typeof frame === 'string') { if (adaptee.timeline._labels[frame] == null) { frame = parseInt(frame); if (!isNaN(frame)) { ( adaptee).jumpToIndex(frame - 1, scene); if (stop) { adaptee.stop(); } else { adaptee.play(); } } if (stop) { // for FP>10 we should throw a error and stop the timeline if (( this.sec).swfVersion > 10) { adaptee.currentFrameIndex = 0; } this.stop(); } return; } } if (typeof frame === 'number' && frame <= 0) { if (MovieClip.current_script_scope == this) { return; } frame = 1; } if (stop) { this.stop(); } else { this.play(); } this._gotoFrame(frame, scene); } private _gotoFrame(frame: string | number, scene: string = null): void { const adaptee = this._adaptee; let navigated: boolean; if (typeof frame === 'string') { navigated = adaptee.jumpToLabel(frame); } else if (typeof frame === 'number' && frame <= 0) { console.warn('[playerglobal/MovieClip] - gotoFrame called with invalid frame-index'); navigated = true; } else { navigated = adaptee.jumpToIndex(frame - 1, scene); } //only execute scripts if playhead has moved or constructors have yet to run if (!navigated) { return; } //console.log("_gotoFrame", this.name); FrameScriptManager.execute_as3_constructors_recursiv( this.adaptee); FrameScriptManager.execute_as3_constructors_finish_scene( this.activeStage.getChildAt(0).adaptee); // this is not true! // only in FP10 and above we want to execute scripts immediatly here // BUT ONLY ON SUB TIMELINES if (( this.sec).swfVersion > 9 && this !== MovieClip.current_script_scope) { this.dispatchStaticBroadCastEvent(Event.FRAME_CONSTRUCTED); FrameScriptManager.execute_queue(); this.dispatchStaticBroadCastEvent(Event.EXIT_FRAME); } } /** * Sends the playhead to the next frame and stops it. This happens after all * remaining actions in the frame have finished executing. */ public nextFrame(force: boolean = false) { if (!force && MovieClip.current_script_scope == this) { this.queuedNavigationAction = ()=>this.nextFrame(true); return; } ( this._adaptee).stop(); ++( this._adaptee).currentFrameIndex; FrameScriptManager.execute_as3_constructors_recursiv( this.adaptee); FrameScriptManager.execute_as3_constructors_finish_scene( this.activeStage.getChildAt(0).adaptee); // only in FP10 and above we want to execute scripts immediatly here if (( this.sec).swfVersion > 9) { this.dispatchStaticBroadCastEvent(Event.FRAME_CONSTRUCTED); FrameScriptManager.execute_queue(); this.dispatchStaticBroadCastEvent(Event.EXIT_FRAME); } } /** * Moves the playhead to the next scene of the MovieClip instance. This happens after all * remaining actions in the frame have finished executing. */ public nextScene(force: boolean = false) { if (!force && MovieClip.current_script_scope == this) { this.queuedNavigationAction = ()=>this.nextScene(true); return; } // @todo Debug.throwPIR('playerglobals/display/MovieClip', 'nextScene', ''); } /** * Moves the playhead in the timeline of the movie clip. */ public play() { return ( this._adaptee).play(); } /** * Sends the playhead to the previous frame and stops it. This happens after all * remaining actions in the frame have finished executing. */ public prevFrame(force: boolean = false) { if (!force && MovieClip.current_script_scope == this) { this.queuedNavigationAction = ()=>this.prevFrame(true); return; } const adaptee = ( this._adaptee); if (adaptee.currentFrameIndexRelative > 0) { adaptee.currentFrameIndexRelative--; } FrameScriptManager.execute_as3_constructors_recursiv( this.adaptee); FrameScriptManager.execute_as3_constructors_finish_scene( this.activeStage.getChildAt(0).adaptee); // only in FP10 and above we want to execute scripts immediatly here if (( this.sec).swfVersion > 9) { this.dispatchStaticBroadCastEvent(Event.FRAME_CONSTRUCTED); FrameScriptManager.execute_queue(); this.dispatchStaticBroadCastEvent(Event.EXIT_FRAME); } } /** * Moves the playhead to the previous scene of the MovieClip instance. This happens after all * remaining actions in the frame have finished executing. */ public prevScene(force: boolean = false) { if (!force && MovieClip.current_script_scope == this) { this.queuedNavigationAction = ()=>this.prevScene(true); return; } // @todo Debug.throwPIR('playerglobals/display/MovieClip', 'prevScene', ''); } /** * Stops the playhead in the movie clip. */ public stop() { return ( this._adaptee).stop(); } }