1000 lines
41 KiB
Plaintext
1000 lines
41 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 :Array<FlxTypedGroup<FlxSprite>> spriteLayers [])
|
|
(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 {
|
|
(FlxG.switchState (new MenuState))
|
|
}))
|
|
("Start From Beginning" (movie.run))
|
|
(never otherwise)) true "escape")
|
|
(set pauseMenu.onClose ->:Void (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)
|
|
|
|
(doFor i (range (+ 1 LAYER_MAX))
|
|
(let [g (new FlxTypedGroup<FlxSprite>)]
|
|
(spriteLayers.push g)
|
|
(FlxG.state.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]
|
|
([: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 []
|
|
(set FlxG.mouse.visible true)
|
|
(FlxG.inputs.add actionManager)
|
|
(FlxG.state.forEach
|
|
->:Void child
|
|
(typeCase [child]
|
|
([: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:
|
|
(m.prepareForSkip)
|
|
((dictGet runners label) m.skipMovie))
|
|
} 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)
|
|
(sh.registerItem "{escape} pause" ->cc (showPauseMenu cc) true)
|
|
(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"
|
|
(sh.registerItem "{escape} pause" ->cc (showPauseMenu cc) true)
|
|
(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)
|
|
(sh.registerItem "{tab} show history" ->cc (showDialogHistory flxMovie.dialogHistory cc) true)
|
|
(sh.registerItem "{escape} pause" ->cc (showPauseMenu cc) true)
|
|
(resume)
|
|
}]
|
|
|
|
(set pauseMenu (new SimpleWindow "HISTORY" FlxColor.BLACK FlxColor.WHITE 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.makeWrappedText (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.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 :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.cameras [camera])
|
|
(.add (first spriteLayers) skySprite)
|
|
|
|
(FlxG.cameras.remove flxMovie.uiCamera false)
|
|
(FlxG.cameras.remove flxMovie.screenCamera false)
|
|
(FlxG.cameras.add camera)
|
|
(FlxG.cameras.add flxMovie.screenCamera)
|
|
(FlxG.cameras.add flxMovie.uiCamera)
|
|
(set setSprite.cameras [camera])
|
|
(.add (first spriteLayers) setSprite)
|
|
(cc))
|
|
|
|
(method :Void hideSet [:FlxSprite set :FlxCamera camera :Continuation cc]
|
|
(when (FlxG.cameras.list.contains camera)
|
|
(FlxG.cameras.remove camera false))
|
|
|
|
(.remove (first spriteLayers) skySprite true)
|
|
(.remove (first spriteLayers) 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))
|
|
|
|
(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
|
|
FlxColor.WHITE
|
|
0.8
|
|
0.8
|
|
?xButton
|
|
(or xKey "")
|
|
"left"
|
|
"right"
|
|
"up"
|
|
"down"
|
|
"enter"
|
|
null
|
|
false
|
|
true))
|
|
(pauseMenu.enableGamepadInput true [=>START ""]))
|
|
|
|
(method :Void enterString [:String prompt :String->Void submit]
|
|
(set pauseMenu
|
|
(SimpleWindow.promptForString prompt submit)))
|
|
|
|
(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)
|
|
(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 []
|
|
(hideTitleCard)
|
|
(hideBlackScreen)
|
|
(hideDialog)
|
|
(hideLighting)
|
|
|
|
(when flxMovie.sceneKey
|
|
(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))
|
|
)
|
|
|
|
(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))
|
|
|
|
(set character.actor.cameras [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 layer) character.actor)))
|
|
(true
|
|
(.add (nth spriteLayers (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 :Void startWaitForInput [:Continuation cc]
|
|
(set nextCC cc))
|
|
|
|
(method :Void stopWaitForInput [:Continuation cc]
|
|
(when (= nextCC cc)
|
|
(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 FlxG.width FlxG.height 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))
|
|
(FlxG.state.add titleCard)
|
|
(cc))
|
|
|
|
(prop &mut :Bool isLoading false)
|
|
(var LOAD_CALLS_PER_FRAME 2)
|
|
(var LOAD_CALLS_PER_FRAME_SCAVENGE 20)
|
|
(method :Void doLoading [:Array<Void->Void> _load :Bool scavenged :Continuation cc]
|
|
(set isLoading true)
|
|
(set FlxG.autoPause false)
|
|
(set FlxG.mouse.visible false)
|
|
(localVar &mut :flixel.addons.util.FlxAsyncLoop loop null)
|
|
(let [doneFuncs []
|
|
bar (new FlxBar 0 0 LEFT_TO_RIGHT (iThird FlxG.width) SimpleWindow.textSize doneFuncs "length" 0 _load.length true)]
|
|
(set loop (new flixel.addons.util.FlxAsyncLoop (+ 1 _load.length)
|
|
->:Void
|
|
(ifLet [nextLoad (_load.shift)]
|
|
{
|
|
(nextLoad)
|
|
(doneFuncs.push nextLoad)
|
|
}
|
|
{
|
|
(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: ")
|
|
(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)))
|
|
|
|
(method :Void hideTitleCard []
|
|
(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 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 attribute on-screen dialogue to the character's stageposition
|
|
|
|
// 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)
|
|
(startWaitForInput cc)
|
|
(return)))
|
|
((OnScreen character)
|
|
(set speakerNameX (+ character.actor.x (fHalf character.actor.width)))
|
|
// When an actor is associated with the line, check for an animation matching the wryly
|
|
(let [actor (the ActorFlxSprite character.actor)]
|
|
(if wryly
|
|
(actor.playAnimation wryly)
|
|
(actor.playAnimation "neutral"))))
|
|
((or (OffScreen actor) (VoiceOver actor) (TextMessage actor) (FromPhone actor))
|
|
(if wryly
|
|
(actor.playAnimation wryly)
|
|
(actor.playAnimation "neutral")))
|
|
(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)
|
|
(dialogBox.revive)
|
|
// 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)
|
|
|
|
(set dialogText.size DIALOG_SIZE)
|
|
(while (< FlxG.height (+ dialogText.y dialogText.height))
|
|
(-= dialogText.size 6))
|
|
// 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)
|
|
(speakerNameText.revive)
|
|
(set dialogText.y (+ flxMovie.DIALOG_Y speakerNameText.height))
|
|
}
|
|
(set dialogText.y flxMovie.DIALOG_Y))
|
|
(dialogText.revive)
|
|
(startWaitForInput cc))
|
|
|
|
(method :Void hideDialog []
|
|
(dialogText?.kill)
|
|
(speakerNameText?.kill)
|
|
(dialogBox?.kill)
|
|
(when superText (FlxG.state.remove superText true)))
|
|
|
|
(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 (FlxG.sound.load path)]
|
|
(set s.persist true)
|
|
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)))
|
|
(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")
|
|
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]
|
|
(FlxG.sound.load wavPath))
|
|
|
|
(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]
|
|
(set music.volume (* volumeMod musicVolume))
|
|
(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)
|
|
(unless (Std.isOfType prop FlxText)
|
|
// 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 HFloat 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)))
|
|
|
|
(method :Void showProp [:FlxSprite prop :StagePosition position :Appearance appearance :FlxCamera camera :Continuation cc]
|
|
(set prop.cameras [camera])
|
|
(let [width (min (max prop.width PROP_MIN_WIDTH) PROP_MAX_WIDTH)]
|
|
(whenLet [FirstAppearance appearance]
|
|
(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 (min LAYER_MAX (+ 1 (Std.int layerNum)))) prop)))
|
|
(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 :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))
|
|
(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)
|
|
|
|
// TODO currently the bg will cover whatever the final scene was - making after credits scenes impossible
|
|
(method :Void rollCredits [:Array<CreditsLine> credits cc &opt :Float timeLimit]
|
|
(localVar bg (new FlxSprite))
|
|
(bg.makeGraphic FlxG.width FlxG.height FlxColor.BLACK true)
|
|
(set bg.cameras [flxMovie.uiCamera])
|
|
(FlxG.state.add bg)
|
|
(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)))
|
|
|
|
(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 cc)
|
|
(Reflect.callMethod flxMovie (Reflect.field flxMovie "linearMotion") tweenArgs)))))
|
|
|
|
(prop &mut :FlxSprite inputIcon)
|
|
|
|
(method :Void showInputIcon []
|
|
(set inputIcon (SpriteTools.textPlate "[...]" DIALOG_SIZE SUPER_MARGIN null null applyFormat))
|
|
(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)) |