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();
}
}