From 25dc011df2e30934cd3a8f32cd37346de7181531 Mon Sep 17 00:00:00 2001 From: Eric Myllyoja <ericmyllyoja@gmail.com> Date: Tue, 29 Mar 2022 21:56:04 -0400 Subject: [PATCH] Many bug fixes; combo meter now works; main menu graphics split up --- .../introMod/_append/data/introText.txt | 1 - source/funkin/Conductor.hx | 34 +++- source/funkin/InitState.hx | 1 + source/funkin/LatencyState.hx | 2 +- source/funkin/LoadingState.hx | 8 +- source/funkin/MainMenuState.hx | 105 ++++------ source/funkin/MusicBeatState.hx | 38 +++- source/funkin/TitleState.hx | 33 +++- source/funkin/charting/ChartingState.hx | 12 +- source/funkin/modding/IScriptedClass.hx | 2 +- source/funkin/modding/events/ScriptEvent.hx | 23 ++- .../modding/events/ScriptEventDispatcher.hx | 2 +- source/funkin/modding/module/Module.hx | 2 +- source/funkin/play/Countdown.hx | 25 +-- source/funkin/play/PicoFight.hx | 16 +- source/funkin/play/PlayState.hx | 185 +++++++++--------- source/funkin/play/character/CharacterData.hx | 50 ++--- source/funkin/play/stage/Bopper.hx | 2 +- source/funkin/play/stage/Stage.hx | 3 +- source/funkin/ui/AtlasMenuList.hx | 19 +- source/funkin/ui/AtlasText.hx | 19 +- source/funkin/ui/ControlsMenu.hx | 6 +- source/funkin/ui/OptionsState.hx | 2 +- source/funkin/ui/PreferencesMenu.hx | 2 +- source/funkin/ui/TextMenuList.hx | 4 +- source/funkin/util/SortUtil.hx | 2 + 26 files changed, 345 insertions(+), 253 deletions(-) delete mode 100644 example_mods/introMod/_append/data/introText.txt diff --git a/example_mods/introMod/_append/data/introText.txt b/example_mods/introMod/_append/data/introText.txt deleted file mode 100644 index 45e0c08ab..000000000 --- a/example_mods/introMod/_append/data/introText.txt +++ /dev/null @@ -1 +0,0 @@ -swagshit--moneymoney \ No newline at end of file diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 765686740..331dd46f7 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -15,9 +15,31 @@ typedef BPMChangeEvent = class Conductor { + /** + * Beats per minute of the song. + */ public static var bpm:Float = 100; - public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds - public static var stepCrochet:Float = crochet / 4; // steps in milliseconds + + /** + * Duration of a beat in millisecond. + */ + public static var crochet(get, null):Float; + + static function get_crochet():Float + { + return ((60 / bpm) * 1000); + } + + /** + * Duration of a step in milliseconds. + */ + public static var stepCrochet(get, null):Float; + + static function get_stepCrochet():Float + { + return crochet / 4; + } + public static var songPosition:Float; public static var lastSongPos:Float; public static var offset:Float = 0; @@ -52,12 +74,4 @@ class Conductor } trace("new BPM map BUDDY " + bpmChangeMap); } - - public static function changeBPM(newBpm:Float) - { - bpm = newBpm; - - crochet = ((60 / bpm) * 1000); - stepCrochet = crochet / 4; - } } diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 58aae986c..f7ec0ff4b 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -124,6 +124,7 @@ class InitState extends FlxTransitionableState StageDataParser.loadStageCache(); CharacterDataParser.loadCharacterCache(); + ModuleHandler.buildModuleCallbacks(); ModuleHandler.loadModuleCache(); #if song diff --git a/source/funkin/LatencyState.hx b/source/funkin/LatencyState.hx index 4e0ca07af..0239bea53 100644 --- a/source/funkin/LatencyState.hx +++ b/source/funkin/LatencyState.hx @@ -31,7 +31,7 @@ class LatencyState extends FlxState strumLine = new FlxSprite(FlxG.width / 2, 100).makeGraphic(FlxG.width, 5); add(strumLine); - Conductor.changeBPM(120); + Conductor.bpm = 120; super.create(); } diff --git a/source/funkin/LoadingState.hx b/source/funkin/LoadingState.hx index b163b49d0..bbe20ff2d 100644 --- a/source/funkin/LoadingState.hx +++ b/source/funkin/LoadingState.hx @@ -117,9 +117,11 @@ class LoadingState extends MusicBeatState } } - override function beatHit() + override function beatHit():Bool { - super.beatHit(); + // super.beatHit() returns false if a module cancelled the event. + if (!super.beatHit()) + return false; // logo.animation.play('bump'); danceLeft = !danceLeft; @@ -128,6 +130,8 @@ class LoadingState extends MusicBeatState gfDance.animation.play('danceRight'); else gfDance.animation.play('danceLeft'); */ + + return true; } var targetShit:Float = 0; diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx index bc20714e1..39eeb87e7 100644 --- a/source/funkin/MainMenuState.hx +++ b/source/funkin/MainMenuState.hx @@ -20,6 +20,7 @@ import funkin.NGio; import funkin.shaderslmfao.ScreenWipeShader; import funkin.ui.AtlasMenuList; import funkin.ui.MenuList; +import funkin.ui.MenuList.MenuItem; import funkin.ui.OptionsState; import funkin.ui.PreferencesMenu; import funkin.ui.Prompt; @@ -40,7 +41,7 @@ import funkin.ui.NgPrompt; class MainMenuState extends MusicBeatState { - var menuItems:MainMenuList; + var menuItems:MenuTypedList<AtlasMenuItem>; var magenta:FlxSprite; var camFollow:FlxObject; @@ -88,7 +89,7 @@ class MainMenuState extends MusicBeatState add(magenta); // magenta.scrollFactor.set(); - menuItems = new MainMenuList(); + menuItems = new MenuTypedList<AtlasMenuItem>(); add(menuItems); menuItems.onChange.add(onMenuItemChange); menuItems.onAcceptPress.add(function(_) @@ -104,31 +105,37 @@ class MainMenuState extends MusicBeatState }); menuItems.enabled = false; // disable for intro - menuItems.createItem('story mode', function() startExitState(new StoryMenuState())); - menuItems.createItem('freeplay', function() + + createMenuItem('storymode', 'mainmenu/storymode', function() + { + startExitState(new StoryMenuState()); + }); + + createMenuItem('freeplay', 'mainmenu/freeplay', function() { persistentDraw = true; persistentUpdate = false; openSubState(new FreeplayState()); }); - // addMenuItem('options', function () startExitState(new OptionMenu())); #if CAN_OPEN_LINKS var hasPopupBlocker = #if web true #else false #end; if (VideoState.seenVideo) - menuItems.createItem('kickstarter', selectDonate, hasPopupBlocker); + { + createMenuItem('kickstarter', 'mainmenu/kickstarter', selectDonate, hasPopupBlocker); + } else - menuItems.createItem('donate', selectDonate, hasPopupBlocker); + { + createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker); + } #end - menuItems.createItem('options', function() startExitState(new OptionsState())); - // #if newgrounds - // if (NGio.isLoggedIn) - // menuItems.createItem("logout", selectLogout); - // else - // menuItems.createItem("login", selectLogin); - // #end - // center vertically + createMenuItem('options', 'mainmenu/options', function() + { + startExitState(new OptionsState()); + }); + + // Reset position of menu items. var spacing = 160; var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2; for (i in 0...menuItems.length) @@ -146,19 +153,26 @@ class MainMenuState extends MusicBeatState // This has to come AFTER! this.leftWatermarkText.text = Constants.VERSION; - this.rightWatermarkText.text = "blablabla test"; - - // var versionStr = 'v${Application.current.meta.get('version')}'; - // versionStr += ' (secret week 8 build do not leak)'; - // - // var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12); - // versionShit.scrollFactor.set(); - // versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - // add(versionShit); + // this.rightWatermarkText.text = "blablabla test"; // NG.core.calls.event.logEvent('swag').send(); } + function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void + { + var item = new AtlasMenuItem(name, Paths.getSparrowAtlas(atlas), callback); + item.fireInstantly = fireInstantly; + item.ID = menuItems.length; + + item.scrollFactor.set(); + + // Set the offset of the item so the sprite is centered on the origin. + item.centered = true; + item.changeAnim('idle'); + + menuItems.addItem(name, item); + } + override function closeSubState() { magenta.visible = false; @@ -308,46 +322,3 @@ class MainMenuState extends MusicBeatState } } } - -private class MainMenuList extends MenuTypedList<MainMenuItem> -{ - public var atlas:FlxAtlasFrames; - - public function new() - { - atlas = Paths.getSparrowAtlas('main_menu'); - super(Vertical); - } - - public function createItem(x = 0.0, y = 0.0, name:String, callback, fireInstantly = false) - { - var item = new MainMenuItem(x, y, name, atlas, callback); - item.fireInstantly = fireInstantly; - item.ID = length; - - return addItem(name, item); - } - - override function destroy() - { - super.destroy(); - atlas = null; - } -} - -private class MainMenuItem extends AtlasMenuItem -{ - public function new(x = 0.0, y = 0.0, name, atlas, callback) - { - super(x, y, name, atlas, callback); - scrollFactor.set(); - } - - override function changeAnim(anim:String) - { - super.changeAnim(anim); - // position by center - centerOrigin(); - offset.copyFrom(origin); - } -} diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 9ee8d0e36..a11d468ca 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -1,5 +1,7 @@ package funkin; +import flixel.util.FlxSort; +import funkin.util.SortUtil; import funkin.play.stage.StageData.StageDataParser; import funkin.play.character.CharacterData.CharacterDataParser; import flixel.FlxState; @@ -135,16 +137,46 @@ class MusicBeatState extends FlxUIState curStep = lastChange.stepTime + Math.floor((Conductor.songPosition - lastChange.songTime) / Conductor.stepCrochet); } - public function stepHit():Void + public function stepHit():Bool { + var event = new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep); + + dispatchEvent(event); + + if (event.eventCanceled) + { + return false; + } + if (curStep % 4 == 0) beatHit(); + + return true; } - public function beatHit():Void + public function beatHit():Bool { + var event = new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep); + + dispatchEvent(event); + + if (event.eventCanceled) + { + return false; + } + lastBeatHitTime = Conductor.songPosition; - // do literally nothing dumbass + + return true; + } + + /** + * Refreshes the state, by redoing the render order of all sprites. + * It does this based on the `zIndex` of each prop. + */ + public function refresh() + { + sort(SortUtil.byZIndex, FlxSort.ASCENDING); } override function switchTo(nextState:FlxState):Bool diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index 76749b47b..df91a5841 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.ui.AtlasText.BoldText; import funkin.audiovis.SpectogramSprite; import flixel.FlxObject; import flixel.FlxSprite; @@ -160,7 +161,7 @@ class TitleState extends MusicBeatState FlxG.sound.music.fadeIn(4, 0, 0.7); } - Conductor.changeBPM(102); + Conductor.bpm = 102; persistentUpdate = true; var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); @@ -218,6 +219,7 @@ class TitleState extends MusicBeatState blackScreen = bg.clone(); credGroup.add(blackScreen); + credGroup.add(textGroup); // var atlasBullShit:FlxSprite = new FlxSprite(); // atlasBullShit.frames = CoolUtil.fromAnimate(Paths.image('money'), Paths.file('images/money.json')); @@ -495,39 +497,48 @@ class TitleState extends MusicBeatState var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music); add(spec); - Conductor.changeBPM(190); + Conductor.bpm = 190; FlxG.camera.flash(FlxColor.WHITE, 1); FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); } function createCoolText(textArray:Array<String>) { + if (credGroup == null || textGroup == null) + return; + for (i in 0...textArray.length) { - var money:Alphabet = new Alphabet(0, 0, textArray[i], true, false); + var money:BoldText = new BoldText(0, 0, textArray[i]); money.screenCenter(X); money.y += (i * 60) + 200; - credGroup.add(money); + // credGroup.add(money); textGroup.add(money); } } function addMoreText(text:String) { + if (credGroup == null || textGroup == null) + return; + lime.ui.Haptic.vibrate(100, 100); - var coolText:Alphabet = new Alphabet(0, 0, text, true, false); + var coolText:BoldText = new BoldText(0, 0, text); coolText.screenCenter(X); coolText.y += (textGroup.length * 60) + 200; - credGroup.add(coolText); + // credGroup.add(coolText); textGroup.add(coolText); } function deleteCoolText() { + if (credGroup == null || textGroup == null) + return; + while (textGroup.members.length > 0) { - credGroup.remove(textGroup.members[0], true); + // credGroup.remove(textGroup.members[0], true); textGroup.remove(textGroup.members[0], true); } } @@ -535,9 +546,11 @@ class TitleState extends MusicBeatState var isRainbow:Bool = false; var skippedIntro:Bool = false; - override function beatHit() + override function beatHit():Bool { - super.beatHit(); + // super.beatHit() returns false if a module cancelled the event. + if (!super.beatHit()) + return false; if (!skippedIntro) { @@ -597,6 +610,8 @@ class TitleState extends MusicBeatState else gfDance.animation.play('danceLeft'); } + + return true; } function skipIntro():Void diff --git a/source/funkin/charting/ChartingState.hx b/source/funkin/charting/ChartingState.hx index 7c5e8bd27..507d8dec6 100644 --- a/source/funkin/charting/ChartingState.hx +++ b/source/funkin/charting/ChartingState.hx @@ -145,7 +145,7 @@ class ChartingState extends MusicBeatState updateGrid(); loadSong(_song.song); - Conductor.changeBPM(_song.bpm); + Conductor.bpm = _song.bpm; Conductor.mapBPMChanges(_song); bpmTxt = new FlxText(1000, 50, 0, "", 16); @@ -546,7 +546,7 @@ class ChartingState extends MusicBeatState { tempBpm = nums.value; Conductor.mapBPMChanges(_song); - Conductor.changeBPM(nums.value); + Conductor.bpm = nums.value; } else if (wname == 'note_susLength') { @@ -976,9 +976,9 @@ class ChartingState extends MusicBeatState _song.bpm = tempBpm; /* if (FlxG.keys.justPressed.UP) - Conductor.changeBPM(Conductor.bpm + 1); + Conductor.bpm += 1; if (FlxG.keys.justPressed.DOWN) - Conductor.changeBPM(Conductor.bpm - 1); */ + Conductor.bpm -= 1; */ var shiftThing:Int = 1; if (FlxG.keys.pressed.SHIFT) @@ -1214,7 +1214,7 @@ class ChartingState extends MusicBeatState if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0) { - Conductor.changeBPM(SongLoad.getSong()[curSection].bpm); + Conductor.bpm = SongLoad.getSong()[curSection].bpm; FlxG.log.add('CHANGED BPM!'); } else @@ -1224,7 +1224,7 @@ class ChartingState extends MusicBeatState for (i in 0...curSection) if (SongLoad.getSong()[i].changeBPM) daBPM = SongLoad.getSong()[i].bpm; - Conductor.changeBPM(daBPM); + Conductor.bpm = daBPM; } /* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE diff --git a/source/funkin/modding/IScriptedClass.hx b/source/funkin/modding/IScriptedClass.hx index 70ab28983..4261032a2 100644 --- a/source/funkin/modding/IScriptedClass.hx +++ b/source/funkin/modding/IScriptedClass.hx @@ -54,7 +54,7 @@ interface INoteScriptedClass extends IScriptedClass */ interface IPlayStateScriptedClass extends IScriptedClass { - public function onPause(event:ScriptEvent):Void; + public function onPause(event:PauseScriptEvent):Void; public function onResume(event:ScriptEvent):Void; public function onSongLoaded(eent:SongLoadScriptEvent):Void; diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 1c55616fc..ef8fc3a06 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -220,7 +220,7 @@ class ScriptEvent */ /** * If true, the behavior associated with this event can be prevented. - * For example, cancelling COUNTDOWN_BEGIN should prevent the countdown from starting, + * For example, cancelling COUNTDOWN_START should prevent the countdown from starting, * until another script restarts it, or cancelling NOTE_HIT should cause the note to be missed. */ public var cancelable(default, null):Bool; @@ -250,7 +250,7 @@ class ScriptEvent /** * Call this function on a cancelable event to cancel the associated behavior. - * For example, cancelling COUNTDOWN_BEGIN will prevent the countdown from starting. + * For example, cancelling COUNTDOWN_START will prevent the countdown from starting. */ public function cancelEvent():Void { @@ -400,7 +400,7 @@ class SongTimeScriptEvent extends ScriptEvent public function new(type:ScriptEventType, beat:Int, step:Int):Void { - super(type, false); + super(type, true); this.beat = beat; this.step = step; } @@ -535,3 +535,20 @@ class SubStateScriptEvent extends ScriptEvent return 'SubStateScriptEvent(type=' + type + ', targetState=' + targetState + ')'; } } + +/** + * An event which is called when the player attempts to pause the game. + */ +class PauseScriptEvent extends ScriptEvent +{ + /** + * Whether to use the Gitaroo Man pause. + */ + public var gitaroo(default, default):Bool; + + public function new(gitaroo:Bool):Void + { + super(ScriptEvent.PAUSE, true); + this.gitaroo = gitaroo; + } +} diff --git a/source/funkin/modding/events/ScriptEventDispatcher.hx b/source/funkin/modding/events/ScriptEventDispatcher.hx index 45edf5214..bfb62b9d1 100644 --- a/source/funkin/modding/events/ScriptEventDispatcher.hx +++ b/source/funkin/modding/events/ScriptEventDispatcher.hx @@ -68,7 +68,7 @@ class ScriptEventDispatcher t.onGameOver(event); return; case ScriptEvent.PAUSE: - t.onPause(event); + t.onPause(cast event); return; case ScriptEvent.RESUME: t.onResume(event); diff --git a/source/funkin/modding/module/Module.hx b/source/funkin/modding/module/Module.hx index 207fc2a4a..74f04c3bd 100644 --- a/source/funkin/modding/module/Module.hx +++ b/source/funkin/modding/module/Module.hx @@ -79,7 +79,7 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte public function onUpdate(event:UpdateScriptEvent) {} - public function onPause(event:ScriptEvent) {} + public function onPause(event:PauseScriptEvent) {} public function onResume(event:ScriptEvent) {} diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index ec6fd9dce..235db587e 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -28,23 +28,24 @@ class Countdown * Performs the countdown. * Pauses the song, plays the countdown graphics/sound, and then starts the song. * This will automatically stop and restart the countdown if it is already running. + * @returns `false` if the countdown was cancelled by a script. */ - public static function performCountdown(isPixelStyle:Bool):Void + public static function performCountdown(isPixelStyle:Bool):Bool { + countdownStep = BEFORE; + var cancelled:Bool = propagateCountdownEvent(countdownStep); + if (cancelled) + return false; + // Stop any existing countdown. stopCountdown(); PlayState.isInCountdown = true; Conductor.songPosition = Conductor.crochet * -5; - countdownStep = BEFORE; // Handle onBeatHit events manually @:privateAccess PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); - var cancelled:Bool = propagateCountdownEvent(countdownStep); - if (cancelled) - return; - // The timer function gets called based on the beat of the song. countdownTimer = new FlxTimer(); @@ -72,7 +73,9 @@ class Countdown { stopCountdown(); } - }, 6); // Before, 3, 2, 1, GO!, After + }, 5); // Before, 3, 2, 1, GO!, After + + return true; } /** @@ -94,11 +97,9 @@ class Countdown return true; } - // Stage - ScriptEventDispatcher.callEvent(PlayState.instance.currentStage, event); - - // Modules - ModuleHandler.callEvent(event); + // Modules, stages, characters. + @:privateAccess + PlayState.instance.dispatchEvent(event); return event.eventCanceled; } diff --git a/source/funkin/play/PicoFight.hx b/source/funkin/play/PicoFight.hx index 9a4dded9a..016fada3f 100644 --- a/source/funkin/play/PicoFight.hx +++ b/source/funkin/play/PicoFight.hx @@ -37,7 +37,7 @@ class PicoFight extends MusicBeatState FlxG.sound.playMusic(Paths.inst("blazin")); SongLoad.loadFromJson('blazin', "blazin"); - Conductor.changeBPM(SongLoad.songData.bpm); + Conductor.bpm = SongLoad.songData.bpm; for (dumbassSection in SongLoad.songData.noteMap['hard']) { @@ -184,13 +184,17 @@ class PicoFight extends MusicBeatState super.update(elapsed); } - override function stepHit() + override function stepHit():Bool { - super.stepHit(); + return super.stepHit(); } - override function beatHit() + override function beatHit():Bool { + // super.beatHit() returns false if a module cancelled the event. + if (!super.beatHit()) + return false; + funnyWave.thickness = 10; funnyWave.waveAmplitude = 300; funnyWave.realtimeVisLenght = 0.1; @@ -198,7 +202,7 @@ class PicoFight extends MusicBeatState picoHealth += 1; makeNotes(); - // trace(picoHealth); - super.beatHit(); + + return true; } } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 2faa894b2..09cdc0c06 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -140,8 +140,9 @@ class PlayState extends MusicBeatState implements IHook * An empty FlxObject contained in the scene. * The current gameplay camera will be centered on this object. Tween its position to move the camera smoothly. * - * NOTE: This must be an FlxObject, not an FlxPoint, because it needs to be added to the scene. - * Once it's added to the scene, the camera can be configured to follow it. + * This is an FlxSprite for two reasons: + * 1. It needs to be an object in the scene for the camera to be configured to follow it. + * 2. It needs to be an FlxSprite to allow a graphic (optionally, for debug purposes) to be drawn on it. */ public var cameraFollowPoint:FlxSprite = new FlxSprite(0, 0); @@ -286,6 +287,7 @@ class PlayState extends MusicBeatState implements IHook // Displays the camera follow point as a sprite for debug purposes. // TODO: Put this on a toggle? cameraFollowPoint.makeGraphic(8, 8, 0xFF00FF00); + cameraFollowPoint.visible = false; cameraFollowPoint.zIndex = 1000000; // Reduce physics accuracy (who cares!!!) to improve animation quality. @@ -313,7 +315,7 @@ class PlayState extends MusicBeatState implements IHook currentSong = SongLoad.loadFromJson('tutorial'); Conductor.mapBPMChanges(currentSong); - Conductor.changeBPM(currentSong.bpm); + Conductor.bpm = currentSong.bpm; switch (currentSong.song.toLowerCase()) { @@ -555,7 +557,6 @@ class PlayState extends MusicBeatState implements IHook if (dad != null) { dad.characterType = CharacterType.DAD; - cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y); } switch (currentSong.player2) @@ -578,24 +579,6 @@ class PlayState extends MusicBeatState implements IHook boyfriend.characterType = CharacterType.BF; } - // REPOSITIONING PER STAGE - switch (currentStageId) - { - case "tank": - girlfriend.y += 10; - girlfriend.x -= 30; - boyfriend.x += 40; - boyfriend.y += 0; - dad.y += 60; - dad.x -= 80; - - if (gfVersion != 'pico-speaker') - { - girlfriend.x -= 170; - girlfriend.y -= 75; - } - } - if (currentStage != null) { // We're using Eric's stage handler. @@ -604,15 +587,12 @@ class PlayState extends MusicBeatState implements IHook currentStage.addCharacter(boyfriend, BF); currentStage.addCharacter(dad, DAD); + // Camera starts at dad. + cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y); + // Redo z-indexes. currentStage.refresh(); } - else - { - add(girlfriend); - add(dad); - add(boyfriend); - } } /** @@ -802,7 +782,7 @@ class PlayState extends MusicBeatState implements IHook { // FlxG.log.add(ChartParser.parse()); - Conductor.changeBPM(currentSong.bpm); + Conductor.bpm = currentSong.bpm; currentSong.song = currentSong.song; @@ -1024,28 +1004,35 @@ class PlayState extends MusicBeatState implements IHook if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame) { - persistentUpdate = false; - persistentDraw = true; + var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000)); - // There is a 1/1000 change to use a special pause menu. - // This prevents the player from resuming, but that's the point. - // It's a reference to Gitaroo Man, which doesn't let you pause the game. - if (FlxG.random.bool(1 / 1000)) - { - FlxG.switchState(new GitarooPause()); - } - else - { - var boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); - var pauseSubState = new PauseSubState(boyfriendPos.x, boyfriendPos.y); - openSubState(pauseSubState); - pauseSubState.camera = camHUD; - boyfriendPos.put(); - } + dispatchEvent(event); - #if discord_rpc - DiscordClient.changePresence(detailsPausedText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); - #end + if (!event.eventCanceled) + { + persistentUpdate = false; + persistentDraw = true; + + // There is a 1/1000 change to use a special pause menu. + // This prevents the player from resuming, but that's the point. + // It's a reference to Gitaroo Man, which doesn't let you pause the game. + if (event.gitaroo) + { + FlxG.switchState(new GitarooPause()); + } + else + { + var boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); + var pauseSubState = new PauseSubState(boyfriendPos.x, boyfriendPos.y); + openSubState(pauseSubState); + pauseSubState.camera = camHUD; + boyfriendPos.put(); + } + + #if discord_rpc + DiscordClient.changePresence(detailsPausedText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + #end + } } if (FlxG.keys.justPressed.SEVEN) @@ -1076,13 +1063,6 @@ class PlayState extends MusicBeatState implements IHook changeSection(-1); #end - if (generatedMusic && SongLoad.getSong()[Std.int(curStep / 16)] != null) - { - cameraRightSide = SongLoad.getSong()[Std.int(curStep / 16)].mustHitSection; - - controlCamera(); - } - if (camZooming) { FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95); @@ -1091,6 +1071,7 @@ class PlayState extends MusicBeatState implements IHook FlxG.watch.addQuick("beatShit", curBeat); FlxG.watch.addQuick("stepShit", curStep); + FlxG.watch.addQuick("songPos", Conductor.songPosition); if (currentSong.song == 'Fresh') { @@ -1722,10 +1703,14 @@ class PlayState extends MusicBeatState implements IHook } } - override function stepHit() + override function stepHit():Bool { - super.stepHit(); - if (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 20 + // super.stepHit() returns false if a module cancelled the event. + if (!super.stepHit()) + return false; + + if (!isInCutscene + && Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 20 || (currentSong.needsVoices && Math.abs(vocals.time - (Conductor.songPosition - Conductor.offset)) > 20)) { resyncVocals(); @@ -1734,23 +1719,32 @@ class PlayState extends MusicBeatState implements IHook iconP1.onStepHit(curStep); iconP2.onStepHit(curStep); - dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_STEP_HIT, curBeat, curStep)); + return true; } - override function beatHit() + override function beatHit():Bool { - super.beatHit(); + // super.beatHit() returns false if a module cancelled the event. + if (!super.beatHit()) + return false; if (generatedMusic) { activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING); } + // Moving this code into the `beatHit` function allows for scripts and modules to control the camera better. + if (generatedMusic && SongLoad.getSong()[Std.int(curStep / 16)] != null) + { + cameraRightSide = SongLoad.getSong()[Std.int(curStep / 16)].mustHitSection; + controlCamera(); + } + if (SongLoad.getSong()[Math.floor(curStep / 16)] != null) { if (SongLoad.getSong()[Math.floor(curStep / 16)].changeBPM) { - Conductor.changeBPM(SongLoad.getSong()[Math.floor(curStep / 16)].bpm); + Conductor.bpm = SongLoad.getSong()[Math.floor(curStep / 16)].bpm; FlxG.log.add('CHANGED BPM!'); } } @@ -1772,11 +1766,34 @@ class PlayState extends MusicBeatState implements IHook } } + // That combo counter that got spoiled that one time. + // Comes with NEAT visual and audio effects. + + var shouldShowComboText:Bool = (curBeat % 8 == 7) // End of measure. TODO: Is this always the correct time? + && (SongLoad.getSong()[Std.int(curStep / 16)].mustHitSection) // Current section is BF's. + && (combo > 5) // Don't want to show on small combos. + && ((SongLoad.getSong().length < Std.int(curStep / 16)) // Show at the end of the song. + || (!SongLoad.getSong()[Std.int(curStep / 16) + 1].mustHitSection) // Or when the next section is Dad's. + ); + + if (shouldShowComboText) + { + var animShit:ComboCounter = new ComboCounter(-100, 300, combo); + animShit.scrollFactor.set(0.6, 0.6); + add(animShit); + + var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation + + new FlxTimer().start(((Conductor.crochet / 1000) * 1.25) - frameShit, function(tmr) + { + animShit.forceFinish(); + }); + } + // Make the characters dance on the beat danceOnBeat(); - // Call any relevant event handlers. - dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, curBeat, curStep)); + return true; } /** @@ -1876,13 +1893,6 @@ class PlayState extends MusicBeatState implements IHook Countdown.pauseCountdown(); } - var event:ScriptEvent = new ScriptEvent(ScriptEvent.PAUSE, true); - - dispatchEvent(event); - - if (event.eventCanceled) - return; - super.openSubState(subState); } @@ -1894,7 +1904,14 @@ class PlayState extends MusicBeatState implements IHook { if (isGamePaused) { - if (FlxG.sound.music != null && !startingSong) + var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true); + + dispatchEvent(event); + + if (event.eventCanceled) + return; + + if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -1909,13 +1926,6 @@ class PlayState extends MusicBeatState implements IHook #end } - var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true); - - dispatchEvent(event); - - if (event.eventCanceled) - return; - super.closeSubState(); } @@ -1925,13 +1935,15 @@ class PlayState extends MusicBeatState implements IHook */ function startCountdown():Void { + var result = Countdown.performCountdown(currentStageId.startsWith('school')); + if (!result) + return; + isInCutscene = false; camHUD.visible = true; talking = false; buildStrumlines(); - - Countdown.performCountdown(currentStageId.startsWith('school')); } override function dispatchEvent(event:ScriptEvent):Void @@ -2001,15 +2013,6 @@ class PlayState extends MusicBeatState implements IHook instance = null; } - /** - * Refreshes the state, by redoing the render order of all elements. - * It does this based on the `zIndex` of each element. - */ - public function refresh() - { - sort(SortUtil.byZIndex, FlxSort.ASCENDING); - } - /** * This function is called whenever Flixel switches switching to a new FlxState. * @return Whether to actually switch to the new state. diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index f4ce9823e..d38f6cc72 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -104,41 +104,47 @@ class CharacterDataParser } var scriptedCharClassNames3:Array<String> = ScriptedMultiSparrowCharacter.listScriptClasses(); - trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...'); - for (charCls in scriptedCharClassNames3) + if (scriptedCharClassNames3.length > 0) { - var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID); - if (character == null) + trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...'); + for (charCls in scriptedCharClassNames3) { - trace(' Failed to instantiate scripted character: ${charCls}'); - continue; + var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID); + if (character == null) + { + trace(' Failed to instantiate scripted character: ${charCls}'); + continue; + } + characterScriptedClass.set(character.characterId, charCls); } - characterScriptedClass.set(character.characterId, charCls); } // NOTE: Only instantiate the ones not populated above. // ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes. var scriptedCharClassNames:Array<String> = ScriptedBaseCharacter.listScriptClasses(); - scriptedCharClassNames.filter(function(charCls:String):Bool + scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool { - return !scriptedCharClassNames1.contains(charCls) - && !scriptedCharClassNames2.contains(charCls) - && !scriptedCharClassNames3.contains(charCls); + return !(scriptedCharClassNames1.contains(charCls) + || scriptedCharClassNames2.contains(charCls) + || scriptedCharClassNames3.contains(charCls)); }); - trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...'); - for (charCls in scriptedCharClassNames) + if (scriptedCharClassNames.length > 0) { - var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID); - if (character == null) + trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...'); + for (charCls in scriptedCharClassNames) { - trace(' Failed to instantiate scripted character: ${charCls}'); - continue; - } - else - { - trace(' Successfully instantiated scripted character: ${charCls}'); - characterScriptedClass.set(character.characterId, charCls); + var character = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID); + if (character == null) + { + trace(' Failed to instantiate scripted character: ${charCls}'); + continue; + } + else + { + trace(' Successfully instantiated scripted character: ${charCls}'); + characterScriptedClass.set(character.characterId, charCls); + } } } diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 9462c3ada..0b026ed22 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -236,7 +236,7 @@ class Bopper extends FlxSprite implements IPlayStateScriptedClass public function onUpdate(event:UpdateScriptEvent) {} - public function onPause(event:ScriptEvent) {} + public function onPause(event:PauseScriptEvent) {} public function onResume(event:ScriptEvent) {} diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 3cafd79a2..2b8c46acb 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -247,6 +247,7 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte // TODO: Make this a toggle? It's useful to turn on from time to time. var debugIcon:FlxSprite = new FlxSprite(0, 0); debugIcon.makeGraphic(8, 8, 0xffff00ff); + debugIcon.visible = false; debugIcon.zIndex = 1000000; #end @@ -480,7 +481,7 @@ class Stage extends FlxSpriteGroup implements IHook implements IPlayStateScripte public function onScriptEvent(event:ScriptEvent) {} - public function onPause(event:ScriptEvent) {} + public function onPause(event:PauseScriptEvent) {} public function onResume(event:ScriptEvent) {} diff --git a/source/funkin/ui/AtlasMenuList.hx b/source/funkin/ui/AtlasMenuList.hx index 7a217a44c..922ef9369 100644 --- a/source/funkin/ui/AtlasMenuList.hx +++ b/source/funkin/ui/AtlasMenuList.hx @@ -5,6 +5,9 @@ import flixel.graphics.frames.FlxAtlasFrames; typedef AtlasAsset = flixel.util.typeLimit.OneOfTwo<String, FlxAtlasFrames>; +/** + * A menulist whose items share a single texture atlas. + */ class AtlasMenuList extends MenuTypedList<AtlasMenuItem> { public var atlas:FlxAtlasFrames; @@ -33,11 +36,16 @@ class AtlasMenuList extends MenuTypedList<AtlasMenuItem> } } +/** + * A menu list item which uses single texture atlas. + */ class AtlasMenuItem extends MenuItem { var atlas:FlxAtlasFrames; - public function new(x = 0.0, y = 0.0, name:String, atlas:FlxAtlasFrames, callback) + public var centered:Bool = false; + + public function new(x = 0.0, y = 0.0, name:String, atlas, callback) { this.atlas = atlas; super(x, y, name, callback); @@ -52,10 +60,17 @@ class AtlasMenuItem extends MenuItem super.setData(name, callback); } - function changeAnim(animName:String) + public function changeAnim(animName:String) { animation.play(animName); updateHitbox(); + + if (centered) + { + // position by center + centerOrigin(); + offset.copyFrom(origin); + } } override function idle() diff --git a/source/funkin/ui/AtlasText.hx b/source/funkin/ui/AtlasText.hx index 8eb0d041d..3006f3994 100644 --- a/source/funkin/ui/AtlasText.hx +++ b/source/funkin/ui/AtlasText.hx @@ -10,7 +10,7 @@ abstract BoldText(AtlasText) from AtlasText to AtlasText { inline public function new(x = 0.0, y = 0.0, text:String) { - this = new AtlasText(x, y, text, Bold); + this = new AtlasText(x, y, text, AtlasFont.BOLD); } } @@ -42,7 +42,7 @@ class AtlasText extends FlxTypedSpriteGroup<AtlasChar> inline function get_maxHeight() return font.maxHeight; - public function new(x = 0.0, y = 0.0, text:String, fontName:AtlasFont = Default) + public function new(x = 0.0, y = 0.0, text:String, fontName:AtlasFont = AtlasFont.DEFAULT) { if (!fonts.exists(fontName)) fonts[fontName] = new AtlasFontData(fontName); @@ -247,7 +247,14 @@ private class AtlasFontData public function new(name:AtlasFont) { - atlas = Paths.getSparrowAtlas("fonts/" + name.getName().toLowerCase()); + var fontName:String = name; + atlas = Paths.getSparrowAtlas('fonts/${fontName.toLowerCase()}'); + if (atlas == null) + { + FlxG.log.warn('Could not find font atlas for font "${fontName}".'); + return; + } + atlas.parent.destroyOnNoUse = false; atlas.parent.persist = true; @@ -277,8 +284,8 @@ enum Case Lower; } -enum AtlasFont +enum abstract AtlasFont(String) from String to String { - Default; - Bold; + var DEFAULT = "default"; + var BOLD = "bold"; } diff --git a/source/funkin/ui/ControlsMenu.hx b/source/funkin/ui/ControlsMenu.hx index fb4144b5b..62445be96 100644 --- a/source/funkin/ui/ControlsMenu.hx +++ b/source/funkin/ui/ControlsMenu.hx @@ -66,11 +66,11 @@ class ControlsMenu extends funkin.ui.OptionsState.Page var item; - item = deviceList.createItem("Keyboard", Bold, selectDevice.bind(Keys)); + item = deviceList.createItem("Keyboard", AtlasFont.BOLD, selectDevice.bind(Keys)); item.x = FlxG.width / 2 - item.width - 30; item.y = (devicesBg.height - item.height) / 2; - item = deviceList.createItem("Gamepad", Bold, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id))); + item = deviceList.createItem("Gamepad", AtlasFont.BOLD, selectDevice.bind(Gamepad(FlxG.gamepads.firstActive.id))); item.x = FlxG.width / 2 + 30; item.y = (devicesBg.height - item.height) / 2; } @@ -317,7 +317,7 @@ class InputItem extends TextMenuItem this.index = index; this.input = getInput(); - super(x, y, getLabel(input), Default, callback); + super(x, y, getLabel(input), DEFAULT, callback); } public function updateDevice(device:Device) diff --git a/source/funkin/ui/OptionsState.hx b/source/funkin/ui/OptionsState.hx index fc5c928c2..32b511d3e 100644 --- a/source/funkin/ui/OptionsState.hx +++ b/source/funkin/ui/OptionsState.hx @@ -194,7 +194,7 @@ class OptionsMenu extends Page function createItem(name:String, callback:Void->Void, fireInstantly = false) { - var item = items.createItem(0, 100 + items.length * 100, name, Bold, callback); + var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback); item.fireInstantly = fireInstantly; item.screenCenter(X); return item; diff --git a/source/funkin/ui/PreferencesMenu.hx b/source/funkin/ui/PreferencesMenu.hx index d6faf7b27..a5aa4e368 100644 --- a/source/funkin/ui/PreferencesMenu.hx +++ b/source/funkin/ui/PreferencesMenu.hx @@ -84,7 +84,7 @@ class PreferencesMenu extends Page private function createPrefItem(prefName:String, prefString:String, prefValue:Dynamic):Void { - items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.Bold, function() + items.createItem(120, (120 * items.length) + 30, prefName, AtlasFont.BOLD, function() { preferenceCheck(prefString, prefValue); diff --git a/source/funkin/ui/TextMenuList.hx b/source/funkin/ui/TextMenuList.hx index ae3ed7bff..fe1a13a7c 100644 --- a/source/funkin/ui/TextMenuList.hx +++ b/source/funkin/ui/TextMenuList.hx @@ -10,7 +10,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem> super(navControls, wrapMode); } - public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback, fireInstantly = false) + public function createItem(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback, fireInstantly = false) { var item = new TextMenuItem(x, y, name, font, callback); item.fireInstantly = fireInstantly; @@ -20,7 +20,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem> class TextMenuItem extends TextTypedMenuItem<AtlasText> { - public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = Bold, callback) + public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, callback) { super(x, y, new AtlasText(0, 0, name, font), name, callback); setEmptyBackground(); diff --git a/source/funkin/util/SortUtil.hx b/source/funkin/util/SortUtil.hx index b0bd798dc..6e92d6b68 100644 --- a/source/funkin/util/SortUtil.hx +++ b/source/funkin/util/SortUtil.hx @@ -13,6 +13,8 @@ class SortUtil */ public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int { + if (Obj1 == null || Obj2 == null) + return 0; return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex); }