386 lines
16 KiB
Plaintext
386 lines
16 KiB
Plaintext
(prop :FlxActionDigital continueAction)
|
|
(prop actionManager (new FlxActionManager))
|
|
(prop &mut :Movie<String,FlxStagePosition,FlxStageFacing,FlxScreenPosition,ActorFlxSprite,FlxSound,String,FlxSprite,FlxSound> 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<String,FlxStagePosition,FlxStageFacing,FlxScreenPosition,ActorFlxSprite,FlxSprite> 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<FlxStagePosition,FlxStageFacing,ActorFlxSprite> 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<FlxStagePosition,FlxStageFacing,ActorFlxSprite> 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<FlxStagePosition,FlxStageFacing,ActorFlxSprite> 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<FlxStagePosition,FlxStageFacing,ActorFlxSprite> a :Character<FlxStagePosition,FlxStageFacing,ActorFlxSprite> 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<Continuation> 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<String> 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<FlxStagePosition,FlxStageFacing,ActorFlxSprite> 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<FlxSound,Function> 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<FlxText> 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<CreditsLine> 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))))) |