Files
kiss-vscode/projects/hollywoo-flixel/src/hollywoo_flixel/FlxDirector.kiss
2022-01-09 18:48:10 -07:00

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)))))