From 9f8d1dbac2e15a91482d106694ad2f687a6c4655 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 2 Jun 2023 15:35:01 -0400 Subject: [PATCH] Playstate fixes WIP (TODO: fix compile errors) --- source/funkin/FreeplayState.hx | 66 +- source/funkin/play/PlayState.hx | 1628 +++++++++++++------------------ 2 files changed, 697 insertions(+), 997 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 975c679b3..2b869a21e 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -34,6 +34,8 @@ import funkin.play.song.SongData.SongDataParser; import funkin.shaderslmfao.AngleMask; import funkin.shaderslmfao.PureColor; import funkin.shaderslmfao.StrokeShader; +import funkin.play.PlayStatePlaylist; +import funkin.play.song.Song; import lime.app.Future; import lime.utils.Assets; @@ -128,19 +130,20 @@ class FreeplayState extends MusicBeatSubState if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu')); } - addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']); + if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']); - addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']); + if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']); - addWeek(['Pico', 'Philly', 'Blammed'], 3, ['pico']); + if (StoryMenuState.weekUnlocked[3] || isDebug) addWeek(['Pico', 'Philly-Nice', 'Blammed'], 3, ['pico']); - addWeek(['Satin-Panties', 'High', 'Milf'], 4, ['mom']); + if (StoryMenuState.weekUnlocked[4] || isDebug) addWeek(['Satin-Panties', 'High', 'MILF'], 4, ['mom']); - addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 5, ['parents-christmas', 'parents-christmas', 'monster-christmas']); + if (StoryMenuState.weekUnlocked[5] || isDebug) addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 5, + ['parents-christmas', 'parents-christmas', 'monster-christmas']); - addWeek(['Senpai', 'Roses', 'Thorns'], 6, ['senpai', 'senpai', 'spirit']); + if (StoryMenuState.weekUnlocked[6] || isDebug) addWeek(['Senpai', 'Roses', 'Thorns'], 6, ['senpai', 'senpai', 'spirit']); - addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']); + if (StoryMenuState.weekUnlocked[7] || isDebug) addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']); addWeek(["Darnell", "lit-up", "2hot", "blazin"], 8, ['darnell']); @@ -850,11 +853,9 @@ class FreeplayState extends MusicBeatSubState curDifficulty = 1; }*/ - PlayState.currentSong = SongLoad.loadFromJson(poop, songs[curSelected].songName.toLowerCase()); - PlayState.currentSong_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase()); - PlayState.isStoryMode = false; - PlayState.storyDifficulty = curDifficulty; - PlayState.storyDifficulty_NEW = switch (curDifficulty) + PlayStatePlaylist.isStoryMode = false; + var targetSong:Song = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase()); + var targetDifficulty:String = switch (curDifficulty) { case 0: 'easy'; @@ -864,27 +865,41 @@ class FreeplayState extends MusicBeatSubState 'hard'; default: 'normal'; }; - // SongLoad.curDiff = Highscore.formatSong() - SongLoad.curDiff = PlayState.storyDifficulty_NEW; + // TODO: Implement additional difficulties into the interface properly. + if (FlxG.keys.pressed.E) + { + targetDifficulty = 'erect'; + } - PlayState.storyWeek = songs[curSelected].week; - // trace(' CUR WEEK ' + PlayState.storyWeek); + // TODO: Implement Pico into the interface properly. + var targetCharacter:String = 'bf'; + if (FlxG.keys.pressed.P) + { + targetCharacter = 'pico'; + } + + // PlayState.storyWeek = songs[curSelected].week; // Visual and audio effects. FlxG.sound.play(Paths.sound('confirmMenu')); dj.confirm(); new FlxTimer().start(1, function(tmr:FlxTimer) { - LoadingState.loadAndSwitchState(new PlayState(), true); + LoadingState.loadAndSwitchState(new PlayState( + { + targetSong: targetSong, + targetDifficulty: targetDifficulty, + targetCharacter: targetCharacter, + }), true); }); } } - override function startOutro(onComplete:() -> Void):Void + override function switchTo(nextState:FlxState):Bool { clearDaCache(songs[curSelected].songName); - super.startOutro(onComplete); + return super.switchTo(nextState); } function changeDiff(change:Int = 0) @@ -900,19 +915,6 @@ class FreeplayState extends MusicBeatSubState intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty); - PlayState.storyDifficulty = curDifficulty; - PlayState.storyDifficulty_NEW = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: - 'normal'; - }; - grpDifficulties.group.forEach(function(spr) { spr.visible = false; }); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 2fe173902..adcc509c5 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,17 +1,16 @@ package funkin.play; -import funkin.play.song.SongData.SongDataParser; -import flixel.sound.FlxSound; +import flixel.addons.display.FlxPieDial; import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxObject; -import funkin.ui.story.StoryMenuState; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; +import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.text.FlxText; import flixel.tweens.FlxEase; @@ -20,18 +19,19 @@ import flixel.ui.FlxBar; import flixel.util.FlxColor; import flixel.util.FlxSort; import flixel.util.FlxTimer; -import funkin.charting.ChartingState; +import funkin.audio.VoicesGroup; import funkin.Highscore.Tallies; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.Note; import funkin.play.character.BaseCharacter; -import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.cutscene.VanillaCutscenes; -import funkin.play.event.SongEvent.SongEventParser; +import funkin.play.cutscene.VideoCutscene; +import funkin.play.event.SongEventData.SongEventParser; import funkin.play.scoring.Scoring; import funkin.play.song.Song; +import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongEventData; import funkin.play.song.SongData.SongNoteData; import funkin.play.song.SongData.SongPlayableChar; @@ -40,9 +40,6 @@ import funkin.play.stage.Stage; import funkin.play.stage.StageData.StageDataParser; import funkin.play.Strumline.StrumlineArrow; import funkin.play.Strumline.StrumlineStyle; -import funkin.Section.SwagSection; -import funkin.SongLoad.SwagSong; -import funkin.audio.VoicesGroup; import funkin.ui.PopUpStuff; import funkin.ui.PreferencesMenu; import funkin.ui.stageBuildShit.StageOffsetSubState; @@ -54,6 +51,28 @@ import lime.ui.Haptic; import Discord.DiscordClient; #end +/** + * Parameters used to initialize the PlayState. + */ +typedef PlayStateParams = +{ + /** + * The song to play. + */ + targetSong:Song, + + /** + * The difficulty to play the song on. + * @default `Constants.DEFAULT_DIFFICULTY` + */ + ?targetDifficulty:String, + /** + * The character to play as. + * @default `bf`, or the first character in the song's character list. + */ + ?targetCharacter:String, +} + /** * The gameplay state, where all the rhythm gaming happens. */ @@ -66,89 +85,58 @@ class PlayState extends MusicBeatState */ /** * The currently active PlayState. - * Since there is only one PlayState in existance at a time, we can use a singleton. + * There should be only one PlayState in existance at a time, we can use a singleton. */ public static var instance:PlayState = null; - /** - * The currently active song. Includes data about what stage should be used, what characters, - * and the notes to be played. - */ - public static var currentSong:SwagSong = null; - - public static var currentSong_NEW:Song = null; - - /** - * Whether the game is currently in Story Mode. If false, we are in Free Play Mode. - */ - public static var isStoryMode:Bool = false; - - /** - * Whether the game is currently in Practice Mode. - * If true, player will not lose gain or lose score from notes. - */ - public static var isPracticeMode:Bool = false; - - /** - * Whether the game is currently in an animated cutscene, and gameplay should be stopped. - */ - public static var isInCutscene:Bool = false; - - /** - * Whether the inputs should be disabled for whatever reason... used for the stage edit lol! - */ - public static var disableKeys:Bool = false; - - /* - * Whether the game is currently in dialog, and gameplay should be stopped. - */ - public static var isInDialog:Bool = false; - - /** - * Whether the game is currently in the countdown before the song resumes. - */ - public static var isInCountdown:Bool = false; - - /** - * Gets set to true when the PlayState needs to reset (player opted to restart or died). - * Gets disabled once resetting happens. - */ - public static var needsReset:Bool = false; - - /** - * The current "Blueball Counter" to display in the pause menu. - * Resets when you beat a song or go back to the main menu. - */ - public static var deathCounter:Int = 0; - - /** - * The default camera zoom level. The camera lerps back to this after zooming in. - * Defaults to 1.05 but may be larger or smaller depending on the current stage. - */ - public static var defaultCameraZoom:Float = 1.05; - - /** - * Used to persist the position of the `cameraFollowPosition` between resets. - */ - static var previousCameraFollowPoint:FlxObject = null; - /** * PUBLIC INSTANCE VARIABLES * Public instance variables should be used for information that must be reset or dereferenced - * every time the state is reset, such as the currently active stage, but may need to be accessed externally. + * every time the state is changed, but may need to be accessed externally. */ + /** + * The currently selected stage. + */ + public var currentSong:Song = null; + + /** + * The currently selected difficulty. + */ + public var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY; + + /** + * The player character being used for this level, as a character ID. + */ + public var currentPlayerId:String = 'bf'; + /** * The currently active Stage. This is the object containing all the props. */ public var currentStage:Stage = null; + /** + * Data for the current difficulty for the current song. + * Includes chart data, scroll speed, and other information. + */ public var currentChart(get, null):SongDifficulty; /** * The internal ID of the currently active Stage. * Used to retrieve the data required to build the `currentStage`. */ - public var currentStageId:String = ''; + public var currentStageId(get, null):String; + + /** + * Gets set to true when the PlayState needs to reset (player opted to restart or died). + * Gets disabled once resetting happens. + */ + public var needsReset:Bool = false; + + /** + * The current 'Blueball Counter' to display in the pause menu. + * Resets when you beat a song or go back to the main menu. + */ + public var deathCounter:Int = 0; /** * The player's current health. @@ -171,6 +159,67 @@ class PlayState extends MusicBeatState */ public var cameraFollowPoint:FlxSprite = new FlxSprite(0, 0); + /** + * The camera follow point from the last stage. + * Used to persist the position of the `cameraFollowPosition` between levels. + */ + public var previousCameraFollowPoint:FlxSprite = null; + + /** + * The current camera zoom level. + * + * The camera zoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. + * Defaults to 1.05 but may be larger or smaller depending on the current stage, + * and may be changed by the `ZoomCamera` song event. + */ + public var defaultCameraZoom:Float = FlxCamera.defaultZoom * 1.05; + + /** + * The current HUD camera zoom level. + * + * The camera zoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. + */ + public var defaultHUDCameraZoom:Float = FlxCamera.defaultZoom * 1.0; + + /** + * Intensity of the gameplay camera zoom. + * @default `1.5%` + */ + public var cameraZoomIntensity:Float = Constants.DEFAULT_ZOOM_INTENSITY; + + /** + * Intensity of the HUD camera zoom. + * @default `3.0%` + */ + public var hudCameraZoomIntensity:Float = Constants.DEFAULT_ZOOM_INTENSITY * 2.0; + + /** + * How many beats (quarter notes) between camera zooms. + * @default One camera zoom per measure (four beats). + */ + public var cameraZoomRate:Int = Constants.DEFAULT_ZOOM_RATE; + + /** + * Whether the game is currently in the countdown before the song resumes. + */ + public var isInCountdown:Bool = false; + + /** + * Whether the game is currently in Practice Mode. + * If true, player will not lose gain or lose score from notes. + */ + public var isPracticeMode:Bool = false; + + /** + * Whether the game is currently in an animated cutscene, and gameplay should be stopped. + */ + public var isInCutscene:Bool = false; + + /** + * Whether the inputs should be disabled for whatever reason... used for the stage edit lol! + */ + public var disableKeys:Bool = false; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -182,6 +231,10 @@ class PlayState extends MusicBeatState */ var inactiveNotes:Array<Note>; + /** + * The Array containing the upcoming song events. + * The `update()` function regularly shifts these out to trigger events. + */ var songEvents:Array<SongEventData>; /** @@ -196,17 +249,26 @@ class PlayState extends MusicBeatState */ var healthLerp:Float = 1; + /** + * How long the user has held the "Skip Video Cutscene" button for. + */ + var skipHeldTimer:Float = 0; + /** * Forcibly disables all update logic while the game moves back to the Menu state. - * This is used only when a critical error occurs and the game cannot continue. + * This is used only when a critical error occurs and the game absolutely cannot continue. */ var criticalFailure:Bool = false; /** - * How many beats between camera zooms. - * @default One camera zoom per four beats. + * False as long as the countdown has not finished yet. */ - var camZoomRate:Int = 4; + var startingSong:Bool = false; + + /** + * A group of audio tracks, used to play the song's vocals. + */ + var vocals:VoicesGroup; /** * RENDER OBJECTS @@ -268,6 +330,8 @@ class PlayState extends MusicBeatState */ public var camCutscene:FlxCamera; + var skipTimer:FlxPieDial; + /** * PROPERTIES */ @@ -287,24 +351,9 @@ class PlayState extends MusicBeatState return this.subState != null; } - // TODO: Reorganize these variables (maybe there should be a separate class like Conductor just to hold them?) - public static var storyWeek:Int = 0; - public static var storyPlaylist:Array<String> = []; - public static var storyDifficulty:Int = 1; - public static var storyDifficulty_NEW:String = "normal"; - public static var seenCutscene:Bool = false; - public static var campaignScore:Int = 0; - - var vocals:VoicesGroup; - var vocalsFinished:Bool = false; - var gfSpeed:Int = 1; var generatedMusic:Bool = false; - var startingSong:Bool = false; - var dialogue:Array<String>; - var talking:Bool = true; - var doof:DialogueBox; var grpNoteSplashes:FlxTypedGroup<NoteSplash>; var comboPopUps:PopUpStuff; var perfectMode:Bool = false; @@ -320,28 +369,76 @@ class PlayState extends MusicBeatState var detailsPausedText:String = ''; #end - override public function create():Void + /** + * This sucks. We need this because FlxG.resetState(); assumes the constructor has no arguments. + * @see https://github.com/HaxeFlixel/flixel/issues/2541 + */ + static var lastParams:PlayStateParams = null; + + public function new(params:PlayStateParams) + { + super(); + + if (params == null && lastParams == null) + { + throw 'PlayState constructor called with no available parameters.'; + } + else if (params == null) + { + trace('WARNING: PlayState constructor called with no parameters. Reusing previous parameters.'); + params = lastParams; + } + else + { + lastParams = params; + } + + currentSong = params.targetSong; + if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; + if (params.targetCharacter != null) currentPlayerId = params.targetCharacter; + } + + public override function create():Void { super.create(); - if (currentSong == null && currentSong_NEW == null) + if (instance != null) + { + trace('WARNING: PlayState instance already exists. This should not happen.'); + } + instance = this; + + if (currentSong != null) + { + // TODO: Do this in the loading state. + currentSong.cacheCharts(true); + } + + // Returns null if the song failed to load or doesn't have the selected difficulty. + if (currentChart == null) { criticalFailure = true; - lime.app.Application.current.window.alert("There was a critical error while accessing the selected song. Click OK to return to the main menu.", - "Error loading PlayState"); + var message:String = 'There was a critical error. Click OK to return to the main menu.'; + + if (currentSong == null) + { + message = 'The was a critical error loading this song\'s chart. Click OK to return to the main menu.'; + } + else if (currentDifficulty == null) + { + message = 'The was a critical error selecting a difficulty for this song. Click OK to return to the main menu.'; + } + else if (currentSong.getDifficulty(currentDifficulty) == null) + { + message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty. Click OK to return to the main menu.'; + } + + lime.app.Application.current.window.alert(message, 'Error loading PlayState'); FlxG.switchState(new MainMenuState()); return; } - instance = this; - - if (currentSong_NEW != null) - { - // TODO: Do this in the loading state. - currentSong_NEW.cacheCharts(true); - } - // Displays the camera follow point as a sprite for debug purposes. // TODO: Put this on a toggle? cameraFollowPoint.makeGraphic(8, 8, 0xFF00FF00); @@ -363,55 +460,16 @@ class PlayState extends MusicBeatState if (currentChart != null) { currentChart.cacheInst(); - currentChart.cacheVocals('bf'); - } - else - { - FlxG.sound.cache(Paths.inst(currentSong.song)); - FlxG.sound.cache(Paths.voices(currentSong.song)); + currentChart.cacheVocals(currentPlayerId); } // Initialize stage stuff. initCameras(); - if (currentSong == null && currentSong_NEW == null) - { - currentSong = SongLoad.loadFromJson('tutorial'); - } - - if (currentSong_NEW != null) - { - Conductor.mapTimeChanges(currentChart.timeChanges); - // Conductor.bpm = currentChart.getStartingBPM(); - - // TODO: Support for dialog. - } - else - { - Conductor.mapBPMChanges(currentSong); - // Conductor.bpm = currentSong.bpm; - - switch (currentSong.song.toLowerCase()) - { - case 'senpai': - dialogue = CoolUtil.coolTextFile(Paths.txt('songs/senpai/senpaiDialogue')); - case 'roses': - dialogue = CoolUtil.coolTextFile(Paths.txt('songs/roses/rosesDialogue')); - case 'thorns': - dialogue = CoolUtil.coolTextFile(Paths.txt('songs/thorns/thornsDialogue')); - } - } + Conductor.mapTimeChanges(currentChart.timeChanges); Conductor.update(-5000); - if (dialogue != null) - { - doof = new DialogueBox(false, dialogue); - doof.scrollFactor.set(); - doof.finishThing = startCountdown; - doof.cameras = [camHUD]; - } - // Once the song is loaded, we can continue and initialize the stage. var healthBarYPos:Float = PreferencesMenu.getPref('downscroll') ? FlxG.height * 0.1 : FlxG.height * 0.9; @@ -444,6 +502,8 @@ class PlayState extends MusicBeatState comboPopUps.cameras = [camHUD]; add(comboPopUps); + buildStrumlines(); + grpNoteSplashes = new FlxTypedGroup<NoteSplash>(); var noteSplash:NoteSplash = new NoteSplash(100, 100, 0); @@ -452,24 +512,25 @@ class PlayState extends MusicBeatState add(grpNoteSplashes); - if (currentSong_NEW != null) - { - generateSong_NEW(); - } - else - { - generateSong(); - } + generateSong(); resetCamera(); FlxG.worldBounds.set(0, 0, FlxG.width, FlxG.height); - scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, "", 20); - scoreText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); + scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, '', 20); + scoreText.setFormat(Paths.font('vcr.ttf'), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.scrollFactor.set(); add(scoreText); + // Skip Video Cutscene + skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24); + skipTimer.amount = 0; + skipTimer.zIndex = 1000; + // Renders only in video cutscene mode. + skipTimer.cameras = [camCutscene]; + add(skipTimer); + // Attach the groups to the HUD camera so they are rendered independent of the stage. grpNoteSplashes.cameras = [camHUD]; activeNotes.cameras = [camHUD]; @@ -488,22 +549,21 @@ class PlayState extends MusicBeatState // TODO: Alternatively: make a song script that allows startCountdown to be called, // then cancels the countdown, hides the UI, plays the cutscene, // then calls PlayState.startCountdown later? - if (isStoryMode && !seenCutscene) + if (currentSong != null) { - seenCutscene = true; - - switch (currentSong_NEW.songId.toLowerCase()) + switch (currentSong.songId.toLowerCase()) { - case "winter-horrorland": + case 'winter-horrorland': VanillaCutscenes.playHorrorStartCutscene(); - case 'senpai' | 'roses' | 'thorns': - schoolIntro(doof); // doof is assumed to be non-null, lol! - case 'ugh': - VanillaCutscenes.playUghCutscene(); - case 'stress': - VanillaCutscenes.playStressCutscene(); - case 'guns': - VanillaCutscenes.playGunsCutscene(); + // This one is softcoded now WOOOO! + // case 'senpai' | 'roses' | 'thorns': + // schoolIntro(doof); + // case 'ugh': + // VanillaCutscenes.playUghCutscene(); + // case 'stress': + // VanillaCutscenes.playStressCutscene(); + // case 'guns': + // VanillaCutscenes.playGunsCutscene(); default: // VanillaCutscenes will call startCountdown later. startCountdown(); @@ -525,8 +585,14 @@ class PlayState extends MusicBeatState function get_currentChart():SongDifficulty { - if (currentSong_NEW == null || storyDifficulty_NEW == null) return null; - return currentSong_NEW.getDifficulty(storyDifficulty_NEW); + if (currentSong == null || currentDifficulty == null) return null; + return currentSong.getDifficulty(currentDifficulty); + } + + function get_currentStageId():String + { + if (currentChart == null || currentChart.stage == null || currentChart.stage == '') return Constants.DEFAULT_STAGE; + return currentChart.stage; } /** @@ -534,8 +600,8 @@ class PlayState extends MusicBeatState */ function initCameras():Void { - // Configure the default camera zoom level. - defaultCameraZoom = FlxCamera.defaultZoom * 1.05; + // Set the camera zoom. This gets overridden by the value in the stage data. + // defaultCameraZoom = FlxCamera.defaultZoom * 1.05; camGame = new SwagCamera(); camHUD = new FlxCamera(); @@ -550,187 +616,25 @@ class PlayState extends MusicBeatState function initStage():Void { - if (currentSong_NEW != null) + if (currentSong != null) { - initStage_NEW(); - return; - } + if (currentChart == null) + { + trace('Song difficulty could not be loaded.'); + } - // TODO: Move stageId to the song file. - switch (currentSong.song.toLowerCase()) - { - case 'spookeez' | 'monster' | 'south': - currentStageId = 'spookyMansion'; - case 'pico' | 'blammed' | 'philly': - currentStageId = 'phillyTrain'; - case 'milf' | 'satin-panties' | 'high': - currentStageId = 'limoRide'; - case 'cocoa' | 'eggnog': - currentStageId = 'mallXmas'; - case 'winter-horrorland': - currentStageId = 'mallEvil'; - case 'senpai' | 'roses': - currentStageId = 'school'; - case 'darnell' | 'lit-up' | '2hot': - currentStageId = 'phillyStreets'; - case 'blazin': - currentStageId = 'phillyBlazin'; - case 'pyro': - currentStageId = 'pyro'; - case 'thorns': - currentStageId = 'schoolEvil'; - case 'guns' | 'stress' | 'ugh': - currentStageId = 'tankmanBattlefield'; - default: - currentStageId = 'mainStage'; - } - // Loads the relevant stage based on its ID. - loadStage(currentStageId); - } - - function initStage_NEW():Void - { - if (currentChart == null) - { - trace('Song difficulty could not be loaded.'); - } - - if (currentChart.stage != null && currentChart.stage != '') - { - currentStageId = currentChart.stage; + loadStage(currentStageId); } else { - currentStageId = SongValidator.DEFAULT_STAGE; - } - - loadStage(currentStageId); - } - - function initCharacters() - { - if (currentSong_NEW != null) - { - initCharacters_NEW(); - return; - } - - iconP1 = new HealthIcon(currentSong.player1, 0); - iconP1.y = healthBar.y - (iconP1.height / 2); - add(iconP1); - - iconP2 = new HealthIcon(currentSong.player2, 1); - iconP2.y = healthBar.y - (iconP2.height / 2); - add(iconP2); - - // - // GIRLFRIEND - // - - // TODO: Tie the GF version to the song data, not the stage ID or the current player. - var gfVersion:String = 'gf'; - - switch (currentStageId) - { - case 'pyro' | 'phillyStreets': - gfVersion = 'nene'; - case 'blazin': - gfVersion = ''; - case 'limoRide': - gfVersion = 'gf-car'; - case 'mallXmas' | 'mallEvil': - gfVersion = 'gf-christmas'; - case 'school' | 'schoolEvil': - gfVersion = 'gf-pixel'; - case 'tankmanBattlefield': - gfVersion = 'gf-tankmen'; - } - - if (currentSong.player1 == "pico") gfVersion = "nene"; - - if (currentSong.song.toLowerCase() == 'stress') gfVersion = 'pico-speaker'; - - if (currentSong.song.toLowerCase() == 'tutorial') gfVersion = ''; - - // - // GIRLFRIEND - // - var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(gfVersion); - - if (girlfriend != null) - { - girlfriend.characterType = CharacterType.GF; - girlfriend.scrollFactor.set(0.95, 0.95); - if (gfVersion == 'pico-speaker') - { - girlfriend.x -= 50; - girlfriend.y -= 200; - } - } - else if (gfVersion != '') - { - trace('WARNING: Could not load girlfriend character with ID ${gfVersion}, skipping...'); - } - - // - // DAD - // - var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentSong.player2); - - if (dad != null) - { - dad.characterType = CharacterType.DAD; - } - - switch (currentSong.player2) - { - case 'gf': - if (isStoryMode) - { - cameraFollowPoint.x += 600; - tweenCamIn(); - } - } - - // - // BOYFRIEND - // - var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentSong.player1); - - if (boyfriend != null) - { - boyfriend.characterType = CharacterType.BF; - } - - if (currentStage != null) - { - // We're using Eric's stage handler. - // Characters get added to the stage, not the main scene. - if (girlfriend != null) - { - currentStage.addCharacter(girlfriend, GF); - } - - if (boyfriend != null) - { - currentStage.addCharacter(boyfriend, BF); - } - - if (dad != null) - { - currentStage.addCharacter(dad, DAD); - // Camera starts at dad. - cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y); - } - - // Redo z-indexes. - currentStage.refresh(); + // Fallback. + loadStage('mainStage'); } } - function initCharacters_NEW() + function initCharacters():Void { - if (currentSong_NEW == null || currentChart == null) + if (currentSong == null || currentChart == null) { trace('Song difficulty could not be loaded.'); } @@ -738,19 +642,18 @@ class PlayState extends MusicBeatState // TODO: Switch playable character by manipulating this value. // TODO: How to choose which one to use for story mode? - var playableChars = currentChart.getPlayableChars(); - var currentPlayer = 'bf'; + var playableChars:Array<String> = currentChart.getPlayableChars(); if (playableChars.length == 0) { trace('WARNING: No playable characters found for this song.'); } - else if (playableChars.indexOf(currentPlayer) == -1) + else if (playableChars.indexOf(currentPlayerId) == -1) { - currentPlayer = playableChars[0]; + currentPlayerId = playableChars[0]; } - var currentCharData:SongPlayableChar = currentChart.getPlayableChar(currentPlayer); + var currentCharData:SongPlayableChar = currentChart.getPlayableChar(currentPlayerId); // // GIRLFRIEND @@ -780,28 +683,18 @@ class PlayState extends MusicBeatState dad.characterType = CharacterType.DAD; } - // TODO: Cut out this code/make it generic. - switch (currentCharData.opponent) - { - case 'gf': - if (isStoryMode) - { - cameraFollowPoint.x += 600; - tweenCamIn(); - } - } - // // OPPONENT HEALTH ICON // iconP2 = new HealthIcon(currentCharData.opponent, 1); iconP2.y = healthBar.y - (iconP2.height / 2); + dad.initHealthIcon(true); add(iconP2); // // BOYFRIEND // - var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentPlayer); + var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentPlayerId); if (boyfriend != null) { @@ -811,8 +704,9 @@ class PlayState extends MusicBeatState // // PLAYER HEALTH ICON // - iconP1 = new HealthIcon(currentPlayer, 0); + iconP1 = new HealthIcon(currentPlayerId, 0); iconP1.y = healthBar.y - (iconP1.height / 2); + boyfriend.initHealthIcon(false); add(iconP1); // @@ -865,7 +759,7 @@ class PlayState extends MusicBeatState * * Call this by pressing F5 on a debug build. */ - override function debug_refreshModules() + override function debug_refreshModules():Void { // Remove the current stage. If the stage gets deleted while it's still in use, // it'll probably crash the game or something. @@ -877,13 +771,22 @@ class PlayState extends MusicBeatState currentStage = null; } + // Stop the vocals. + if (vocals != null) + { + vocals.stop(); + } + super.debug_refreshModules(); + + var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); + ScriptEventDispatcher.callEvent(currentSong, event); } /** * Pauses music and vocals easily. */ - public function pauseMusic() + public function pauseMusic():Void { FlxG.sound.music.pause(); vocals.pause(); @@ -894,7 +797,7 @@ class PlayState extends MusicBeatState * and adds it to the state. * @param id */ - function loadStage(id:String) + function loadStage(id:String):Void { currentStage = StageDataParser.fetchStage(id); @@ -904,7 +807,7 @@ class PlayState extends MusicBeatState var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); ScriptEventDispatcher.callEvent(currentStage, event); - // Apply camera zoom. + // Apply camera zoom level from stage data. defaultCameraZoom = currentStage.camZoom; // Add the stage to the scene. @@ -914,6 +817,12 @@ class PlayState extends MusicBeatState FlxG.console.registerObject('stage', currentStage); #end } + else + { + // lolol + lime.app.Application.current.window.alert('Nice job, you ignoramus. $id isn\'t a real stage.\nI\'m falling back to the default so the game doesn\'t shit itself.', + 'Stage Error'); + } } function initDiscord():Void @@ -934,91 +843,14 @@ class PlayState extends MusicBeatState } // String that contains the mode defined here so it isn't necessary to call changePresence for each mode - detailsText = isStoryMode ? "Story Mode: Week " + storyWeek : "Freeplay"; - detailsPausedText = "Paused - " + detailsText; + detailsText = isStoryMode ? 'Story Mode: Week $storyWeek' : 'Freeplay'; + detailsPausedText = 'Paused - $detailsText'; // Updating Discord Rich Presence. - DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); #end } - function schoolIntro(?dialogueBox:DialogueBox):Void - { - var black:FlxSprite = new FlxSprite(-100, -100).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); - black.scrollFactor.set(); - add(black); - - var red:FlxSprite = new FlxSprite(-100, -100).makeGraphic(FlxG.width * 2, FlxG.height * 2, 0xFFff1b31); - red.scrollFactor.set(); - - var senpaiEvil:FlxSprite = new FlxSprite(); - senpaiEvil.frames = Paths.getSparrowAtlas('weeb/senpaiCrazy'); - senpaiEvil.animation.addByPrefix('idle', 'Senpai Pre Explosion', 24, false); - senpaiEvil.setGraphicSize(Std.int(senpaiEvil.width * Constants.PIXEL_ART_SCALE)); - senpaiEvil.scrollFactor.set(); - senpaiEvil.updateHitbox(); - senpaiEvil.screenCenter(); - senpaiEvil.x += senpaiEvil.width / 5; - - if (currentSong.song.toLowerCase() == 'roses' || currentSong.song.toLowerCase() == 'thorns') - { - remove(black); - - if (currentSong.song.toLowerCase() == 'thorns') - { - add(red); - camHUD.visible = false; - } - else - FlxG.sound.play(Paths.sound('ANGRY')); - // moved senpai angry noise in here to clean up cutscene switch case lol - } - - new FlxTimer().start(0.3, function(tmr:FlxTimer) { - black.alpha -= 0.15; - - if (black.alpha > 0) tmr.reset(0.3); - else - { - if (dialogueBox != null) - { - isInDialog = true; - - if (currentSong.song.toLowerCase() == 'thorns') - { - add(senpaiEvil); - senpaiEvil.alpha = 0; - new FlxTimer().start(0.3, function(swagTimer:FlxTimer) { - senpaiEvil.alpha += 0.15; - if (senpaiEvil.alpha < 1) swagTimer.reset(); - else - { - senpaiEvil.animation.play('idle'); - FlxG.sound.play(Paths.sound('Senpai_Dies'), 1, false, null, true, function() { - remove(senpaiEvil); - remove(red); - FlxG.camera.fade(FlxColor.WHITE, 0.01, true, function() { - add(dialogueBox); - camHUD.visible = true; - }, true); - }); - new FlxTimer().start(3.2, function(deadTime:FlxTimer) { - FlxG.camera.fade(FlxColor.WHITE, 1.6, false); - }); - } - }); - } - else - add(dialogueBox); - } - else - startCountdown(); - - remove(black); - } - }); - } - function startSong():Void { dispatchEvent(new ScriptEvent(ScriptEvent.SONG_START)); @@ -1027,19 +859,9 @@ class PlayState extends MusicBeatState previousFrameTime = FlxG.game.ticks; - if (!isGamePaused) + if (!isGamePaused && currentChart != null) { - // if (FlxG.sound.music != null) - // FlxG.sound.music.play(true); - // else - if (currentChart != null) - { - currentChart.playInst(1.0, false); - } - else - { - FlxG.sound.playMusic(Paths.inst(currentSong.song), 1, false); - } + currentChart.playInst(1.0, false); } FlxG.sound.music.onComplete = endSong; @@ -1051,28 +873,26 @@ class PlayState extends MusicBeatState songLength = FlxG.sound.music.length; // Updating Discord Rich Presence (with Time Left) - DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength); + DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, songLength); #end } function generateSong():Void { - trace('===WARNING=== Song uses old chart format!!!!!'); + if (currentChart == null) + { + trace('Song difficulty could not be loaded.'); + } - Conductor.forceBPM(currentSong.bpm); + Conductor.forceBPM(currentChart.getStartingBPM()); - currentSong.song = currentSong.song; - - vocals = new VoicesGroup(); - var playerVocals:FlxSound = FlxG.sound.load(Paths.voices(currentSong.song, 'bf'), 1.0, false); - vocals.addPlayerVoice(playerVocals); - var opponentVocals:FlxSound = FlxG.sound.load(Paths.voices(currentSong.song, 'dad'), 1.0, false); - vocals.addOpponentVoice(opponentVocals); - - vocals.members[0].onComplete = function() { - vocalsFinished = true; - }; + vocals = currentChart.buildVocals(currentPlayerId); + if (vocals.members.length == 0) + { + trace('WARNING: No vocals found for this song.'); + } + // Create the rendered note group. activeNotes = new FlxTypedGroup<Note>(); activeNotes.zIndex = 1000; add(activeNotes); @@ -1082,144 +902,7 @@ class PlayState extends MusicBeatState generatedMusic = true; } - function generateSong_NEW():Void - { - if (currentChart == null) - { - trace('Song difficulty could not be loaded.'); - } - - Conductor.forceBPM(currentChart.getStartingBPM()); - - // TODO: Fix grouped vocals - vocals = currentChart.buildVocals(); - vocals.onComplete = function() { - vocalsFinished = true; - } - - // Create the rendered note group. - activeNotes = new FlxTypedGroup<Note>(); - activeNotes.zIndex = 1000; - add(activeNotes); - - regenNoteData_NEW(); - - generatedMusic = true; - } - function regenNoteData():Void - { - // resets combo, should prob put somewhere else! - Highscore.tallies.combo = 0; - Highscore.tallies = new Tallies(); - // make unspawn notes shit def empty - inactiveNotes = []; - - activeNotes.forEach(function(nt) { - nt.followsTime = false; - FlxTween.tween(nt, {y: FlxG.height + nt.y}, 0.5, - { - ease: FlxEase.expoIn, - onComplete: function(twn) { - nt.kill(); - activeNotes.remove(nt, true); - nt.destroy(); - } - }); - }); - - var noteData:Array<SwagSection>; - - // NEW SHIT - noteData = SongLoad.getSong(); - - for (section in noteData) - { - for (songNotes in section.sectionNotes) - { - var daStrumTime:Float = songNotes.strumTime; - // TODO: Replace 4 with strumlineSize - var daNoteData:Int = Std.int(songNotes.noteData % 4); - var gottaHitNote:Bool = section.mustHitSection; - - if (songNotes.highStakes) // noteData > 3 - gottaHitNote = !section.mustHitSection; - - var oldNote:Note; - if (inactiveNotes.length > 0) oldNote = inactiveNotes[Std.int(inactiveNotes.length - 1)]; - else - oldNote = null; - - var strumlineStyle:StrumlineStyle = NORMAL; - - // TODO: Put this in the chart or something? - switch (currentStageId) - { - case 'school': - strumlineStyle = PIXEL; - case 'schoolEvil': - strumlineStyle = PIXEL; - } - - var swagNote:Note = new Note(daStrumTime, daNoteData, oldNote, false, strumlineStyle); - // swagNote.data = songNotes; - swagNote.data.sustainLength = songNotes.sustainLength; - swagNote.data.noteKind = songNotes.noteKind; - swagNote.scrollFactor.set(0, 0); - - var susLength:Float = swagNote.data.sustainLength; - - susLength = susLength / Conductor.stepCrochet; - inactiveNotes.push(swagNote); - - for (susNote in 0...Math.round(susLength)) - { - oldNote = inactiveNotes[Std.int(inactiveNotes.length - 1)]; - - var sustainNote:Note = new Note(daStrumTime + (Conductor.stepCrochet * susNote) + Conductor.stepCrochet, daNoteData, oldNote, true, strumlineStyle); - sustainNote.data.noteKind = songNotes.noteKind; - sustainNote.scrollFactor.set(); - inactiveNotes.push(sustainNote); - - sustainNote.mustPress = gottaHitNote; - - if (sustainNote.mustPress) sustainNote.x += FlxG.width / 2; // general offset - } - - // TODO: Replace 4 with strumlineSize - swagNote.mustPress = gottaHitNote; - - if (swagNote.mustPress) - { - if (playerStrumline != null) - { - swagNote.x = playerStrumline.getArrow(swagNote.data.noteData).x; - } - else - { - swagNote.x += FlxG.width / 2; // general offset - } - } - else - { - if (enemyStrumline != null) - { - swagNote.x = enemyStrumline.getArrow(swagNote.data.noteData).x; - } - else - { - // swagNote.x += FlxG.width / 2; // general offset - } - } - } - } - - inactiveNotes.sort(function(a:Note, b:Note):Int { - return SortUtil.byStrumtime(FlxSort.ASCENDING, a, b); - }); - } - - function regenNoteData_NEW():Void { Highscore.tallies.combo = 0; Highscore.tallies = new Tallies(); @@ -1274,27 +957,11 @@ class PlayState extends MusicBeatState // TODO: Make this more robust. if (newNote.mustPress) { - if (playerStrumline != null) - { - // Align with the strumline arrow. - newNote.x = playerStrumline.getArrow(songNote.getDirection()).x; - } - else - { - // Assume strumline position. - newNote.x += FlxG.width / 2; - } + newNote.alignToSturmlineArrow(playerStrumline.getArrow(songNote.getDirection())); } else { - if (enemyStrumline != null) - { - newNote.x = enemyStrumline.getArrow(songNote.getDirection()).x; - } - else - { - // newNote.x += 0; - } + newNote.alignToSturmlineArrow(enemyStrumline.getArrow(songNote.getDirection())); } inactiveNotes.push(newNote); @@ -1302,10 +969,10 @@ class PlayState extends MusicBeatState oldNote = newNote; // Generate X sustain notes. - var sustainSections = Math.round(songNote.length / Conductor.stepCrochet); + var sustainSections = Math.round(songNote.length / Conductor.stepLengthMs); for (noteIndex in 0...sustainSections) { - var noteTimeOffset:Float = Conductor.stepCrochet + (Conductor.stepCrochet * noteIndex); + var noteTimeOffset:Float = Conductor.stepLengthMs + (Conductor.stepLengthMs * noteIndex); var sustainNote:Note = new Note(songNote.time + noteTimeOffset, songNote.data, oldNote, true, strumlineStyle); sustainNote.mustPress = mustHitNote; sustainNote.data.noteKind = songNote.kind; @@ -1313,27 +980,12 @@ class PlayState extends MusicBeatState if (sustainNote.mustPress) { - if (playerStrumline != null) - { - // Align with the strumline arrow. - sustainNote.x = playerStrumline.getArrow(songNote.getDirection()).x; - } - else - { - // Assume strumline position. - sustainNote.x += FlxG.width / 2; - } + // Align with the strumline arrow. + sustainNote.alignToSturmlineArrow(playerStrumline.getArrow(songNote.getDirection())); } else { - if (enemyStrumline != null) - { - sustainNote.x = enemyStrumline.getArrow(songNote.getDirection()).x; - } - else - { - // newNote.x += 0; - } + sustainNote.alignToSturmlineArrow(enemyStrumline.getArrow(songNote.getDirection())); } inactiveNotes.push(sustainNote); @@ -1343,29 +995,24 @@ class PlayState extends MusicBeatState } // Sorting is an expensive operation. - // Assume it was done in the chart file. + // TODO: Make this more efficient. + // DO NOT assume it was done in the chart file. Notes created artificially by sustains are in here too. + inactiveNotes.sort(function(a:Note, b:Note):Int { + return SortUtil.byStrumtime(FlxSort.ASCENDING, a, b); + }); /** - inactiveNotes.sort(function(a:Note, b:Note):Int - { - return SortUtil.byStrumtime(FlxSort.ASCENDING, a, b); - }); **/ } - function tweenCamIn():Void - { - FlxTween.tween(FlxG.camera, {zoom: 1.3 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); - } - #if discord_rpc override public function onFocus():Void { if (health > 0 && !paused && FlxG.autoPause) { - if (Conductor.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC, true, + if (Conductor.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC, true, songLength - Conductor.songPosition); else - DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); } super.onFocus(); @@ -1373,7 +1020,7 @@ class PlayState extends MusicBeatState override public function onFocusLost():Void { - if (health > 0 && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + if (health > 0 && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); super.onFocusLost(); } @@ -1386,20 +1033,18 @@ class PlayState extends MusicBeatState vocals.pause(); FlxG.sound.music.play(); - Conductor.update(FlxG.sound.music.time + Conductor.offset); - - if (vocalsFinished) return; + Conductor.update(); vocals.time = FlxG.sound.music.time; vocals.play(); } - override public function update(elapsed:Float) + public override function update(elapsed:Float):Void { - super.update(elapsed); - if (criticalFailure) return; + super.update(elapsed); + if (FlxG.keys.justPressed.U) { // hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!! @@ -1411,6 +1056,7 @@ class PlayState extends MusicBeatState updateHealthBar(); updateScoreText(); + // Handle restarting the song when needed (player death or pressing Retry) if (needsReset) { dispatchEvent(new ScriptEvent(ScriptEvent.SONG_RETRY)); @@ -1439,14 +1085,12 @@ class PlayState extends MusicBeatState currentStage.resetStage(); // Delete all notes and reset the arrays. - if (currentChart != null) - { - regenNoteData_NEW(); - } - else - { - regenNoteData(); - } + regenNoteData(); + + // Reset camera zooming + cameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY; + hudCameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * 2.0; + cameraZoomRate = Constants.DEFAULT_ZOOM_RATE; health = 1; songScore = 0; @@ -1456,13 +1100,7 @@ class PlayState extends MusicBeatState needsReset = false; } - #if !debug - perfectMode = false; - #else - if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible; - #end - - // do this BEFORE super.update() so songPosition is accurate + // Update the conductor. if (startingSong) { if (isInCountdown) @@ -1473,9 +1111,12 @@ class PlayState extends MusicBeatState } else { - if (Paths.SOUND_EXT == 'mp3') Conductor.offset = -13; // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM! + // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM! - Conductor.update(FlxG.sound.music.time + Conductor.offset); + // :nerd: um ackshually it's not 13 it's 11.97278911564 + if (Paths.SOUND_EXT == 'mp3') Conductor.offset = Constants.MP3_DELAY_MS; + + Conductor.update(); if (!isGamePaused) { @@ -1497,6 +1138,7 @@ class PlayState extends MusicBeatState androidPause = FlxG.android.justPressed.BACK; #end + // Attempt to pause the game. if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame) { var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000)); @@ -1515,77 +1157,58 @@ class PlayState extends MusicBeatState // It's a reference to Gitaroo Man, which doesn't let you pause the game. if (event.gitaroo) { - FlxG.switchState(new GitarooPause()); + FlxG.switchState(new GitarooPause( + { + targetSong: currentSong, + targetDifficulty: currentDifficulty, + targetCharacter: currentPlayerId, + })); } else { - var boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); - var pauseSubState = new PauseSubState(); + var boyfriendPos:FlxPoint = new FlxPoint(0, 0); + + // Prevent the game from crashing if Boyfriend isn't present. + if (currentStage != null && currentStage.getBoyfriend() != null) + { + boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); + } + + var pauseSubState:FlxSubState = new PauseSubState(); + openSubState(pauseSubState); pauseSubState.camera = camHUD; - boyfriendPos.put(); + // boyfriendPos.put(); // TODO: Why is this here? } #if discord_rpc - DiscordClient.changePresence(detailsPausedText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); #end } } - #if debug - // 1: End the song immediately. - if (FlxG.keys.justPressed.ONE) endSong(); - - // 2: Gain 10% health. - if (FlxG.keys.justPressed.TWO) health += 0.1 * 2.0; - - // 3: Lose 5% health. - if (FlxG.keys.justPressed.THREE) health -= 0.05 * 2.0; - #end - - // 7: Move to the charter. - if (FlxG.keys.justPressed.SEVEN) - { - FlxG.switchState(new ChartingState()); - - #if discord_rpc - DiscordClient.changePresence("Chart Editor", null, null, true); - #end - } - - // 8: Move to the offset editor. - if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState()); - - // 9: Toggle the old icon. - if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon(); - - #if debug - // PAGEUP: Skip forward one section. - // SHIFT+PAGEUP: Skip forward ten sections. - if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 10 : 1); - // PAGEDOWN: Skip backward one section. Doesn't replace notes. - // SHIFT+PAGEDOWN: Skip backward ten sections. - if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -10 : -1); - #end - + // Cap health. if (health > 2.0) health = 2.0; if (health < 0.0) health = 0.0; + // Lerp the camera zoom towards the target level. if (subState == null) { FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95); - camHUD.zoom = FlxMath.lerp(1 * FlxCamera.defaultZoom, camHUD.zoom, 0.95); + camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } - FlxG.watch.addQuick("beatShit", Conductor.currentBeat); - FlxG.watch.addQuick("stepShit", Conductor.currentStep); + FlxG.watch.addQuick('beatShit', Conductor.currentBeat); + FlxG.watch.addQuick('stepShit', Conductor.currentStep); if (currentStage != null) { - FlxG.watch.addQuick("bfAnim", currentStage.getBoyfriend().getCurrentAnimation()); + FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } - FlxG.watch.addQuick("songPos", Conductor.songPosition); + FlxG.watch.addQuick('songPos', Conductor.songPosition); - if (currentSong != null && currentSong.song == 'Fresh') + // Handle GF dance speed. + // TODO: Add a song event for this. + if (currentSong.songId == 'fresh') { switch (Conductor.currentBeat) { @@ -1600,20 +1223,21 @@ class PlayState extends MusicBeatState } } - if (!isInCutscene && !isInDialog && !disableKeys && !_exiting) + // Handle player death. + if (!isInCutscene && !disableKeys && !_exiting) { // RESET = Quick Game Over Screen if (controls.RESET) { health = 0; - trace("RESET = True"); + trace('RESET = True'); } #if CAN_CHEAT // brandon's a pussy if (controls.CHEAT) { health += 1; - trace("User is cheating!"); + trace('User is cheating!'); } #end @@ -1648,12 +1272,13 @@ class PlayState extends MusicBeatState #if discord_rpc // Game Over doesn't get his own variable because it's only used here - DiscordClient.changePresence("Game Over - " + detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); #end } } - while (inactiveNotes[0] != null && inactiveNotes[0].data.strumTime - Conductor.songPosition < 1800 / SongLoad.getSpeed()) + // Iterate over inactive notes. + while (inactiveNotes[0] != null && inactiveNotes[0].data.strumTime - Conductor.songPosition < 1800 / currentChart.scrollSpeed) { var dunceNote:Note = inactiveNotes[0]; @@ -1664,6 +1289,7 @@ class PlayState extends MusicBeatState inactiveNotes.shift(); } + // Iterate over active notes. if (generatedMusic && playerStrumline != null) { activeNotes.forEachAlive(function(daNote:Note) { @@ -1679,19 +1305,26 @@ class PlayState extends MusicBeatState daNote.active = true; } - var strumLineMid = playerStrumline.y + Note.swagWidth / 2; + var strumLineMid:Float = playerStrumline.y + Note.swagWidth / 2; - if (daNote.followsTime) daNote.y = (Conductor.songPosition - daNote.data.strumTime) * (0.45 * FlxMath.roundDecimal(SongLoad.getSpeed(), - 2) * daNote.noteSpeedMulti); + if (daNote.followsTime) + { + daNote.y = (Conductor.songPosition - daNote.data.strumTime) * (0.45 * FlxMath.roundDecimal(currentChart.scrollSpeed, 2) * daNote.noteSpeedMulti); + } if (PreferencesMenu.getPref('downscroll')) { daNote.y += playerStrumline.y; if (daNote.isSustainNote) { - if (daNote.animation.curAnim.name.endsWith("end") && daNote.prevNote != null) daNote.y += daNote.prevNote.height; + if (daNote.animation.curAnim.name.endsWith('end') && daNote.prevNote != null) + { + daNote.y += daNote.prevNote.height; + } else + { daNote.y += daNote.height / 2; + } if ((!daNote.mustPress || (daNote.wasGoodHit || (daNote.prevNote.wasGoodHit && !daNote.canBeHit))) && daNote.y - daNote.offset.y * daNote.scale.y + daNote.height >= strumLineMid) @@ -1724,15 +1357,10 @@ class PlayState extends MusicBeatState { daNote.tooLate = true; } - else - { - // Volume of DAD. - if (currentSong != null && currentSong.needsVoices) vocals.opponentVolume = 1; - } } // WIP interpolation shit? Need to fix the pause issue - // daNote.y = (strumLine.y - (songTime - daNote.strumTime) * (0.45 * SONG.speed[SongLoad.curDiff])); + // daNote.y = (strumLine.y - (songTime - daNote.strumTime) * (0.45 * SONG.speed[.curDiff])); // removing this so whether the note misses or not is entirely up to Note class // var noteMiss:Bool = daNote.y < -daNote.height; @@ -1770,6 +1398,7 @@ class PlayState extends MusicBeatState }); } + // Query and activate song events. if (songEvents != null && songEvents.length > 0) { var songEventsToActivate:Array<SongEventData> = SongEventParser.queryEvents(songEvents, Conductor.songPosition); @@ -1790,17 +1419,35 @@ class PlayState extends MusicBeatState } } - if (!isInCutscene && !isInDialog && !disableKeys) keyShit(true); - if (isInCutscene && !disableKeys) handleCutsceneKeys(); + // Handle keybinds. + if (!isInCutscene && !disableKeys) keyShit(true); + if (!isInCutscene && !disableKeys) debugKeyShit(); + + // Dispatch the onUpdate event to scripted elements. + dispatchEvent(new UpdateScriptEvent(elapsed)); } static final CUTSCENE_KEYS:Array<FlxKey> = [SPACE, ESCAPE, ENTER]; - function handleCutsceneKeys():Void + public function trySkipVideoCutscene(elapsed:Float):Void { - if (FlxG.keys.anyJustPressed(CUTSCENE_KEYS)) + if (skipTimer == null || skipTimer.animation == null) return; + + if (elapsed < 0) { - VanillaCutscenes.finishCutscene(); + skipHeldTimer = 0.0; + } + else + { + skipHeldTimer += elapsed; + } + + skipTimer.visible = skipHeldTimer >= 0.05; + skipTimer.amount = Math.min(skipHeldTimer / 1.5, 1.0); + + if (skipHeldTimer >= 1.5) + { + VideoCutscene.finishVideo(); } } @@ -1827,8 +1474,13 @@ class PlayState extends MusicBeatState function killCombo():Void { // Girlfriend gets sad if you combo break after hitting 5 notes. - if (currentStage != null && currentStage.getGirlfriend() != null) if (Highscore.tallies.combo > 5 - && currentStage.getGirlfriend().hasAnimation('sad')) currentStage.getGirlfriend().playAnimation('sad'); + if (currentStage != null && currentStage.getGirlfriend() != null) + { + if (Highscore.tallies.combo > 5 && currentStage.getGirlfriend().hasAnimation('sad')) + { + currentStage.getGirlfriend().playAnimation('sad'); + } + } if (Highscore.tallies.combo != 0) { @@ -1840,26 +1492,36 @@ class PlayState extends MusicBeatState /** * Jumps forward or backward a number of sections in the song. * Accounts for BPM changes, does not prevent death from skipped notes. - * @param sec + * @param sections The number of sections to jump, negative to go backwards. */ - function changeSection(sec:Int):Void + function changeSection(sections:Int):Void { FlxG.sound.music.pause(); - var daBPM:Float = currentSong.bpm; - var daPos:Float = 0; - for (i in 0...(Std.int(Conductor.currentStep / 16 + sec))) - { - var section = SongLoad.getSong()[i]; - if (section == null) continue; - if (section.changeBPM) + FlxG.sound.music.time += sections * Conductor.measureLengthMs; + + Conductor.update(FlxG.sound.music.time); + + /** + * + // TODO: Redo this for the new conductor. + var daBPM:Float = Conductor.bpm; + var daPos:Float = 0; + for (i in 0...(Std.int(Conductor.currentStep / 16 + sec))) { - daBPM = SongLoad.getSong()[i].bpm; + var section = .getSong()[i]; + if (section == null) continue; + if (section.changeBPM) + { + daBPM = .getSong()[i].bpm; + } + daPos += 4 * (1000 * 60 / daBPM); } - daPos += 4 * (1000 * 60 / daBPM); - } - Conductor.songPosition = FlxG.sound.music.time = daPos; - Conductor.songPosition += Conductor.offset; + Conductor.songPosition = FlxG.sound.music.time = daPos; + Conductor.songPosition += Conductor.offset; + + */ + resyncVocals(); } #end @@ -1870,11 +1532,11 @@ class PlayState extends MusicBeatState #if sys // spitter for ravy, teehee!! + var output = SerializerUtil.toJSON(inputSpitter); sys.io.File.saveContent("./scores.json", output); #end - seenCutscene = false; deathCounter = 0; mayPauseGame = false; FlxG.sound.music.volume = 0; @@ -1882,54 +1544,45 @@ class PlayState extends MusicBeatState if (currentSong != null && currentSong.validScore) { // crackhead double thingie, sets whether was new highscore, AND saves the song! - Highscore.tallies.isNewHighscore = Highscore.saveScore(currentSong.song, songScore, storyDifficulty); + Highscore.tallies.isNewHighscore = Highscore.saveScoreForDifficulty(currentSong.songId, songScore, currentDifficulty); - Highscore.saveCompletion(currentSong.song, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, storyDifficulty); + Highscore.saveCompletionForDifficulty(currentSong.songId, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, currentDifficulty); } - if (isStoryMode) + if (PlayStatePlaylist.isStoryMode) { - campaignScore += songScore; + PlayStatePlaylist.campaignScore += songScore; - storyPlaylist.remove(storyPlaylist[0]); + // Pop the next song ID from the list. + // Returns null if the list is empty. + var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift(); - if (storyPlaylist.length <= 0) + if (targetSongId == null) { FlxG.sound.playMusic(Paths.music('freakyMenu')); transIn = FlxTransitionableState.defaultTransIn; transOut = FlxTransitionableState.defaultTransOut; - switch (storyWeek) - { - case 7: - FlxG.switchState(new VideoState()); - default: - FlxG.switchState(new StoryMenuState()); - } - - // if () + // TODO: Rework week unlock logic. // StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; - if (currentSong?.validScore) + if (currentSong.validScore) { NGio.unlockMedal(60961); - Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty); + Highscore.saveWeekScoreForDifficulty(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignScore, currentDifficulty); } - // FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked; + FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked; FlxG.save.flush(); + + moveToResultsScreen(); } else { - var difficulty:String = ""; + var difficulty:String = ''; - if (storyDifficulty == 0) difficulty = '-easy'; - - if (storyDifficulty == 2) difficulty = '-hard'; - - trace('LOADING NEXT SONG'); - trace(storyPlaylist[0].toLowerCase() + difficulty); + trace('Loading next song ($targetSongId : $difficulty)'); FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; @@ -1937,7 +1590,8 @@ class PlayState extends MusicBeatState FlxG.sound.music.stop(); vocals.stop(); - if ((currentSong?.song ?? '').toLowerCase() == 'eggnog') + // TODO: Softcode this cutscene. + if (currentSong.songId == 'eggnog') { var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); @@ -1948,51 +1602,104 @@ class PlayState extends MusicBeatState FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() { // no camFollow so it centers on horror tree - currentSong = SongLoad.loadFromJson(storyPlaylist[0].toLowerCase() + difficulty, storyPlaylist[0]); - LoadingState.loadAndSwitchState(new PlayState()); + var targetSong:Song = SongDataParser.fetchSong(targetSongId); + + var nextPlayState:PlayState = new PlayState( + { + targetSong: targetSong, + targetDifficulty: currentDifficulty, + targetCharacter: currentPlayerId, + }); + nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y); + LoadingState.loadAndSwitchState(nextPlayState); }); } else { - previousCameraFollowPoint = cameraFollowPoint; - - currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase()); - LoadingState.loadAndSwitchState(new PlayState()); + var targetSong:Song = SongDataParser.fetchSong(targetSongId); + var nextPlayState:PlayState = new PlayState( + { + targetSong: targetSong, + targetDifficulty: currentDifficulty, + targetCharacter: currentPlayerId, + }); + nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y); + LoadingState.loadAndSwitchState(nextPlayState); } } } else { - trace('WENT TO RESULTS SCREEN!'); - trace(songScore); - // unloadAssets(); + moveToResultsScreen(); + } + } - camZoomRate = 0; + /** + * Play the camera zoom animation and move to the results screen. + */ + function moveToResultsScreen():Void + { + trace('WENT TO RESULTS SCREEN!'); + // Stop camera zooming on beat. + cameraZoomRate = 0; + + // If the opponent is GF, zoom in on the opponent. + // Else, if there is no GF, zoom in on BF. + // Else, zoom in on GF. + var targetDad:Bool = PlayState.instance.currentStage.getDad() != null && PlayState.instance.currentStage.getDad().characterId == 'gf'; + var targetBF:Bool = PlayState.instance.currentStage.getGirlfriend() == null && !targetDad; + + if (targetBF) + { + FlxG.camera.follow(PlayState.instance.currentStage.getBoyfriend(), null, 0.05); + FlxG.camera.targetOffset.y -= 350; + FlxG.camera.targetOffset.x += 20; + } + else if (targetDad) + { + FlxG.camera.follow(PlayState.instance.currentStage.getDad(), null, 0.05); + FlxG.camera.targetOffset.y -= 350; + FlxG.camera.targetOffset.x += 20; + } + else + { FlxG.camera.follow(PlayState.instance.currentStage.getGirlfriend(), null, 0.05); FlxG.camera.targetOffset.y -= 350; FlxG.camera.targetOffset.x += 20; - - FlxTween.tween(camHUD, {alpha: 0}, 0.6); - - new FlxTimer().start(0.8, _ -> { - currentStage.getGirlfriend().animation.play("cheer"); - - FlxTween.tween(FlxG.camera, {zoom: 1200}, 1.1, - { - ease: FlxEase.expoIn, - onComplete: _ -> { - persistentUpdate = false; - vocals.stop(); - camHUD.alpha = 1; - var res:ResultState = new ResultState(); - res.camera = camHUD; - openSubState(res); - } - }); - }); - // FlxG.switchState(new FreeplayState()); } + + FlxTween.tween(camHUD, {alpha: 0}, 0.6); + + // Zoom in on Girlfriend (or BF if no GF) + new FlxTimer().start(0.8, function(_) { + if (targetBF) + { + currentStage.getBoyfriend().animation.play('hey'); + } + else if (targetDad) + { + currentStage.getDad().animation.play('cheer'); + } + else + { + currentStage.getGirlfriend().animation.play('cheer'); + } + + // Zoom over to the Results screen. + FlxTween.tween(FlxG.camera, {zoom: 1200}, 1.1, + { + ease: FlxEase.expoIn, + onComplete: function(_) { + persistentUpdate = false; + vocals.stop(); + camHUD.alpha = 1; + var res:ResultState = new ResultState(); + res.camera = camHUD; + openSubState(res); + } + }); + }); } // gives score and pops up rating @@ -2052,6 +1759,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array<Int> = []; for (i in 0...pressArray.length) { @@ -2083,63 +1791,6 @@ class PlayState extends MusicBeatState if (Highscore.tallies.combo >= 10 || Highscore.tallies.combo == 0) comboPopUps.displayCombo(Highscore.tallies.combo); } - /* - function controlCamera() - { - if (currentStage == null) - return; - - switch (cameraFocusCharacter) - { - default: // null = No change - break; - case 0: // Boyfriend - var isFocusedOnBF = cameraFollowPoint.x == currentStage.getBoyfriend().cameraFocusPoint.x; - if (!isFocusedOnBF) - { - // Focus the camera on the player. - cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y); - } - case 1: // Dad - var isFocusedOnDad = cameraFollowPoint.x == currentStage.getDad().cameraFocusPoint.x; - if (!isFocusedOnDad) - { - cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y); - } - case 2: // Girlfriend - var isFocusedOnGF = cameraFollowPoint.x == currentStage.getGirlfriend().cameraFocusPoint.x; - if (!isFocusedOnGF) - { - cameraFollowPoint.setPosition(currentStage.getGirlfriend().cameraFocusPoint.x, currentStage.getGirlfriend().cameraFocusPoint.y); - } - } - - /* - if (cameraRightSide && !isFocusedOnBF) - { - // Focus the camera on the player. - cameraFollowPoint.setPosition(currentStage.getBoyfriend().cameraFocusPoint.x, currentStage.getBoyfriend().cameraFocusPoint.y); - - // TODO: Un-hardcode this. - if (currentSong.song.toLowerCase() == 'tutorial') - FlxTween.tween(FlxG.camera, {zoom: 1 * FlxCamera.defaultZoom}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut}); - } - else if (!cameraRightSide && !isFocusedOnDad) - { - // Focus the camera on the opponent. - cameraFollowPoint.setPosition(currentStage.getDad().cameraFocusPoint.x, currentStage.getDad().cameraFocusPoint.y); - - // TODO: Un-hardcode this stuff. - if (currentStage.getDad().characterId == 'mom') - { - } - - if (currentSong.song.toLowerCase() == 'tutorial') - tweenCamIn(); - } - */ - // } - /** * Spitting out the input for ravy 🙇♂️!! */ @@ -2167,11 +1818,9 @@ class PlayState extends MusicBeatState // if (pressArray.contains(true)) // { // var lol:Array<Int> = cast pressArray; - // inputSpitter.push(Std.int(Conductor.songPosition) + " " + lol.join(" ")); + // inputSpitter.push(Std.int(Conductor.songPosition) + ' ' + lol.join(' ')); // } - if (FlxG.keys.justPressed.B) trace(inputSpitter.join("\n")); - // HOLDS, check for sustain notes if (holdArray.contains(true) && PlayState.instance.generatedMusic) { @@ -2185,7 +1834,10 @@ class PlayState extends MusicBeatState { Haptic.vibrate(100, 100); - PlayState.instance.currentStage.getBoyfriend().holdTimer = 0; + if (currentStage != null && currentStage.getBoyfriend() != null) + { + currentStage.getBoyfriend().holdTimer = 0; + } var possibleNotes:Array<Note> = []; // notes that can be hit var directionList:Array<Int> = []; // directions that can be hit @@ -2222,7 +1874,7 @@ class PlayState extends MusicBeatState for (note in dumbNotes) { - FlxG.log.add("killing dumb ass note at " + note.data.strumTime); + FlxG.log.add('killing dumb ass note at ' + note.data.strumTime); note.kill(); PlayState.instance.activeNotes.remove(note, true); note.destroy(); @@ -2269,6 +1921,65 @@ class PlayState extends MusicBeatState } } + /** + * Debug keys. Disabled while in cutscenes. + */ + public function debugKeyShit():Void + { + #if !debug + perfectMode = false; + #else + if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible; + #end + + if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState()); + + if (FlxG.keys.justPressed.F5) debug_refreshModules(); + + // Press U to open stage ditor. + if (FlxG.keys.justPressed.U) + { + // hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!! + disableKeys = true; + persistentUpdate = false; + openSubState(new StageOffsetSubState()); + } + + #if debug + // 1: End the song immediately. + if (FlxG.keys.justPressed.ONE) endSong(); + + // 2: Gain 10% health. + if (FlxG.keys.justPressed.TWO) health += 0.1 * 2.0; + + // 3: Lose 5% health. + if (FlxG.keys.justPressed.THREE) health -= 0.05 * 2.0; + #end + + // 7: Move to the charter. + if (FlxG.keys.justPressed.SEVEN) + { + lime.app.Application.current.window.alert("Press ~ on the main menu to get to the editor", 'LOL'); + } + + // 8: Move to the offset editor. + if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState()); + + // 9: Toggle the old icon. + if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon(); + + #if debug + // PAGEUP: Skip forward one section. + // SHIFT+PAGEUP: Skip forward ten sections. + if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 10 : 1); + // PAGEDOWN: Skip backward one section. Doesn't replace notes. + // SHIFT+PAGEDOWN: Skip backward ten sections. + if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -10 : -1); + #end + + if (FlxG.keys.justPressed.B) trace(inputSpitter.join('\n')); + } + /** * Called when a player presses a key with no note present. * Scripts can modify the amount of health/score lost, whether player animations or sounds are used, @@ -2301,6 +2012,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array<Int> = []; for (i in 0...pressArray.length) { @@ -2347,6 +2059,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array<Int> = []; for (i in 0...pressArray.length) { @@ -2381,6 +2094,12 @@ class PlayState extends MusicBeatState Highscore.tallies.combo = comboPopUps.displayCombo(0); } + if (event.playSound) + { + vocals.playerVolume = 0; + FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); + } + note.active = false; note.visible = false; @@ -2425,14 +2144,15 @@ class PlayState extends MusicBeatState override function stepHit():Bool { - if (SongLoad.songData == null) return false; - // super.stepHit() returns false if a module cancelled the event. if (!super.stepHit()) return false; - if (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 20 - || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 20) + if (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 + || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200) { + trace("VOCALS NEED RESYNC"); + if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)); + trace(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)); resyncVocals(); } @@ -2453,47 +2173,17 @@ class PlayState extends MusicBeatState activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING); } - // Moving this code into the `beatHit` function allows for scripts and modules to control the camera better. - if (currentSong != null) + // Only zoom camera if we are zoomed by less than 35%. + if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.currentBeat % cameraZoomRate == 0) { - if (generatedMusic && SongLoad.getSong()[Std.int(Conductor.currentStep / 16)] != null) - { - // cameraRightSide = SongLoad.getSong()[Std.int(Conductor.currentStep / 16)].mustHitSection; - } - - if (SongLoad.getSong()[Math.floor(Conductor.currentStep / 16)] != null) - { - if (SongLoad.getSong()[Math.floor(Conductor.currentStep / 16)].changeBPM) - { - Conductor.forceBPM(SongLoad.getSong()[Math.floor(Conductor.currentStep / 16)].bpm); - FlxG.log.add('CHANGED BPM!'); - } - } + // Zoom camera in (1.5%) + FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom; + // Hud zooms double (3%) + camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom; } + // trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.currentBeat} % ${cameraZoomRate} == ${Conductor.currentBeat % cameraZoomRate}}'); - if (PreferencesMenu.getPref('camera-zoom')) - { - // TODO: Move this into a song script. - if (currentSong != null - && currentSong.song.toLowerCase() == 'milf' - && Conductor.currentBeat >= 168 - && Conductor.currentBeat < 200) - { - camZoomRate = 1; - } - if (currentSong != null && currentSong.song.toLowerCase() == 'milf' && Conductor.currentBeat >= 200) - { - camZoomRate = 4; - } - - if (FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom) && camZoomRate > 0 && Conductor.currentBeat % camZoomRate == 0) - { - FlxG.camera.zoom += 0.015 * FlxCamera.defaultZoom; - camHUD.zoom += 0.03; - } - } - - // That combo counter that got spoiled that one time. + // That combo milestones that got spoiled that one time. // Comes with NEAT visual and audio effects. // bruh this var is bonkers i thot it was a function lmfaooo @@ -2501,17 +2191,18 @@ class PlayState extends MusicBeatState // Break up into individual lines to aid debugging. var shouldShowComboText:Bool = false; - if (currentSong != null) - { - shouldShowComboText = (Conductor.currentBeat % 8 == 7); - var daSection = SongLoad.getSong()[Std.int(Conductor.currentBeat / 16)]; - shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection); - shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5); - - var daNextSection = SongLoad.getSong()[Std.int(Conductor.currentBeat / 16) + 1]; - var isEndOfSong = SongLoad.getSong().length < Std.int(Conductor.currentBeat / 16); - shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection)); - } + // TODO: Re-enable combo text (how to do this without sections?). + // if (currentSong != null) + // { + // shouldShowComboText = (Conductor.currentBeat % 8 == 7); + // var daSection = .getSong()[Std.int(Conductor.currentBeat / 16)]; + // shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection); + // shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5); + // + // var daNextSection = .getSong()[Std.int(Conductor.currentBeat / 16) + 1]; + // var isEndOfSong = .getSong().length < Std.int(Conductor.currentBeat / 16); + // shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection)); + // } if (shouldShowComboText) { @@ -2522,7 +2213,7 @@ class PlayState extends MusicBeatState var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation - new FlxTimer().start(((Conductor.crochet / 1000) * 1.25) - frameShit, function(tmr) { + new FlxTimer().start(((Conductor.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) { animShit.forceFinish(); }); } @@ -2538,12 +2229,12 @@ class PlayState extends MusicBeatState * * TODO: Move some of this logic into `Bopper.hx` */ - public function danceOnBeat() + public function danceOnBeat():Void { if (currentStage == null) return; - // TODO: Move this to a song event. - if (Conductor.currentBeat % 16 == 15 // && currentSong.song == 'Tutorial' + // TODO: Add HEY! song events to Tutorial. + if (Conductor.currentBeat % 16 == 15 && currentStage.getDad().characterId == 'gf' && Conductor.currentBeat > 16 && Conductor.currentBeat < 48) @@ -2579,7 +2270,7 @@ class PlayState extends MusicBeatState add(playerStrumline); playerStrumline.cameras = [camHUD]; - if (!isStoryMode) + if (!PlayStatePlaylist.isStoryMode) { playerStrumline.fadeInArrows(); } @@ -2592,7 +2283,7 @@ class PlayState extends MusicBeatState add(enemyStrumline); enemyStrumline.cameras = [camHUD]; - if (!isStoryMode) + if (!PlayStatePlaylist.isStoryMode) { enemyStrumline.fadeInArrows(); } @@ -2604,7 +2295,7 @@ class PlayState extends MusicBeatState * Function called before opening a new substate. * @param subState The substate to open. */ - public override function openSubState(subState:FlxSubState) + public override function openSubState(subState:FlxSubState):Void { // If there is a substate which requires the game to continue, // then make this a condition. @@ -2630,7 +2321,7 @@ class PlayState extends MusicBeatState * Function called before closing the current substate. * @param subState */ - public override function closeSubState() + public override function closeSubState():Void { if (isGamePaused) { @@ -2640,16 +2331,20 @@ class PlayState extends MusicBeatState if (event.eventCanceled) return; - if (FlxG.sound.music != null && !startingSong && !isInCutscene && !isInDialog) resyncVocals(); + if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. Countdown.resumeCountdown(); #if discord_rpc - if (startTimer.finished) DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC, true, - songLength - Conductor.songPosition); + if (startTimer.finished) + { + DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, songLength - Conductor.songPosition); + } else - DiscordClient.changePresence(detailsText, currentSong.song + " (" + storyDifficultyText + ")", iconRPC); + { + DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); + } #end } @@ -2667,11 +2362,8 @@ class PlayState extends MusicBeatState if (!result) return; isInCutscene = false; - isInDialog = false; + camCutscene.visible = false; camHUD.visible = true; - talking = false; - - buildStrumlines(); } override function dispatchEvent(event:ScriptEvent):Void @@ -2687,6 +2379,10 @@ class PlayState extends MusicBeatState // Dispatch event to character script(s). if (currentStage != null) currentStage.dispatchToCharacters(event); + + ScriptEventDispatcher.callEvent(currentSong, event); + + // TODO: Dispatch event to note scripts } /** @@ -2695,7 +2391,7 @@ class PlayState extends MusicBeatState function updateScoreText():Void { // TODO: Add functionality for modules to update the score text. - scoreText.text = "Score:" + songScore; + scoreText.text = 'Score:' + songScore; } /** @@ -2720,14 +2416,11 @@ class PlayState extends MusicBeatState /** * Perform necessary cleanup before leaving the PlayState. */ - function performCleanup() + function performCleanup():Void { - // Uncache the song. - if (currentChart != null) {} - else if (currentSong != null) + if (currentChart != null) { - openfl.utils.Assets.cache.clear(Paths.inst(currentSong.song)); - openfl.utils.Assets.cache.clear(Paths.voices(currentSong.song)); + // TODO: Uncache the song. } // Remove reference to stage and remove sprites from it to save memory. @@ -2749,10 +2442,15 @@ class PlayState extends MusicBeatState * This function is called whenever Flixel switches switching to a new FlxState. * @return Whether to actually switch to the new state. */ - override function startOutro(onComplete:() -> Void):Void + override function switchTo(nextState:FlxState):Bool { - performCleanup(); + var result:Bool = super.switchTo(nextState); - onComplete(); + if (result) + { + performCleanup(); + } + + return result; } }