/* Handles any calls to PlayWithCallback.play, tracking the 'cancel' callbacks, * and automatically calling them in the event that the entire set gets cancelled. * This can help prevent spurious callbacks from messing with the animation state * form FormationCharacterAnimations when the animation is cancelled, * and the next animation sets up new sequences (which may cause the PlayWithCallback instances * from the previous animation to get called, even though that animation has been deactivated) */ class ManagedAnimationCallbackSet { constructor() { this.cancels = []; } // plays a new animation, and cancels the callbacks currently waiting // (assumedly, we have moved on from the animation state that those // callbacks represented, and we no longer want to process the actions // that should occur at the end of those animations) playNew(g /*Drawable*/, sequence /*int*/, callback /*Action<bool>*/, playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/, startFrame /*= 0*/, endFrame /*int? = null*/) /*function(bool)*/ { this.cancel(); return this.play(g, sequence, callback, playMode, startFrame, endFrame); } // Plays a new animation on a clip, and calls the given callback when the animation // completes or is interrupted. If cancel() is called on the entire set, the // given callback will never be called (presumably the state of the animation // has changed such that the given callback is no longer valid, for instance, the owner // FormationCharacterAnimation has been deactivate()'ed) play(g /*Drawable*/, sequence /*int*/, callback /*Action<bool>*/, playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/, startFrame /*= 0*/, endFrame /*int? = null*/) /*function(bool)*/ { var newCallback = (finished, cancel) => { this.cancels.remove(cancel); if (callback != null) callback(finished, cancel); }; var cancel = PlayWithCallback.play(g, sequence, newCallback, playMode, startFrame, endFrame); this.cancels.push(cancel); return cancel; } cancel() { for (var c of this.cancels) { c(false); } this.cancels.length = 0; } } var PlayWithCallback = {}; /// <summary> /// Play the given sequence once through on the given drawable (either forwards or backwards) /// Calls a callback with true/false depending on whether the sequence plays: /// - graphic failed to load = false (immediate/on load failed) /// - no such sequence = false (immediate) /// - sequence has only 1 frame = true (immediate) /// - sequence plays through = true (after finished) /// - sequence interrupted = false (when interrupted) /// /// Returns a callback you can call to cancel the action (param controls whether or not you still get the callback) /// </summary> /// <param name="g"></param> /// <param name="sequence"></param> /// <param name="callback"></param> /// <param name="playMode"></param> /// <returns></returns> PlayWithCallback.play = function(g /*Drawable*/, sequence /*int*/, callback /*Action<bool>*/, playMode /*= Engine.DrawableStatics.PlayMode.Forwards*/, startFrame /*= 0*/, endFrame /*int? = null*/) /*function(bool)*/ { if (playMode === undefined) playMode = Engine.DrawableStatics.PlayMode.Forwards; if (startFrame === undefined) startFrame = 0; if (endFrame === undefined) endFrame = null; var pwc = PlayWithCallback.pool.getNextObject(); var activationNumber = pwc.activationNumber; pwc.init(g, sequence, callback, playMode, startFrame, endFrame); var cancel = b => pwc.cancel(activationNumber, b); pwc.lastCancel = cancel; return cancel; } class PlayWithCallbackInternal { constructor() { this.graphicDef = null; this.g = null; //Drawable this.sequence = 0; this.startFrame = 0; this.endFrame = null; //int? this.callback = null; //function(bool) this.playMode = Engine.DrawableStatics.PlayMode.Forwards; this.active = false; this.activationNumber = 0; this.lastStopAtEnd = null; //bool? this.frameEventReference = null; this.lastCancel = null; // last cancel callback } init(g /*Drawable*/, sequence, callback /*function(bool)*/, playMode, startFrame, endFrame /*int?*/) { this.active = true; this.g = g; this.graphicDef = g.graphicData; this.sequence = sequence; this.callback = callback; this.playMode = playMode; this.startFrame = startFrame; this.endFrame = endFrame; this.lastStopAtEnd = null; Engine.EnterFrameManagerInstance.add(this, this.update); this.go(); } update(timeElapsed) { if (this.active && (this.g.getSequence() != this.sequence || this.g.graphicData != this.graphicDef)) { this.done(true, false); } } go() { this.frameEventReference = null; if (this.g.graphicData == null) { this.done(true, false); return; } if (this.g.graphicData.loadStatus == GraphicDefStatics.LoadState.None) { this.graphicDef.onChange.removeListener(this); this.graphicDef.onChange.addListener(this, this.graphicChanged); return; } if (this.g.numSequences > this.sequence) { var frame = this.playMode == Engine.DrawableStatics.PlayMode.Forwards ? this.startFrame : -1 - this.startFrame; this.g.setSequence(this.sequence); if (this.endFrame != null) this.endFrame = Math.min(this.g.totalFrames - 1, this.endFrame); if (Engine.Utils.getValueOrDefault(this.endFrame, this.g.totalFrames - 1) > this.startFrame) { this.lastStopAtEnd = this.g.stopAtEnd; if (this.endFrame != null) { this.frameEventReference = this.animationCompleted.bind(this); this.g.setFrameEvent(this.g.getSequence(), this.endFrame, this.frameEventReference, true); } else { this.g.stopAtEnd = true; this.g.animationCompleted.addListener(this, this.animationCompleted); } this.g.gotoAndPlay(frame, this.playMode); } else { this.g.gotoAndStop(frame); this.done(true, true); } } else { this.done(true, false); } } animationCompleted(obj) { obj.stop(); this.done(true, this.g.getSequence() == this.sequence); } graphicChanged(graphicDef) { this.go(); } done(makeCallback, played) { if (!this.active) return; Engine.EnterFrameManagerInstance.remove(this); this.active = false; this.activationNumber++; if (this.endFrame != null) { if (this.frameEventReference != null) { this.g.removeFrameEvent(this.g.getSequence(), this.endFrame, this.frameEventReference); this.frameEventReference = null; } } else { this.g.animationCompleted.removeListener(this); } this.graphicDef.onChange.removeListener(this); this.g = null; this.graphicDef = null; var tmpCancel = this.lastCancel; this.lastCancel = null; var tmp = this.callback; this.callback = null; PlayWithCallback.pool.returnObject(this); if (makeCallback) { if (tmp != null) tmp(played, tmpCancel); } } cancel(activationNumber, stillMakeCallback) { if (this.active && this.activationNumber == activationNumber) { this.done(stillMakeCallback, false); } } } PlayWithCallback.pool = new Engine.ObjectPool(PlayWithCallbackInternal);