(prop :FlxActionDigital continueAction) (prop actionManager (new FlxActionManager)) (prop &mut :Movie movie) (defNew [] (set continueAction (new FlxActionDigital "Continue" onContinue)) // TODO allow configuring continue keys -- any key, specifically mapped keys, etc. (continueAction.addKey SPACE JUST_PRESSED) (continueAction.addMouse LEFT JUST_PRESSED) (actionManager.addAction continueAction) (FlxG.inputs.add actionManager) (set actionManager.resetOnStateSwitch NONE)) (prop &mut :SceneFlxState currentState) (method :Void showScene [:Scene scene :Appearance appearance :Continuation cc] // Close the last scene state (when currentState (currentState.close)) // TODO on the first appearance, give a super (for some scenes but probably not others... hm....) (set currentState (cast scene SceneFlxState)) (set currentState.parent FlxG.state) (FlxG.state.openSubState currentState) // Re-show characters in case their actor sprites were moved in different scenes: (doFor =>name character currentState.characters (showCharacter character ReAppearance ->:Void {})) (cc)) (method :Void cleanup [] (FlxG.state.openSubState null)) (var STAGE_LEFT_X 150) (var STAGE_RIGHT_X (- 1280 150)) (var STAGE_BEHIND_DY -250) (var ACTOR_Y 500) (var ACTOR_WIDTH 300) (method :Void showCharacter [:Character character :Appearance appearance :Continuation cc] // TODO on the first appearance, show name and description (maybe? also probably not for all?) // TODO also allow for manually defined flipped frames so text on clothing doesn't mirror (set character.actor.flipX ?!(= character.stageFacing character.actor.defaultFacing)) (character.actor.setGraphicSize ACTOR_WIDTH) (character.actor.updateHitbox) (set character.actor.x (- (case character.stagePosition (Left (set currentState.actorOnLeft character.actor) (set currentState.characterOnLeft character) STAGE_LEFT_X) (LeftBehind STAGE_LEFT_X) (Right (set currentState.actorOnRight character.actor) (set currentState.characterOnRight character) STAGE_RIGHT_X) (RightBehind STAGE_RIGHT_X) (otherwise (throw "unsupported stage position"))) (/ character.actor.width 2))) (set character.actor.y ACTOR_Y) (let [bottom (+ character.actor.y character.actor.height)] (when (> bottom 720) (-= character.actor.y (- bottom 720)))) (let [&mut reAddFront false] (case character.stagePosition ((or LeftBehind RightBehind) (set reAddFront true) (+= character.actor.y STAGE_BEHIND_DY)) (otherwise)) (when reAddFront (when currentState.actorOnLeft (currentState.remove currentState.actorOnLeft)) (when currentState.actorOnRight (currentState.remove currentState.actorOnRight))) (currentState.add character.actor) (when reAddFront (when currentState.actorOnLeft (currentState.add currentState.actorOnLeft)) (when currentState.actorOnRight (currentState.add currentState.actorOnRight)))) (cc)) (method :Void hideCharacter [:Character character :Continuation cc] (when (= currentState.actorOnLeft character.actor) (set currentState.actorOnLeft null) (set currentState.characterOnLeft null)) (when (= currentState.actorOnRight character.actor) (set currentState.actorOnRight null) (set currentState.characterOnRight null)) (currentState.remove character.actor) (cc)) (method :Void moveCharacter [:Character character toPos toFacing :Continuation cc] // Directors don't have to change the character, but FlxDirector does because that state determines // where actors are drawn in showCharacter: (set character.stagePosition toPos) (set character.stageFacing toFacing) (hideCharacter character ->:Void (showCharacter character ReAppearance cc))) (method :Void swapCharacters [:Character a :Character b :Continuation cc] (let [noCC ->:Void {} tempStagePos a.stagePosition tempStageFacing a.stageFacing] (moveCharacter a b.stagePosition b.stageFacing noCC) (moveCharacter b tempStagePos tempStageFacing cc))) (prop &mut :Null nextCC) (method onContinue [:FlxActionDigital continueAction] (whenLet [cc nextCC] (set nextCC null) (cc))) (method :Void startWaitForInput [:Continuation cc] (set nextCC cc)) (method :Void stopWaitForInput [] (set nextCC null)) (var TITLE_Y 240) (var TITLE_SIZE 72) (var TITLE_MARGIN 100) (var SUBTITLES_MARGIN 30) (var SUBTITLES_SIZE 48) (prop &mut :FlxSprite titleCard null) (method :Void showTitleCard [:Array text :Continuation cc] (set titleCard (new FlxSprite)) (titleCard.makeGraphic 1280 720 FlxColor.BLACK true) (SpriteTools.writeOnSprite (text.shift) TITLE_SIZE titleCard (object x (Percent 0.5) y (Pixels TITLE_Y))) (localVar &mut subtitleY (+ TITLE_Y TITLE_SIZE TITLE_MARGIN)) (doFor subtitle text (SpriteTools.writeOnSprite subtitle SUBTITLES_SIZE titleCard (object x (Percent 0.5) y (Pixels subtitleY))) (+= subtitleY SUBTITLES_SIZE SUBTITLES_MARGIN)) (currentState.add titleCard) // Allow skipping (startWaitForInput cc)) (method :Void hideTitleCard [] (currentState.remove titleCard)) (var DIALOG_X 300) (var DIALOG_WIDTH (- 1280 ACTOR_WIDTH ACTOR_WIDTH)) (var DIALOG_Y 500) (var DIALOG_HEIGHT (- 720 DIALOG_Y)) // TODO these could be customizable to the Actor, wrylies, etc. (var DIALOG_BOX_COLOR FlxColor.BLACK) (var DIALOG_COLOR FlxColor.WHITE) (var DIALOG_SIZE 24) (var &mut :FlxSprite dialogBox) (var &mut :FlxText dialogText) (var &mut :FlxText speakerNameText) (method showDialog [:String speakerName :SpeechType type :String wryly :String text :Continuation cc] // TODO handle text messages, wrylies, off-screen, from-phone, etc. via (case type) // TODO attribute on-screen dialogue to the character's stageposition // When an actor is associated with the line, check for an animation matching the wryly // TODO allow sounds for wrylies, like the dispatch click (localVar &mut nameOnRight false) (case type ((OnScreen character) (case character.stagePosition ((or Right RightBehind) (set nameOnRight true)) (otherwise)) // If the character is behind another character, swap them so the speaker is front (case character.stagePosition (LeftBehind (if currentState.characterOnLeft (swapCharacters character currentState.characterOnLeft ->:Void {})) (moveCharacter character Left character.stageFacing ->:Void {})) (RightBehind (if currentState.characterOnRight (swapCharacters character currentState.characterOnRight ->:Void {})) (moveCharacter character Right character.stageFacing ->:Void {})) (otherwise)) (let [actor (the ActorFlxSprite character.actor)] (if wryly (actor.playAnimation wryly) (actor.playAnimation "neutral")))) ((or (OffScreen actor) (VoiceOver actor) (TextMessage actor) (FromPhone actor) (Custom _ actor _)) (if wryly (actor.playAnimation wryly) (actor.playAnimation "neutral"))) (otherwise)) // Make a dialog box (unless dialogBox (set dialogBox (new FlxSprite DIALOG_X DIALOG_Y)) (dialogBox.makeGraphic DIALOG_WIDTH DIALOG_HEIGHT DIALOG_BOX_COLOR)) (currentState.add dialogBox) (dialogBox.revive) // show the dialog (unless dialogText (set dialogText (new FlxText DIALOG_X DIALOG_Y DIALOG_WIDTH "" DIALOG_SIZE))) (currentState.add dialogText) (set dialogText.text text) // TODO actually page through the dialog instead of sizing it down? // ^ though that doesn't work automatically with VO unless individual word times are kept in the json // (which would be really clunky) (set dialogText.size DIALOG_SIZE) (while (< 720 (+ dialogText.y dialogText.height)) (-= dialogText.size 6)) // show the speaker name (unless speakerNameText (set speakerNameText (new FlxText DIALOG_X DIALOG_Y 0 "" DIALOG_SIZE))) (currentState.add speakerNameText) (if speakerName { (set speakerNameText.text "${speakerName}:") (set speakerNameText.x (if nameOnRight (- (+ DIALOG_X DIALOG_WIDTH) speakerNameText.fieldWidth) DIALOG_X)) (speakerNameText.revive) (set dialogText.y (+ DIALOG_Y speakerNameText.height)) } (set dialogText.y DIALOG_Y)) (dialogText.revive) (startWaitForInput cc)) (method :Void hideDialog [] (dialogText.kill) (speakerNameText.kill) (dialogBox.kill)) (method :Void playSound [:FlxSound sound :Float volumeMod :Bool waitForEnd :Continuation cc] (let [originalVolume sound.volume restoreOriginalVolume ->(set sound.volume originalVolume)] (*= sound.volume volumeMod) (set sound.onComplete (if waitForEnd ->{(restoreOriginalVolume) (cc)} restoreOriginalVolume))) (sound.play) (unless waitForEnd (cc))) (method :Void stopSound [:FlxSound sound] (sound.stop)) (var DELAY_BETWEEN_VOICE_TRACKS 0.1) (prop :Map restoreOriginalVolumes (new Map)) (method :Void playVoiceTrack [:FlxSound track :Float volumeMod :Float start :Float end :Continuation cc] (let [originalVolume track.volume restoreOriginalVolume ->(set track.volume originalVolume)] (dictSet restoreOriginalVolumes track restoreOriginalVolume) (*= track.volume volumeMod) (set track.onComplete ->{(restoreOriginalVolume) (movie.delay DELAY_BETWEEN_VOICE_TRACKS cc)})) (track.play true (* 1000 start) (* 1000 end))) (method :Void stopVoiceTrack [:FlxSound track] (track.stop) ((dictGet restoreOriginalVolumes track))) (prop &mut :FlxSound music) (prop MUSIC_FADE_SEC 1) (prop MUSIC_FADE_STEPS 10) (method :Void playSong [:String song :Float volumeMod :Bool loop :Bool waitForEnd :Continuation cc] (set music (FlxG.sound.play song 0 loop null true (if waitForEnd cc ->{}))) (.start (new FlxTimer) (/ MUSIC_FADE_SEC MUSIC_FADE_STEPS) ->:Void _ (+= music.volume (/ volumeMod MUSIC_FADE_STEPS)) MUSIC_FADE_STEPS) (set music.persist true) (unless waitForEnd (cc))) (method :Void stopSong [] (when music (music.stop))) (var PROP_MIN_WIDTH 200) (var PROP_MAX_WIDTH 500) (method :Void quickShowPropOnScreen [:FlxSprite prop :FlxScreenPosition position :Continuation cc] (let [left (/ 1280 6) right (- 1280 left) upper (/ 720 6) lower (- 720 upper) centerX (/ 1280 2) centerY (/ 720 2) [x y] (case position (Center [centerX centerY]) (UpperLeft [left upper]) (UpperRight [right upper]) (LowerLeft [left lower]) (LowerRight [right lower]) (LowerCenter [centerX lower]) (UpperCenter [centerX upper]) (otherwise (throw "screen position not implemented")))] (let [width (min (max prop.width PROP_MIN_WIDTH) PROP_MAX_WIDTH)] (prop.setGraphicSize width) (prop.updateHitbox) // if the prop is too tall, shrink it heightwise (when (> prop.height DIALOG_Y) (prop.setGraphicSize 0 DIALOG_Y) (prop.updateHitbox)) (set prop.x (- x (/ prop.width 2))) (set prop.y (- y (/ prop.height 2))) // if a prop meant to be centered would block the dialogue box, bump it up (whenLet [Center position] (let [propBottom (+ prop.y prop.height)] (when (> propBottom DIALOG_Y) (-= prop.y (- propBottom DIALOG_Y))))) (.add (nth currentState.spriteLayers SceneFlxState.LAYER_MAX) prop))) (cc)) (method :Void smartShowPropOnScreen [:FlxSprite prop :Int layer :RelativePosition rpos :Continuation cc] (assert (<= 0 layer SceneFlxState.LAYER_MAX) "Layer $layer is out of range 0-$SceneFlxState.LAYER_MAX") (let [canvas (new FlxSprite 0 0)] (canvas.makeGraphic 1280 720 FlxColor.BLACK) (let [[x y] (SpriteTools.positionOn prop canvas rpos)] (SpriteTools.scaleStampOn prop canvas rpos) (set prop.x x) (set prop.y y) (.add (nth currentState.spriteLayers layer) prop))) (cc)) (method :Void showPropOnScreen [:FlxSprite prop :FlxScreenPosition position :Continuation cc] // TODO give the prop reveal some time to land (add a delay to the cc) (ifLet [(FullControl layer rpos) position] (smartShowPropOnScreen prop layer rpos cc) (quickShowPropOnScreen prop position cc))) (method :Void hideProp [:FlxSprite prop cc] (currentState.remove prop) (doFor layer currentState.spriteLayers (layer.remove prop)) (cc)) (prop :Array creditsText []) (method _ctext [:String text :Int size] (let [t (new FlxText 0 0 0 text size)] (creditsText.push t) t)) (prop &mut :FlxSprite blackBG null) (method :Void showBlackScreen [] (set blackBG (new FlxSprite)) (blackBG.makeGraphic 1280 720 FlxColor.BLACK true) (currentState.add blackBG)) (method :Void hideBlackScreen [] (currentState.remove blackBG)) // TODO maybe credits need their own substate so an after-credits scene could be done. // currently the bg will cover whatever the final scene was. (method :Void rollCredits [:Array credits cc] (localVar bg (new FlxSprite)) (bg.makeGraphic 1280 720 FlxColor.BLACK true) (currentState.add bg) (localVar &mut textY 720) (var oneColSize 64) (var twoColSize 48) (var threeColSize 32) (var creditMargin 10) (var twoColumnGap 50) (doFor line credits (case line (Break (+= textY oneColSize)) // Centered, big one column lines ((OneColumn col1) (let [t (_ctext col1 oneColSize)] (t.screenCenter) (set t.y textY)) (+= textY oneColSize creditMargin)) // Centered left/right two column lines ((TwoColumn col1 col2) (let [t1 (_ctext col1 twoColSize) t2 (_ctext col2 twoColSize)] (set t1.x (- (/ 1280 2) t1.width (/ twoColumnGap 2))) (set t1.y textY) (set t2.x (+ (/ 1280 2) (/ twoColumnGap 2))) (set t2.y textY)) (+= textY twoColSize creditMargin)) // Left-justified, centered, right-justified three column lines ((ThreeColumn col1 col2 col3) (let [t1 (_ctext col1 threeColSize) t2 (_ctext col2 threeColSize) t3 (_ctext col3 threeColSize)] (set t1.x creditMargin) (set t1.y textY) (t2.screenCenter) (set t2.y textY) (set t3.x (- 1280 creditMargin t3.width)) (set t3.y textY)) (+= textY threeColSize creditMargin)) (otherwise))) (doFor text creditsText (currentState.add text) (FlxTween.linearMotion text text.x text.y text.x (- text.y textY) 200 false (object onComplete ->:Void _ (cc)))))