Files
hollywoo-flixel/src/hollywoo_flixel/FlxDirector.kiss

1160 lines
46 KiB
Plaintext

(prop :FlxActionDigital continueAction)
(prop :FlxActionDigital fastForwardAction)
(prop actionManager (new FlxActionManager))
(prop &mut :Movie<FlxSprite,ActorFlxSprite,FlxSound,FlxSound,FlxSprite,FlxSound,FlxCamera,FlxLightSource> movie)
(defAlias &ident flxMovie (cast movie FlxMovie))
(loadFrom "hollywoo-flixel" "src/hollywoo_flixel/Aliases.kiss")
(prop :FlxTypedGroup<FlxTypedGroup<FlxBasic>> spriteLayers (new FlxTypedGroup))
(var LAYER_MAX 8)
(savedVar :Map<String,String> lastSceneLabels (new Map))
(function :String lastSceneForMovie [:FlxMovie m &opt :String setValue]
(let [clazz (Type.getClass m)
className (Type.getClassName clazz)
currentValue (dictGet lastSceneLabels className)]
(if setValue
(withMutProperties [lastSceneLabels]
(dictSet lastSceneLabels className setValue)
setValue)
currentValue)))
// TODO this should be part of Hollywoo's logic
(method :Void promptForResume [:FlxMovie movie]
(movie.createCameras)
(if (.exists (movie.labelRunners) (lastSceneForMovie movie))
{(_chooseString "Resume from '$(lastSceneForMovie movie)'?" ["" "Resume" "Scene Selection" "Start From Beginning"]
->choice (case choice
("Resume" (movie.runFromLabel (lastSceneForMovie movie)))
("Scene Selection"
(sceneSelection
->:Void {
(movie.doCleanup)
(FlxG.switchState (new MenuState))
}))
("Start From Beginning" (movie.run))
(never otherwise)) true "escape")
(set pauseMenu.onClose ->:Void {
(movie.doCleanup)
(FlxG.switchState (new MenuState))
})}
(movie.run)))
// MenuState uses a director instance to demo the volume. This shouldn't affect flixel globals i.e. by adding an actionManager or sprite layers
(defNew [&opt :Bool demoDirector]
(unless demoDirector
// Continue action requires fresh input, Fast Forward action supports continuous holding down
(set continueAction (new FlxActionDigital "Continue" onContinue))
(set fastForwardAction (new FlxActionDigital "Fast Forward"))
// TODO allow configuring continue/fast-forward keys -- any key, specifically mapped keys, etc.
(doFor [:FlxActionDigital action :FlxInputState trigger] (zipThrow [continueAction fastForwardAction] [JUST_PRESSED PRESSED])
(action.addKey SPACE trigger)
(action.addKey ENTER trigger)
(action.addMouse LEFT trigger)
(action.addGamepad FlxGamepadInputID.A trigger FlxInputDeviceID.ALL)
(actionManager.addAction action))
(FlxG.inputs.add actionManager)
(#when debug
(kiss_flixel.DebugTools.f1ToRecord actionManager)
(kiss_flixel.DebugTools.f2ToLogSprites actionManager))
(set actionManager.resetOnStateSwitch NONE)
// Create the sprite layers but don't add them until skipping ends (This is how I win)
(doFor i (range (+ 1 LAYER_MAX))
(let [g (new FlxTypedGroup<FlxBasic>)]
(spriteLayers.add g)))))
(prop :Map<SceneTime,FlxSprite> skySprites (new Map))
(prop &mut :FlxSprite skySprite)
(prop :FlxKeyShortcutHandler<Continuation->Void> sh (new FlxKeyShortcutHandler))
(method :KeyShortcutHandler<Continuation->Void> shortcutHandler []
(set sh.cancelKey null)
sh)
(method :Void pause []
(FlxG.inputs.remove actionManager)
(FlxG.state.forEach
->:Void child
(typeCase [child]
([:FlxTypeText typeText]
(when typeText.sounds
(doFor sound typeText.sounds
(when sound.playing
(sound.pause))))
(set typeText.paused true))
([:FlxSprite sprite]
(sprite.animation?.pause))
(otherwise))
true)
(doFor sound currentSounds
(sound.pause))
(doFor track currentVoiceTracks
(track.pause))
(when music
(music.pause)))
(method resumeAndUpdateCurrentVolume [:Array<FlxSound> sounds :Float newVolume]
(doFor sound sounds
(let [[original mod] (dictGet currentSoundVolumes sound)]
(set sound.volume (* original mod newVolume))
(sound.resume))))
(method :Void resume []
(unless movie.promptedRecording
(set FlxG.mouse.visible true))
(FlxG.inputs.add actionManager)
(FlxG.state.forEach
->:Void child
(typeCase [child]
([:FlxTypeText typeText]
(when typeText.sounds
(doFor sound typeText.sounds
(when sound.playing (sound.resume))))
(set typeText.paused false))
([:FlxSprite sprite]
(sprite.animation?.resume))
(otherwise))
true)
(resumeAndUpdateCurrentVolume currentSounds soundVolume)
(resumeAndUpdateCurrentVolume currentVoiceTracks voiceVolume)
(when music
(resumeAndUpdateCurrentVolume [music] musicVolume)))
(prop &mut :SimpleWindow pauseMenu null)
(method :Void sceneSelection [:Continuation cancel]
(let [runners (movie.labelRunners)
labels (sort (collect (runners.keys)))
lastLabelIndex (labels.indexOf movie.lastLabel)]
(_chooseString
"Skip to scene?"
labels
->label {
(let [m flxMovie]
// This calls cleanup:
(cond
(m.didLoading
(m.prepareForSkip)
((dictGet runners label) m.skipMovie))
(true
((dictGet runners label) m))))
} true "escape")
(unless (= -1 lastLabelIndex) (set pauseMenu.selectedIndex (+ 1 lastLabelIndex)))
(set pauseMenu.onClose cancel)))
(method :Void showPauseMenu [:Continuation resume]
// if fullscreen changes while paused, the movie needs to re-load when resuming
(let [oldResume resume
previousFullscreen MenuState.fullscreen
resume ->:Void
(if (= previousFullscreen MenuState.fullscreen)
(oldResume)
(let [m flxMovie
instructionPointer (Reflect.field m "lastInstructionPointer")]
(m.prepareForSkip)
(m.skipMovie.runFromInstruction instructionPointer)))]
// register escape to resume (and register escape to pause when resuming lol)
(sh.registerItem "{escape} resume" ->cc {
(pauseMenu.hide)
(resume)
} true)
(let [choices ["" "Resume" "Scene Selection" "Options" "Send Feedback" "Main Menu" "Quit to Desktop"]
sceneSelectionIndex (choices.indexOf "Scene Selection")
optsIdx (choices.indexOf "Options")
feedbackIdx (choices.indexOf "Send Feedback")]
// chooseString automatically sets pauseMenu
(chooseString "PAUSED" choices
->:Void choice
(case choice
("Resume"
(resume))
("Scene Selection"
(localFunction backToPause []
(set pauseMenu.onClose null)
(pauseMenu.hide)
(showPauseMenu resume)
(set pauseMenu.selectedIndex sceneSelectionIndex))
(sh.registerItem "{escape} pause" ->cc (backToPause) true)
(sceneSelection
->:Void {
(backToPause)
}))
("Options"
(sh.cancel)
(MenuState.optionsMenu
->{
(sh.start)
(showPauseMenu resume)
(set pauseMenu.selectedIndex optsIdx)
}
null
null
this))
("Send Feedback"
(sh.cancel)
(.enableGamepadInput
(kiss_flixel.FeedbackWindow.collectFeedback
->:Void {
(sh.start)
(showPauseMenu resume)
(set pauseMenu.selectedIndex feedbackIdx)
}
null
null
true
"escape") true))
("Main Menu" (FlxG.switchState (new MenuState)))
("Quit to Desktop" (Sys.exit 0))
(never otherwise))))))
(function specialHistoryChars [:String text]
(text.replace "✓" "<check mark>"))
(function specialTypesSpeakerName [:String name :SpeechType<ActorFlxSprite> type]
(case type
((VoiceOver _)
"${name} (voiceover)")
((TextMessage _)
"${name} (text message)")
((FromPhone _)
"${name} (from phone)")
((Custom type _ _)
"${name} (${type})")
(otherwise name)))
(method :Void showDialogHistory [:Array<HistoryElement<ActorFlxSprite>> history :Continuation resume]
// register tab and escape to resume (and register escape to pause and tab to history when resuming lol)
(let [_resume
->cc {
(pauseMenu.hide)
(resume)
}]
(set pauseMenu (new SimpleWindow "HISTORY" FlxColor.BLACK textColor 0.6 0.9 true null null null "up" "down" ->:Void (_resume null)))
(pauseMenu.enableVerticalScrolling)
(pauseMenu.enableGamepadInput true)
(pauseMenu.makeText "")
(doFor element history
(case element
((Sound caption)
(pauseMenu.makeText "<${caption}>")
(pauseMenu.makeText ""))
((Dialog speaker type _wryly text)
(pauseMenu.makeText (specialTypesSpeakerName speaker type))
(pauseMenu.makeMultilineText (specialHistoryChars text))
(pauseMenu.makeText ""))
((Super text)
(pauseMenu.makeText text)
(pauseMenu.makeText ""))
(never otherwise)))
(sh.registerItem "{tab} resume" _resume true)
(sh.registerItem "{escape} resume" _resume true)
(pauseMenu.setUIControlColor buttonColor)
(pauseMenu.show)
(pauseMenu.scrollToBottom)))
(method loadSet [path]
(let [setSprite (new FlxSprite 0 0)]
// Load uniquely so we can draw on sets for specific scenes
(setSprite.loadGraphic path false 0 0 true)))
(method cloneSet [set]
(set.clone))
(method :Void showSet [:FlxSprite setSprite :SceneTime time :ScenePerspective perspective :Appearance appearance :FlxCamera camera :Bool skipping :Continuation cc]
(doFor layer spriteLayers
(set layer.cameras [camera]))
(case appearance
(FirstAppearance
(setSprite.setGraphicSize FlxG.width)
(when (> setSprite.height FlxG.height)
(setSprite.setGraphicSize 0 FlxG.height))
(setSprite.updateHitbox)
(setSprite.screenCenter))
(otherwise))
// draw a sky background depending on time of day, int/ext
(ifLet [specialSkySprite (dictGet skySprites time)]
{
(set skySprite (specialSkySprite.clone))
(skySprite.setGraphicSize (Std.int setSprite.width))
(skySprite.updateHitbox)
(unless (>= skySprite.height setSprite.height)
(skySprite.setGraphicSize 0 (Std.int setSprite.height))
(skySprite.updateHitbox)
(let [hOverflow (- skySprite.width setSprite.width)
unscaledOverflow (/ hOverflow skySprite.scale.x)
half (iHalf unscaledOverflow)]
(set skySprite.clipRect (new FlxRect 0 0 (Std.int (- skySprite.frameWidth unscaledOverflow)) skySprite.frameHeight))))
}
(let [skyColor (case time
((or Morning Day) DAY_SKY_COLOR)
(Evening EVENING_SKY_COLOR)
(Night NIGHT_SKY_COLOR)
(never null))]
(set skySprite (new FlxSprite))
(skySprite.makeGraphic (Std.int setSprite.width) (Std.int setSprite.height) skyColor true)))
(set skySprite.x setSprite.x)
(set skySprite.y 0)
(set skySprite.alpha lastSkyAlpha)
(flxMovie.setCameras skySprite [camera])
(.add (first spriteLayers.members) skySprite)
// Remove cameras top to bottom:
(FlxG.cameras.remove flxMovie.spriteChangeDebugCamera false)
(FlxG.cameras.remove flxMovie.uiCamera false)
(unless skipping
(doFor camera flxMovie.tempCamerasOrder
(FlxG.cameras.remove camera false)))
(FlxG.cameras.remove flxMovie.screenCamera false)
(unless skipping
(doFor camera flxMovie.tempBgCamerasOrder
(FlxG.cameras.remove camera false)))
// Add cameras bottom to top
(unless skipping
(doFor camera flxMovie.tempBgCamerasOrder
(FlxG.cameras.add camera (dictGet flxMovie.tempBgCameras camera))))
(FlxG.cameras.add camera false)
(FlxG.cameras.add flxMovie.screenCamera false)
(unless skipping
(doFor camera flxMovie.tempCamerasOrder
(FlxG.cameras.add camera (dictGet flxMovie.tempCameras camera))))
(FlxG.cameras.add flxMovie.uiCamera false)
(FlxG.cameras.add flxMovie.spriteChangeDebugCamera false)
// Add the set
(flxMovie.setCameras setSprite [camera])
(.add (first spriteLayers.members) setSprite)
(cc))
(prop &mut lastSkyAlpha 1.0)
(method :Void hideSet [:FlxSprite set :FlxCamera camera :Continuation cc]
(when (FlxG.cameras.list.contains camera)
(FlxG.cameras.remove camera false))
(set lastSkyAlpha skySprite.alpha)
(.remove (first spriteLayers.members) skySprite true)
(.remove (first spriteLayers.members) set true)
(cc))
(method :FlxSprite drawLight [:FlxLightSource source]
(lightSprite.drawPolygon source.points source.color (object color FlxColor.TRANSPARENT))
(lightMask.drawPolygon source.points FlxColor.BLACK (object color FlxColor.TRANSPARENT)))
(var DAY_SKY_COLOR FlxColor.CYAN)
(var NIGHT_SKY_COLOR FlxColor.BLACK)
(var EVENING_SKY_COLOR (FlxColor.fromRGB 23 28 70))
(var EVENING_COLOR (FlxColor.fromRGBFloat 0.5 0 0.5 0.2))
(var NIGHT_COLOR (FlxColor.fromRGBFloat 0 0 0 0.5))
(prop &mut :FlxSprite lightSprite null)
(prop &mut :FlxSprite lightMask null)
(prop &mut :FlxSprite darkness null)
(prop &mut :FlxColor darkColor FlxColor.BLACK)
(var &mut :FlxMouseEventManager mm null)
(method :Void chooseString [:String prompt :Array<String> choices :String->Void submit]
(_chooseString prompt choices submit))
// UI color scheme made editable with savedVar for faster testing
(savedVar :Int _textColor (FlxColor.GRAY.getLightened 0.4))
(var :FlxColor textColor (property get null))
(function get_textColor []
(FlxColor.fromInt _textColor))
(savedVar :Int _buttonColor FlxColor.WHITE)
(var :FlxColor buttonColor (property get null))
(function get_buttonColor []
(FlxColor.fromInt _buttonColor))
(savedVar :Int _disabledButtonColor FlxColor.GRAY)
(var :FlxColor disabledButtonColor (property get null))
(function get_disabledButtonColor []
(FlxColor.fromInt _disabledButtonColor))
(method :Void _chooseString [:String prompt :Array<String> choices :String->Void submit &opt :Bool xButton :String xKey]
(set pauseMenu
(kiss_flixel.SimpleWindow.promptForChoice
prompt
choices
submit
FlxColor.BLACK
textColor
buttonColor
0.8
0.8
?xButton
(or xKey "")
"left"
"right"
"up"
"down"
"enter"
null
false
true))
(pauseMenu.setUIControlColor buttonColor)
(pauseMenu.enableGamepadInput true [=>START ""]))
(method :Void enterString [:String prompt :String->Void submit]
(sh.cancel)
(set pauseMenu
(SimpleWindow.promptForString
prompt
->s {(sh.start)(submit s)}
null
null
null
null
null
false
null
null
null
null
null
null
null
true)))
(method :Void defineFlxPoint [:FlxPoint->Void submit]
(unless mm
(set mm (new FlxMouseEventManager)))
(FlxG.state.add mm)
(let [screen (new FlxSprite)]
(screen.makeGraphic FlxG.width FlxG.height (FlxColor.fromRGBFloat 0 1 0 0.2))
(set screen.cameras [flxMovie.uiCamera])
(flxMovie.nextFrameActions.push ->:Void {
(doFor camera FlxG.cameras.list (set camera.zoom 0.5))
(set flxMovie.uiCamera.zoom 1)
(set flxMovie.spriteChangeDebugCamera.zoom 1)
(mm.add screen
->screen {
(mm.remove screen)
(FlxG.state.remove screen true)
(let [pos (FlxG.mouse.getScreenPosition FlxG.camera)]
(doFor camera FlxG.cameras.list (set camera.zoom 1))
(submit pos))
})
(FlxG.state.add screen)
})))
(method :Void defineStagePosition [:FlxCamera camera :StagePosition->Void submit &opt :StagePosition oldPos]
(let [db (new DebugLayer)]
(set db.cameras [camera])
(when oldPos
(db.drawCircle oldPos.x oldPos.y 4 FlxColor.YELLOW 2))
(FlxG.state.add db)
(defineFlxPoint
->point
{(FlxG.state.remove db true)
(submit (new StagePosition point.x point.y (or oldPos?.z 5.0)))})))
(method :Void defineLightSource [:FlxLightSource->Void submit]
(let [points []]
(withFunctions
[
(getNextPoint []
(defineFlxPoint
->point
{
(points.push point)
(getNextPoint)
}))
]
(getNextPoint)
(sh.registerItem
"{enter} submit light source"
(onceLambda [cc]
(doFor camera FlxG.cameras.list (set camera.zoom 1))
(when points
// TODO allow color choice
(submit (new FlxLightSource points FlxColor.TRANSPARENT)))
(cc)) true))))
(method :Void showLighting [:SceneTime sceneTime :Array<FlxLightSource> lightSources :FlxCamera camera]
(set lightSprite (new FlxSprite))
(lightSprite.makeGraphic FlxG.width FlxG.height FlxColor.TRANSPARENT true)
(set lightMask (new FlxSprite))
(lightMask.makeGraphic FlxG.width FlxG.height FlxColor.WHITE true)
(set darkness (new FlxSprite))
// TODO handle morning better
(set darkColor (case sceneTime
(Evening EVENING_COLOR)
(Night NIGHT_COLOR)
(otherwise FlxColor.TRANSPARENT)))
(darkness.makeGraphic FlxG.width FlxG.height darkColor true)
(doFor source lightSources (drawLight source))
(blackAlphaMaskFlxSprite darkness lightMask darkness)
(set lightSprite.cameras [flxMovie.screenCamera])
(set darkness.alpha darkColor.alphaFloat)
(set darkness.cameras [flxMovie.screenCamera])
(FlxG.state.add darkness)
(FlxG.state.add lightSprite))
(method :Void hideLighting []
(when darkness
(FlxG.state.remove darkness true)
(set darkness null))
(when lightSprite
(FlxG.state.remove lightSprite true)
(set lightSprite null)))
(method :Void cleanup []
(when music (stopSong))
(hideTitleCard)
(hideTitleCard true)
(hideBlackScreen)
(_hideDialog)
(hideLighting)
(when (and flxMovie.sceneKey (< 0 (Lambda.count flxMovie.scenes)))
(let [scene (dictGet flxMovie.scenes flxMovie.sceneKey)]
(hideSet scene.set scene.camera ->:Void {})
(doFor prop scene.props
(hideProp prop.prop scene.camera ->:Void {}))
(doFor character scene.characters
(hideCharacter character scene.camera ->:Void {}))))
// Don't remove the sprite layers, which are added in new()
// But DO remove everything in them
(doFor layer spriteLayers
(layer.clear))
// clear out currentSounds and currentVoiceTracks
(restoreOriginalVolumes.clear)
(whileLet [sound (currentSounds.pop)]
(stopSound sound))
(currentSoundVolumes.clear)
(whileLet [vt (currentVoiceTracks.pop)]
(stopVoiceTrack vt))
(doFor text creditsText
(FlxG.state.remove text true)
(text.destroy))
(set creditsText [])
)
(method :Option<AutoZConfig> autoZConfig [] (Some (object zPerLayer flxMovie.STAGE_BEHIND_DY frontLayer 0)))
(method loadActor [path]
// TODO obviously this is wrong:
null)
(method :Void showCharacter [:Character<ActorFlxSprite> character :Appearance appearance :FlxCamera camera :Continuation cc]
// Why is this necessary? Who knows
// (FlxG.state.remove character.actor true)
// Do sizing only on first appearance
(whenLet [FirstAppearance appearance]
// All actors same width, display centered on x
(character.actor.setGraphicSize flxMovie.ACTOR_WIDTH)
(character.actor.updateHitbox))
(flxMovie.setCameras character.actor [camera])
(set character.actor.flipX ?!(= character.stageFacing character.actor.defaultFacing))
// For characters whose images are forward-facing portraits, it's wrong to flip them based on facing:
(when character.actor.portraitFacingForward
(set character.actor.flipX false))
(set character.actor.x
(- character.stagePosition.x
(/ character.actor.width 2)))
(set character.actor.y character.stagePosition.y)
// Bump sprites up from the bottom if they're too tall
(cond
((flxMovie.presetPositions.exists (character.stagePosition.stringify))
(let [bottom (+ character.actor.y character.actor.height)]
(when (> bottom FlxG.height)
(-= character.actor.y (- bottom FlxG.height))))
// Display with y adjusted by z:
(-= character.actor.y character.stagePosition.z)
(let [layer (- LAYER_MAX 1 (Std.int (/ character.stagePosition.z flxMovie.STAGE_BEHIND_DY)))]
(.add (nth spriteLayers.members layer) character.actor)))
(true
(.add (nth spriteLayers.members (min (- LAYER_MAX 1) (Std.int character.stagePosition.z))) character.actor)))
(cc))
(method :Void hideCharacter [:Character<ActorFlxSprite> character :FlxCamera camera :Continuation cc]
(FlxG.state.remove character.actor true)
(doFor layer spriteLayers
(layer.remove character.actor true))
(cc))
(prop &mut :Null<Continuation> nextCC)
(method onContinue [:FlxActionDigital continueAction]
(whenLet [cc nextCC]
(set nextCC null)
(cc)))
(method &public :Void _startWaitForInput [:Continuation cc]
(set nextCC cc))
(method &public :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)
(prop &mut :FlxSprite loadingCard null)
(method :Void showTitleCard [:Array<String> text :Continuation cc &opt :Bool loading]
(if loading
{
(set loadingCard (new FlxSprite))
(dictSet kiss_flixel.SpriteTools.ignoreObjects loadingCard true)
(set loadingCard.cameras [flxMovie.uiCamera])
}
{
(set titleCard (new FlxSprite))
(set titleCard.cameras [flxMovie.screenCamera])
})
(let [card (if loading loadingCard titleCard)]
(card.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
(SpriteTools.writeOnSprite (text.shift) TITLE_SIZE card (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 card (object x (Percent 0.5) y (Pixels subtitleY)))
(+= subtitleY SUBTITLES_SIZE SUBTITLES_MARGIN))
(FlxG.state.add card)
(cc)))
(prop &mut :Bool isLoading false)
(var LOAD_CALLS_PER_FRAME 2)
(var LOAD_CALLS_PER_FRAME_SCAVENGE 20)
(prop &mut :Void->Void doneLoadingCC)
(method :Void doneLoading []
(set FlxG.mouse.visible true)
(set isLoading false)
(unless (Reflect.field FlxG.state "focused")
(pause))
(set FlxG.autoPause true)
(FlxG.state.remove bar true)
(FlxG.state.remove loop true)
(print (/ (/ flash.system.System.totalMemory 1024) 1000) "Memory in use: ")
(doneLoadingCC))
(prop &mut :Int lastIp 0)
(prop &mut :Float inputIconElapsed 0)
(prop inputIconFluctuation 0.2)
(prop inputIconFluctuationSpeed 2)
(method :Void update []
(+= inputIconElapsed FlxG.elapsed)
(let [currentScale (+ 1.0 (* inputIconFluctuation (Math.sin (* inputIconFluctuationSpeed inputIconElapsed))))]
(inputIcon?.scale.set currentScale currentScale))
(when movie.skipTarget
(+= barProgress (- movie.lastInstructionPointer lastIp))
(set lastIp movie.lastInstructionPointer)
(when (= barProgress barMax)
(doneLoading))))
(prop &mut :FlxBar bar)
(prop &mut :flixel.addons.util.FlxAsyncLoop loop null)
(prop &mut :Int barProgress 0)
(prop &mut :Int barMax 0)
(method :Void doLoading [:Array<Void->Void> _load :Bool scavenged :Continuation cc :Continuation done]
(set isLoading true)
(set FlxG.autoPause false)
(set FlxG.mouse.visible false)
(set barProgress 0)
(set lastIp 0)
(set barMax (+ _load.length (or movie.skipTarget 1) -1))
(set bar (new FlxBar 0 0 LEFT_TO_RIGHT (iThird FlxG.width) SimpleWindow.textSize this "barProgress" 0 barMax true))
(set doneLoadingCC done)
(set loop (new flixel.addons.util.FlxAsyncLoop (+ 1 _load.length)
->:Void
(ifLet [nextLoad (_load.shift)]
{
(nextLoad)
(+= barProgress 1)
}
{
(unless movie.skipTarget (doneLoading))
(flxMovie.nextFrameActions.push cc)
}) (if scavenged LOAD_CALLS_PER_FRAME_SCAVENGE LOAD_CALLS_PER_FRAME)))
(haxe.Timer.delay ->:Void (loop.start) 1)
(set bar.cameras [flxMovie.uiCamera])
(bar.createColoredEmptyBar FlxColor.BLACK true FlxColor.WHITE)
(bar.createColoredFilledBar FlxColor.WHITE false)
(bar.screenCenter)
(FlxG.state.add bar)
(FlxG.state.add loop)
(dictSet kiss_flixel.SpriteTools.ignoreObjects bar true)
(dictSet kiss_flixel.SpriteTools.ignoreObjects loop true))
(method :Void hideTitleCard [&opt :Bool loading]
(if loading
(when loadingCard
(FlxG.state.remove loadingCard true)
(set loadingCard null))
(when titleCard
(FlxG.state.remove titleCard true)
(set titleCard null))))
// 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)
(prop &mut :FlxSprite dialogBox)
(prop &mut :FlxSprite superText)
(prop &mut :FlxText dialogText)
(prop &mut :FlxText _speakerNameText)
(prop :FlxText speakerNameText (property get set))
(method set_speakerNameText [text]
(when _speakerNameText
(FlxG.state.remove _speakerNameText true)
(set _speakerNameText null))
(set _speakerNameText text))
(method get_speakerNameText []
_speakerNameText)
(var SUPER_MARGIN 10)
(method applyFormat [:FlxText text]
(text.applyMarkup
text.text
[
(new FlxTextFormatMarkerPair (new FlxTextFormat FlxColor.CYAN) "*")
]))
(method showExpression [:ActorFlxSprite actor :String wryly]
// When an actor is associated with the line, check for an animation matching the wryly
(if wryly
(actor.playAnimation wryly)
(actor.playAnimation "neutral")))
(method &public _showDialog [:String speakerName :SpeechType<ActorFlxSprite> type :String wryly :String text :Continuation cc]
// TODO handle text messages, wrylies, off-screen, from-phone, etc. via (case type)
// TODO allow sounds for wrylies, like the dispatch click
(localVar &mut :Float speakerNameX -1)
(case type
(Super
(when superText (FlxG.state.remove superText true))
(set superText (SpriteTools.textPlate text DIALOG_SIZE SUPER_MARGIN null null applyFormat))
// If supertext can be done in a single centered line, do so
(when (<= superText.width FlxG.width)
(set superText.cameras [flxMovie.uiCamera])
(superText.screenCenter)
(set superText.y flxMovie.DIALOG_Y)
(FlxG.state.add superText)
(return)))
((OnScreen character)
(set speakerNameX (+ character.actor.x (fHalf character.actor.width))))
((or (OffScreen actor) (VoiceOver actor) (TextMessage actor) (FromPhone actor))
null)
(otherwise))
// Make a dialog box
(unless dialogBox
(set dialogBox (new FlxSprite flxMovie.DIALOG_X flxMovie.DIALOG_Y))
(dialogBox.makeGraphic flxMovie.DIALOG_WIDTH flxMovie.DIALOG_HEIGHT DIALOG_BOX_COLOR))
(set dialogBox.cameras [flxMovie.uiCamera])
(FlxG.state.add dialogBox)
// show the dialog
(unless dialogText
(set dialogText (new FlxText flxMovie.DIALOG_X flxMovie.DIALOG_Y flxMovie.DIALOG_WIDTH "" DIALOG_SIZE)))
(set dialogText.cameras [flxMovie.uiCamera])
(FlxG.state.add dialogText)
// *cyan* for italics
(set dialogText.text text)
(applyFormat dialogText)
// show the speaker name
(set speakerNameText null)
(set speakerNameText (new FlxText flxMovie.DIALOG_X flxMovie.DIALOG_Y 0 "" DIALOG_SIZE))
(set speakerNameText.cameras [flxMovie.uiCamera])
(FlxG.state.add speakerNameText)
(if speakerName
{
(set speakerNameText.text "${speakerName}:")
(when (= speakerNameX -1)
(set speakerNameX flxMovie.DIALOG_X))
(-= speakerNameX (fHalf speakerNameText.width))
(clamp speakerNameX flxMovie.DIALOG_X (- (+ flxMovie.DIALOG_X flxMovie.DIALOG_WIDTH) speakerNameText.width))
(set speakerNameText.x speakerNameX)
(set dialogText.y (+ flxMovie.DIALOG_Y speakerNameText.height))
}
(set dialogText.y flxMovie.DIALOG_Y))
(set dialogText.size DIALOG_SIZE)
(while (< FlxG.height (+ dialogText.y dialogText.height))
(-= dialogText.size 6)))
(method &public :Void _hideDialog []
(when dialogText
(FlxG.state.remove dialogText)
(set dialogText null))
(when speakerNameText
(FlxG.state.remove speakerNameText)
(set speakerNameText null))
(when dialogBox
(FlxG.state.remove dialogBox)
(set dialogBox null))
(when superText
(FlxG.state.remove superText true)
(set superText null)))
(savedVar :Float soundVolume 1.0)
(savedVar :Float masterVolume 1.0)
(prop :Array<FlxSound> currentSounds [])
(prop :Map<FlxSound,Array<Float>> currentSoundVolumes (new Map))
(method loadSound [path]
(let [s (new HFlxSound)]
(s.loadEmbedded path)
(set s.persist true)
(set s.autoDestroy false)
s))
(method :Void playSound [:FlxSound sound :Float volumeMod &opt :Continuation cc]
(let [originalVolume sound.volume
onComplete ->{
(currentSounds.remove sound)
(currentSoundVolumes.remove sound)
(set sound.volume originalVolume)
(when cc (cc))
}]
(dictSet currentSoundVolumes sound [originalVolume volumeMod])
(*= sound.volume volumeMod soundVolume)
(set sound.onComplete onComplete))
(currentSounds.push sound)
(sound.play))
(method :Void stopSound [:FlxSound sound]
(currentSounds.remove sound)
(sound.stop))
(var CAPTION_Y 50)
// This group is just for tracking y gaps where a new caption could appear
(prop &mut :FlxGroup captions null)
(prop :Map<Int,FlxSprite> captionIds (new Map))
(method :Void showCaption [:String description :Int id]
(unless captions (set captions (new FlxGroup)))
(let [firstNull (captions.getFirstNull)
row (case firstNull (-1 0) (otherwise firstNull))
plate (SpriteTools.textPlate "[${description}]" DIALOG_SIZE SUPER_MARGIN)]
(plate.screenCenter)
(set plate.y (+ CAPTION_Y (* plate.height row)))
(set plate.cameras [flxMovie.uiCamera])
(captions.add plate)
(dictSet captionIds id plate)
(FlxG.state.add plate)))
(method :Void hideCaption [:Int id]
(let [plate (dictGet captionIds id)]
(captions.remove plate)
(FlxG.state.remove plate true)
(captionIds.remove id)))
(method :Float getSoundLength [:FlxSound sound]
sound.length)
(method loadSong [path]
(let [song (FlxG.sound.load path)]
(assert !(= 0 song.length) "song from $path has 0 length! avoid mp3s for this reason")
(set song.persist true)
(set song.autoDestroy false)
song))
(method :Float getSongLength [:FlxSound song]
(/ song.length 1000))
(savedVar :Float voiceVolume 1.0)
(prop :Array<FlxSound> currentVoiceTracks [])
(prop :Map<FlxSound,Function> restoreOriginalVolumes (new Map))
(method loadVoiceTrack [wavPath]
(let [vt (FlxG.sound.load wavPath)]
(set vt.persist true)
(set vt.autoDestroy false)
vt))
(method :Void playVoiceTrack [:FlxSound track :Float volumeMod :Float start :Float end :Continuation cc]
(let [originalVolume track.volume
restoreOriginalVolume ->{(set track.volume originalVolume)(currentSoundVolumes.remove track)}]
(dictSet currentSoundVolumes track [originalVolume volumeMod])
(dictSet restoreOriginalVolumes track restoreOriginalVolume)
(*= track.volume volumeMod voiceVolume)
(set track.onComplete ->{
(currentVoiceTracks.remove track)
(restoreOriginalVolume)
(cc)
}))
(currentVoiceTracks.push track)
(track.play true (* 1000 start) (* 1000 end)))
(method :Void stopVoiceTrack [:FlxSound track]
(currentVoiceTracks.remove track)
(track.stop)
(when (restoreOriginalVolumes.exists track)
(dictGet restoreOriginalVolumes track)))
(prop &mut :FlxSound music)
(prop &mut :FlxTimer musicFadeTimer)
(prop MUSIC_FADE_SEC 1)
(prop MUSIC_FADE_STEPS 10)
(savedVar :Float musicVolume 1.0)
// You should normally never call this yourself
(method :Void updateMusicVolume []
(when music
(let [originalVolumeSet (dictGet currentSoundVolumes music)]
(when (> 1.0 (first originalVolumeSet))
(+= (first originalVolumeSet) (/ 1.0 MUSIC_FADE_STEPS)))
(set music.volume (* (first originalVolumeSet) (second originalVolumeSet) musicVolume)))))
(method :Void playSong [:FlxSound song :Float volumeMod :Bool loop :Bool waitForEnd :Continuation cc]
(when music (stopSong))
(let [onFinish ->{
(currentSoundVolumes.remove music)
(set music null)
(when waitForEnd (cc))
}]
(set music (FlxG.sound.play (Reflect.field song "_sound") 0 loop null true onFinish))
// Because music needs to fade in, an array stores its current fade percentage AND its eventual volumeMod target.
// All of this is modulated to the musicVolume property which represent's the player's music volume setting
(dictSet currentSoundVolumes music [0 volumeMod])
(set musicFadeTimer
(.start (new FlxTimer)
(/ MUSIC_FADE_SEC MUSIC_FADE_STEPS)
->:Void _ (updateMusicVolume)
MUSIC_FADE_STEPS))
(set music.persist true)
(unless waitForEnd (cc))))
(method :Void changeSongVolume [:Float volumeMod :Continuation cc]
(let [[fade oldMod] (dictGet currentSoundVolumes music)]
(set music.volume (* fade volumeMod musicVolume))
(dictSet currentSoundVolumes music [fade volumeMod]))
(cc))
(method :Void stopSong []
(when music (music.stop))
(when musicFadeTimer (musicFadeTimer.cancel))
(set music null))
(var PROP_MIN_WIDTH 200)
(var PROP_MAX_WIDTH 500)
(prop &mut :Bool autoPlaceProps true)
(method scaleProp [:FlxSprite prop]
(let [propKey (_propKey prop)]
(unless (flxMovie.propScales.exists propKey)
(when (flxMovie.textProps.contains prop)
(return))
(unless (StringTools.contains propKey "anonProp")
// Set the initial scale so the thing fits on the screen, at least
(prop.setGraphicSize FlxG.width)
(prop.updateHitbox)
(when (> prop.height FlxG.height)
(prop.setGraphicSize 0 FlxG.height))
(flxMovie.propScales.put propKey (new JsonFloat prop.scale.x))))
(let [:Float scale .value (flxMovie.propScales.get propKey)]
(when (StringTools.contains propKey "anonProp") (return))
(unless (flxMovie.propsInScene.exists flxMovie.sceneKey)
(dictSet flxMovie.propsInScene flxMovie.sceneKey []))
(unless !(= -1 (.indexOf (dictGet flxMovie.propsInScene flxMovie.sceneKey) propKey))
(.push (dictGet flxMovie.propsInScene flxMovie.sceneKey) propKey))
(prop.scale.set scale scale)
(prop.updateHitbox))))
(method loadProp [path]
(let [propSprite (new FlxSprite 0 0)]
// Load props uniquely because they can be drawn on
(propSprite.loadGraphic path false 0 0 true)))
(prop &mut :Bool oldScaleBehavior false)
(method :Void showProp [:FlxSprite prop :StagePosition position :Appearance appearance :FlxCamera camera :Continuation cc]
(flxMovie.setCameras prop [camera])
(let [width (min (max prop.width PROP_MIN_WIDTH) PROP_MAX_WIDTH)]
(unless (and !oldScaleBehavior (flxMovie.textProps.contains prop))
(whenLet [FirstAppearance appearance]
(when (= 1 prop.scale.x prop.scale.y)
(prop.setGraphicSize width)
(prop.updateHitbox))))
(set prop.x position.x)
(set prop.y position.y)
// When using preset positions, automatically move everything around and scale it
(cond
((flxMovie.presetPositions.exists (position.stringify))
// if the prop is too tall, shrink it heightwise
(when (> prop.height flxMovie.DIALOG_Y)
(prop.setGraphicSize 0 (Std.int flxMovie.DIALOG_Y))
(prop.updateHitbox))
(-= prop.x (/ prop.width 2))
(-= prop.y (/ prop.height 2))
(let [propBottom (+ prop.y prop.height)]
// If a prop goes below the edge, bring it up
(when (> propBottom FlxG.height)
(-= prop.y (- propBottom FlxG.height)))))
// Otherwise, let the director scale props
(true
(scaleProp prop)))
(let [layerNum position.z]
(assertEquals layerNum (Std.int layerNum))
(.add (nth spriteLayers.members (min LAYER_MAX (+ 1 (Std.int layerNum)))) prop)))
(set prop.x (Math.round prop.x))
(set prop.y (Math.round prop.y))
(cc))
(var :Map<FlxSprite,String> _propKeys (new Map))
(var &mut anonProps 0)
(function _propKey [:FlxSprite prop]
(ifLet [key (dictGet _propKeys prop)]
key
(dictSet _propKeys prop (or prop.graphic.assetsKey "anonProp#${anonProps++}"))))
(method :Void hideProp [:FlxSprite prop :FlxCamera camera cc]
(FlxG.state.remove prop true)
(unless (flxMovie.propsInScene.exists flxMovie.sceneKey)
(dictSet flxMovie.propsInScene flxMovie.sceneKey []))
(let [propKey (_propKey prop)]
(.remove (dictGet flxMovie.propsInScene flxMovie.sceneKey) propKey))
(doFor layer spriteLayers
(layer.remove prop true))
(cc))
(prop &mut :kiss.List<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))
(set blackBG.cameras [flxMovie.screenCamera])
(blackBG.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
(FlxG.state.add blackBG))
(method :Void hideBlackScreen []
(when blackBG
(FlxG.state.remove blackBG true)
(set blackBG null)))
(var IDEAL_SCROLL_SPEED 200)
(method :Void rollCredits [:Array<CreditsLine> credits cc &opt :Float timeLimit]
(localVar &mut calledCC false)
(localVar &mut textY FlxG.height)
(var oneColSize 64)
(var twoColSize 48)
(var threeColSize 32)
(var creditMargin 20)
(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 (- (* FlxG.width 0.3) t1.width (/ twoColumnGap 2)))
(set t1.y textY)
(set t2.x (+ (* FlxG.width 0.3) (/ twoColumnGap 2)))
(let [clippedRight (- (+ t2.x t2.width) FlxG.width)]
(when (> clippedRight 0)
(-= t2.x clippedRight)))
(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 (- FlxG.width creditMargin t3.width))
(set t3.y textY))
(+= textY threeColSize creditMargin))
(otherwise)))
// Squish overflowing texts inward:
(doFor text creditsText
(when (< text.x 0)
(text.setGraphicSize (- text.width (- text.x)))
(set text.x 0))
(when (> (+ text.x text.width) FlxG.width)
(text.setGraphicSize (- text.width (- (+ text.x text.width) FlxG.width)))))
(let [pixelsToScroll
(+ textY .height (last creditsText))
idealTimeLimit
(/ pixelsToScroll IDEAL_SCROLL_SPEED)
timeLimitDifference (- idealTimeLimit timeLimit)
scrollSpeed
(if timeLimit
(/ pixelsToScroll timeLimit)
IDEAL_SCROLL_SPEED)]
(#when debug
(when !(= scrollSpeed IDEAL_SCROLL_SPEED)
(print "Time given for credit roll (${timeLimit}s) is $(Math.abs timeLimitDifference)sec $(if (> timeLimitDifference 0) "too short" "too long") for ideal speed.")))
(doFor text creditsText
(FlxG.state.add text)
(set text.cameras [flxMovie.uiCamera])
(let [:Array<Dynamic> tweenArgs []]
(tweenArgs.push false)
(tweenArgs.push text)
(tweenArgs.push text.x)
(tweenArgs.push (- text.y textY))
(tweenArgs.push scrollSpeed)
(tweenArgs.push ->:Void (unless calledCC (set calledCC true) (cc)))
(Reflect.callMethod flxMovie (Reflect.field flxMovie "linearMotion") tweenArgs)))))
(prop &mut :FlxSprite inputIcon)
(method :Void showInputIcon []
(set inputIconElapsed 0)
(unless inputIcon
(set inputIcon (SpriteTools.textPlate "[...]" DIALOG_SIZE SUPER_MARGIN null null applyFormat))
(flixel.util.FlxSpriteUtil.drawRect inputIcon 0 0 inputIcon.width inputIcon.height FlxColor.TRANSPARENT (object thickness 1 color FlxColor.WHITE)))
(set inputIcon.cameras [flxMovie.uiCamera])
(inputIcon.screenCenter)
(set inputIcon.y (- flxMovie.DIALOG_Y inputIcon.height))
(FlxG.state.add inputIcon))
(method :Void hideInputIcon []
(FlxG.state.remove inputIcon true))
(method :Void prepareForRecording []
(set FlxG.mouse.visible false))
(method :FlxLightSource offsetLightSource [:FlxLightSource source :StagePosition offset]
(new FlxLightSource (for point source.points (FlxPoint.get (+ point.x offset.x) (+ point.y offset.y))) source.color))