diff --git a/Project.xml b/Project.xml index e14e0ae0a..2d9dd802b 100644 --- a/Project.xml +++ b/Project.xml @@ -105,7 +105,7 @@ - + diff --git a/hmm.json b/hmm.json index 9652d9440..f45a94b08 100644 --- a/hmm.json +++ b/hmm.json @@ -65,7 +65,7 @@ "version": "2.5.0" }, { - "name": "hxcodec", + "name": "hxCodec", "type": "git", "dir": null, "ref": "c42ab99", diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 77af6b7f6..e31b6d501 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -50,6 +50,16 @@ class Conductor // OLD, replaced with timeChanges. public static var bpmChangeMap:Array = []; + /** + * Duration of a measure in milliseconds. Calculated based on bpm. + */ + public static var measureLengthMs(get, null):Float; + + static function get_measureLengthMs():Float + { + return crochet * timeSignatureNumerator; + } + /** * Duration of a beat in millisecond. Calculated based on bpm. */ diff --git a/source/funkin/CoolUtil.hx b/source/funkin/CoolUtil.hx index 7d7201029..93fa937da 100644 --- a/source/funkin/CoolUtil.hx +++ b/source/funkin/CoolUtil.hx @@ -20,13 +20,6 @@ import openfl.filters.ShaderFilter; class CoolUtil { - public static var difficultyArray:Array = ['EASY', "NORMAL", "HARD"]; - - public static function difficultyString():String - { - return difficultyArray[PlayState.storyDifficulty]; - } - public static function coolBaseLog(base:Float, fin:Float):Float { return Math.log(fin) / Math.log(base); diff --git a/source/funkin/CutsceneAnimTestState.hx b/source/funkin/CutsceneAnimTestState.hx deleted file mode 100644 index 7d6369c12..000000000 --- a/source/funkin/CutsceneAnimTestState.hx +++ /dev/null @@ -1,80 +0,0 @@ -package funkin; - -import flixel.FlxSprite; -import flixel.FlxState; -import flixel.addons.display.FlxGridOverlay; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.math.FlxPoint; -import flixel.text.FlxText; -import flixel.util.FlxColor; -import openfl.Assets; -import openfl.display.BitmapData; -import openfl.display.MovieClip; -import openfl.display.Timeline; -import openfl.geom.Matrix; -import openfl.geom.Rectangle; - -class CutsceneAnimTestState extends FlxState -{ - var cutsceneGroup:CutsceneCharacter; - - var curSelected:Int = 0; - var debugTxt:FlxText; - - var funnySprite:FlxSprite = new FlxSprite(); - var clip:MovieClip; - - public function new() - { - super(); - - var gridBG:FlxSprite = FlxGridOverlay.create(10, 10); - gridBG.scrollFactor.set(0.5, 0.5); - add(gridBG); - - debugTxt = new FlxText(900, 20, 0, "", 20); - debugTxt.color = FlxColor.BLUE; - add(debugTxt); - - clip = Assets.getMovieClip("tanky:"); - // clip.x = FlxG.width/2; - // clip.y = FlxG.height/2; - FlxG.stage.addChild(clip); - - var swagShit:MovieClip = Assets.getMovieClip('tankBG:'); - // swagShit.scaleX = 5; - - FlxG.stage.addChild(swagShit); - swagShit.gotoAndStop(13); - - var swfMountain = new BitmapData(FlxG.width, FlxG.height, true, 0x00000000); - swfMountain.draw(swagShit, swagShit.transform.matrix); - - var mountains:FlxSprite = new FlxSprite().loadGraphic(swfMountain); - // add(mountains); - - FlxG.stage.removeChild(swagShit); - - funnySprite.x = FlxG.width / 2; - funnySprite.y = FlxG.height / 2; - add(funnySprite); - } - - override function update(elapsed:Float) - { - super.update(elapsed); - - // jam sprite into top left corner - var drawMatrix:Matrix = clip.transform.matrix; - var bounds:Rectangle = clip.getBounds(null); - drawMatrix.tx = -bounds.x; - drawMatrix.ty = -bounds.y; - // make bitmapdata only as big as it needs to be - var funnyBmp:BitmapData = new BitmapData(Math.ceil(bounds.width), Math.ceil(bounds.height), true, 0x00000000); - funnyBmp.draw(clip, drawMatrix, true); - funnySprite.loadGraphic(funnyBmp); - // jam sprite back into place lol - funnySprite.offset.x = -bounds.x; - funnySprite.offset.y = -bounds.y; - } -} diff --git a/source/funkin/CutsceneCharacter.hx b/source/funkin/CutsceneCharacter.hx deleted file mode 100644 index 7df3eb91c..000000000 --- a/source/funkin/CutsceneCharacter.hx +++ /dev/null @@ -1,74 +0,0 @@ -package funkin; - -import flixel.FlxSprite; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.math.FlxPoint; - -class CutsceneCharacter extends FlxTypedGroup -{ - public var coolPos:FlxPoint = FlxPoint.get(); - public var animShit:Map = new Map(); - - var imageShit:String; - - public function new(x:Float, y:Float, imageShit:String) - { - super(); - - coolPos.set(x, y); - - this.imageShit = imageShit; - parseOffsets(); - createCutscene(0); - } - - // shitshow, oh well - var arrayLMFAOOOO:Array = []; - - function parseOffsets() - { - var splitShit:Array = CoolUtil.coolTextFile(Paths.file('images/cutsceneStuff/' + imageShit + "CutsceneOffsets.txt")); - - for (i in splitShit) - { - var xAndY:FlxPoint = FlxPoint.get(); - var dumbSplit:Array = i.split('---')[1].trim().split(' '); - trace('cool split: ' + i.split('---')[1]); - trace(dumbSplit); - xAndY.set(Std.parseFloat(dumbSplit[0]), Std.parseFloat(dumbSplit[1])); - - animShit.set(i.split('---')[0].trim(), xAndY); - arrayLMFAOOOO.push(i.split('---')[0].trim()); - } - - trace(animShit); - } - - public function createCutscene(daNum:Int = 0) - { - var cutScene:FlxSprite = new FlxSprite(coolPos.x + animShit.get(arrayLMFAOOOO[daNum]).x, coolPos.y + animShit.get(arrayLMFAOOOO[daNum]).y); - cutScene.frames = Paths.getSparrowAtlas('cutsceneStuff/' + imageShit + "-" + daNum); - cutScene.animation.addByPrefix('weed', arrayLMFAOOOO[daNum], 24, false); - cutScene.animation.play('weed'); - cutScene.antialiasing = true; - - cutScene.animation.finishCallback = function(anim:String) { - cutScene.kill(); - cutScene.destroy(); - cutScene = null; - - if (daNum + 1 < arrayLMFAOOOO.length) createCutscene(daNum + 1); - else - ended(); - }; - - add(cutScene); - } - - public var onFinish:Void->Void; - - public function ended():Void - { - if (onFinish != null) onFinish(); - } -} diff --git a/source/funkin/DialogueBox.hx b/source/funkin/DialogueBox.hx index 945c9fe96..4258f71ce 100644 --- a/source/funkin/DialogueBox.hx +++ b/source/funkin/DialogueBox.hx @@ -38,7 +38,7 @@ class DialogueBox extends FlxSpriteGroup { super(); - switch (PlayState.currentSong.song.toLowerCase()) + switch (PlayState.instance.currentSong.songId.toLowerCase()) { case 'senpai': FlxG.sound.playMusic(Paths.music('Lunchbox'), 0); @@ -79,7 +79,7 @@ class DialogueBox extends FlxSpriteGroup box = new FlxSprite(-20, 45); var hasDialog:Bool = false; - switch (PlayState.currentSong.song.toLowerCase()) + switch (PlayState.instance.currentSong.songId.toLowerCase()) { case 'senpai': hasDialog = true; @@ -151,8 +151,8 @@ class DialogueBox extends FlxSpriteGroup override function update(elapsed:Float):Void { // HARD CODING CUZ IM STUPDI - if (PlayState.currentSong.song.toLowerCase() == 'roses') portraitLeft.visible = false; - if (PlayState.currentSong.song.toLowerCase() == 'thorns') + if (PlayState.instance.currentSong.songId.toLowerCase() == 'roses') portraitLeft.visible = false; + if (PlayState.instance.currentSong.songId.toLowerCase() == 'thorns') { portraitLeft.color = FlxColor.BLACK; swagDialogue.color = FlxColor.WHITE; @@ -188,8 +188,8 @@ class DialogueBox extends FlxSpriteGroup { isEnding = true; - if (PlayState.currentSong.song.toLowerCase() == 'senpai' - || PlayState.currentSong.song.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0); + if (PlayState.instance.currentSong.songId.toLowerCase() == 'senpai' + || PlayState.instance.currentSong.songId.toLowerCase() == 'thorns') FlxG.sound.music.fadeOut(2.2, 0); new FlxTimer().start(0.2, function(tmr:FlxTimer) { box.alpha -= 1 / 5; diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index bcf6bfc18..322e79e31 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -34,12 +34,14 @@ 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; class FreeplayState extends MusicBeatSubState { - var songs:Array = []; + var songs:Array = []; // var selector:FlxText; var curSelected:Int = 0; @@ -112,15 +114,15 @@ class FreeplayState extends MusicBeatSubState #if debug isDebug = true; - addSong('Test', 1, 'bf-pixel'); - addSong('Pyro', 8, 'darnell'); + addSong('Test', 'tutorial', 'bf-pixel'); + addSong('Pyro', 'weekend1', 'darnell'); #end var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); for (i in 0...initSonglist.length) { - songs.push(new SongMetadata(initSonglist[i], 1, 'gf')); + songs.push(new FreeplaySongData(initSonglist[i], 'tutorial', 'gf')); } if (FlxG.sound.music != null) @@ -128,22 +130,28 @@ class FreeplayState extends MusicBeatSubState if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu')); } - if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 1, ['dad']); + // if (StoryMenuState.weekUnlocked[2] || isDebug) + addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 'week1', ['dad']); - if (StoryMenuState.weekUnlocked[2] || isDebug) addWeek(['Spookeez', 'South', 'Monster'], 2, ['spooky', 'spooky', 'monster']); + // if (StoryMenuState.weekUnlocked[2] || isDebug) + addWeek(['Spookeez', 'South', 'Monster'], 'week2', ['spooky', 'spooky', 'monster']); - if (StoryMenuState.weekUnlocked[3] || isDebug) addWeek(['Pico', 'Philly', 'Blammed'], 3, ['pico']); + // if (StoryMenuState.weekUnlocked[3] || isDebug) + addWeek(['Pico', 'Philly-Nice', 'Blammed'], 'week3', ['pico']); - if (StoryMenuState.weekUnlocked[4] || isDebug) addWeek(['Satin-Panties', 'High', 'Milf'], 4, ['mom']); + // if (StoryMenuState.weekUnlocked[4] || isDebug) + addWeek(['Satin-Panties', 'High', 'MILF'], 'week4', ['mom']); - if (StoryMenuState.weekUnlocked[5] || isDebug) addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 5, - ['parents-christmas', 'parents-christmas', 'monster-christmas']); + // if (StoryMenuState.weekUnlocked[5] || isDebug) + addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 'week5', ['parents-christmas', 'parents-christmas', 'monster-christmas']); - if (StoryMenuState.weekUnlocked[6] || isDebug) addWeek(['Senpai', 'Roses', 'Thorns'], 6, ['senpai', 'senpai', 'spirit']); + // if (StoryMenuState.weekUnlocked[6] || isDebug) + addWeek(['Senpai', 'Roses', 'Thorns'], 'week6', ['senpai', 'senpai', 'spirit']); - if (StoryMenuState.weekUnlocked[7] || isDebug) addWeek(['Ugh', 'Guns', 'Stress'], 7, ['tankman']); + // if (StoryMenuState.weekUnlocked[7] || isDebug) + addWeek(['Ugh', 'Guns', 'Stress'], 'week7', ['tankman']); - addWeek(["Darnell", "lit-up", "2hot", "blazin"], 8, ['darnell']); + addWeek(["Darnell", "lit-up", "2hot", "blazin"], 'weekend1', ['darnell']); // LOAD MUSIC @@ -464,7 +472,7 @@ class FreeplayState extends MusicBeatSubState grpCapsules.clear(); // var regexp:EReg = regexp; - var tempSongs:Array = songs; + var tempSongs:Array = songs; if (filterStuff != null) { @@ -553,19 +561,19 @@ class FreeplayState extends MusicBeatSubState changeDiff(); } - public function addSong(songName:String, weekNum:Int, songCharacter:String) + public function addSong(songName:String, levelId:String, songCharacter:String) { - songs.push(new SongMetadata(songName, weekNum, songCharacter)); + songs.push(new FreeplaySongData(songName, levelId, songCharacter)); } - public function addWeek(songs:Array, weekNum:Int, ?songCharacters:Array) + public function addWeek(songs:Array, levelId:String, ?songCharacters:Array) { if (songCharacters == null) songCharacters = ['bf']; var num:Int = 0; for (song in songs) { - addSong(song, weekNum, songCharacters[num]); + addSong(song, levelId, songCharacters[num]); if (songCharacters.length != 1) num++; } @@ -851,11 +859,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'; @@ -865,27 +871,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'; + } + + PlayStatePlaylist.campaignId = songs[curSelected].levelId; // 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) @@ -901,19 +921,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; }); @@ -1040,17 +1047,17 @@ enum abstract FilterType(String) var ALL; } -class SongMetadata +class FreeplaySongData { public var songName:String = ""; - public var week:Int = 0; + public var levelId:String = ""; public var songCharacter:String = ""; public var isFav:Bool = false; - public function new(song:String, week:Int, songCharacter:String, ?isFav:Bool = false) + public function new(song:String, levelId:String, songCharacter:String, ?isFav:Bool = false) { this.songName = song; - this.week = week; + this.levelId = levelId; this.songCharacter = songCharacter; this.isFav = isFav; } diff --git a/source/funkin/GitarooPause.hx b/source/funkin/GitarooPause.hx index fda809548..5747de5e5 100644 --- a/source/funkin/GitarooPause.hx +++ b/source/funkin/GitarooPause.hx @@ -11,12 +11,16 @@ class GitarooPause extends MusicBeatState var replaySelect:Bool = false; - public function new():Void + var previousParams:PlayStateParams; + + public function new(previousParams:PlayStateParams):Void { super(); + + this.previousParams = previousParams; } - override function create() + override function create():Void { if (FlxG.sound.music != null) FlxG.sound.music.stop(); @@ -49,7 +53,7 @@ class GitarooPause extends MusicBeatState super.create(); } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (controls.UI_LEFT_P || controls.UI_RIGHT_P) changeThing(); @@ -57,7 +61,7 @@ class GitarooPause extends MusicBeatState { if (replaySelect) { - FlxG.switchState(new PlayState()); + FlxG.switchState(new PlayState(previousParams)); } else { diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx index 08ad7dcba..904d2cb45 100644 --- a/source/funkin/Highscore.hx +++ b/source/funkin/Highscore.hx @@ -39,7 +39,17 @@ class Highscore return false; } - public static function saveCompletion(song:String, completion:Float, ?diff:Int = 0):Bool + public static function saveScoreForDifficulty(song:String, score:Int = 0, diff:String = 'normal'):Bool + { + var diffInt:Int = 1; + + if (diff == 'easy') diffInt = 0; + else if (diff == 'hard') diffInt = 2; + + return saveScore(song, score, diffInt); + } + + public static function saveCompletion(song:String, completion:Float, diff:Int = 0):Bool { var formattedSong:String = formatSong(song, diff); @@ -57,20 +67,42 @@ class Highscore return false; } - public static function saveWeekScore(week:Int = 1, score:Int = 0, ?diff:Int = 0):Void + public static function saveCompletionForDifficulty(song:String, completion:Float, diff:String = 'normal'):Bool + { + var diffInt:Int = 1; + + if (diff == 'easy') diffInt = 0; + else if (diff == 'hard') diffInt = 2; + + return saveCompletion(song, completion, diffInt); + } + + public static function saveWeekScore(week:String, score:Int = 0, diff:Int = 0):Void { #if newgrounds - NGio.postScore(score, "Week " + week); + NGio.postScore(score, 'Campaign ID $week'); #end - var formattedSong:String = formatSong('week' + week, diff); + var formattedSong:String = formatSong(week, diff); if (songScores.exists(formattedSong)) { if (songScores.get(formattedSong) < score) setScore(formattedSong, score); } else + { setScore(formattedSong, score); + } + } + + public static function saveWeekScoreForDifficulty(week:String, score:Int = 0, diff:String = 'normal'):Void + { + var diffInt:Int = 1; + + if (diff == 'easy') diffInt = 0; + else if (diff == 'hard') diffInt = 2; + + saveWeekScore(week, score, diffInt); } static function setCompletion(formattedSong:String, completion:Float):Void @@ -122,7 +154,7 @@ class Highscore return songCompletion.get(formatSong(song, diff)); } - public static function getAllScores() + public static function getAllScores():Void { trace(songScores.toString()); } diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 45c2645df..8d7d2d550 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -12,7 +12,7 @@ import flixel.util.FlxColor; import funkin.modding.module.ModuleHandler; import funkin.play.PlayState; import funkin.play.character.CharacterData.CharacterDataParser; -import funkin.play.event.SongEvent.SongEventParser; +import funkin.play.event.SongEventData.SongEventParser; import funkin.play.song.SongData.SongDataParser; import funkin.ui.PreferencesMenu; import funkin.util.WindowUtil; @@ -140,10 +140,10 @@ class InitState extends FlxTransitionableState // WEEK UNLOCK PROGRESSION!! // StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked; - if (StoryMenuState.weekUnlocked.length < 4) StoryMenuState.weekUnlocked.insert(0, true); + // if (StoryMenuState.weekUnlocked.length < 4) StoryMenuState.weekUnlocked.insert(0, true); // QUICK PATCH OOPS! - if (!StoryMenuState.weekUnlocked[0]) StoryMenuState.weekUnlocked[0] = true; + // if (!StoryMenuState.weekUnlocked[0]) StoryMenuState.weekUnlocked[0] = true; } if (FlxG.save.data.seenVideo != null) VideoState.seenVideo = FlxG.save.data.seenVideo; @@ -237,20 +237,18 @@ class InitState extends FlxTransitionableState { var dif:Int = getDif(); - PlayState.currentSong = SongLoad.loadFromJson(song, song); - PlayState.currentSong_NEW = SongDataParser.fetchSong(song); - PlayState.isStoryMode = isStoryMode; - PlayState.storyDifficulty = dif; - PlayState.storyDifficulty_NEW = switch (dif) + var targetDifficulty = switch (dif) { case 0: 'easy'; case 1: 'normal'; case 2: 'hard'; default: 'normal'; }; - SongLoad.curDiff = PlayState.storyDifficulty_NEW; - PlayState.storyWeek = week; - LoadingState.loadAndSwitchState(new PlayState()); + LoadingState.loadAndSwitchState(new PlayState( + { + targetSong: SongDataParser.fetchSong(song), + targetDifficulty: targetDifficulty, + })); } } diff --git a/source/funkin/LoadingState.hx b/source/funkin/LoadingState.hx index f0e56f602..604e78f79 100644 --- a/source/funkin/LoadingState.hx +++ b/source/funkin/LoadingState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.play.PlayStatePlaylist; import flixel.FlxSprite; import flixel.FlxState; import flixel.math.FlxMath; @@ -32,7 +33,7 @@ class LoadingState extends MusicBeatState this.stopMusic = stopMusic; } - override function create() + override function create():Void { var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFFcaff4d); add(bg); @@ -52,57 +53,56 @@ class LoadingState extends MusicBeatState initSongsManifest().onComplete(function(lib) { callbacks = new MultiCallback(onLoad); - var introComplete = callbacks.add("introComplete"); - checkLoadSong(getSongPath()); - if (PlayState.currentSong.needsVoices) - { - var files = PlayState.currentSong.voiceList; + var introComplete = callbacks.add('introComplete'); + // checkLoadSong(getSongPath()); + // if (PlayState.currentSong.needsVoices) + // { + // var files = PlayState.currentSong.voiceList; + // + // if (files == null) files = ['']; // loads with no file name assumption, to load 'Voices.ogg' or whatev normally + // + // for (sndFile in files) + // { + // checkLoadSong(getVocalPath(sndFile)); + // } + // } - if (files == null) files = [""]; // loads with no file name assumption, to load "Voices.ogg" or whatev normally + checkLibrary('shared'); + checkLibrary(PlayStatePlaylist.campaignId); + checkLibrary('tutorial'); - for (sndFile in files) - { - checkLoadSong(getVocalPath(sndFile)); - } - } - - checkLibrary("shared"); - if (PlayState.storyWeek > 0) checkLibrary("week" + PlayState.storyWeek); - else - checkLibrary("tutorial"); - - var fadeTime = 0.5; + var fadeTime:Float = 0.5; FlxG.camera.fade(FlxG.camera.bgColor, fadeTime, true); new FlxTimer().start(fadeTime + MIN_TIME, function(_) introComplete()); }); } - function checkLoadSong(path:String) + function checkLoadSong(path:String):Void { if (!Assets.cache.hasSound(path)) { - var library = Assets.getLibrary("songs"); - var symbolPath = path.split(":").pop(); + var library = Assets.getLibrary('songs'); + var symbolPath = path.split(':').pop(); // @:privateAccess // library.types.set(symbolPath, SOUND); // @:privateAccess // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]); - var callback = callbacks.add("song:" + path); + var callback = callbacks.add('song:' + path); Assets.loadSound(path).onComplete(function(_) { callback(); }); } } - function checkLibrary(library:String) + function checkLibrary(library:String):Void { trace(Assets.hasLibrary(library)); if (Assets.getLibrary(library) == null) { @:privateAccess - if (!LimeAssets.libraryPaths.exists(library)) throw "Missing library: " + library; + if (!LimeAssets.libraryPaths.exists(library)) throw 'Missing library: ' + library; - var callback = callbacks.add("library:" + library); + var callback = callbacks.add('library:' + library); Assets.loadLibrary(library).onComplete(function(_) { callback(); }); @@ -121,7 +121,7 @@ class LoadingState extends MusicBeatState var targetShit:Float = 0; - override function update(elapsed:Float) + override function update(elapsed:Float):Void { super.update(elapsed); @@ -147,57 +147,41 @@ class LoadingState extends MusicBeatState } #if debug - if (FlxG.keys.justPressed.SPACE) trace('fired: ' + callbacks.getFired() + " unfired:" + callbacks.getUnfired()); + if (FlxG.keys.justPressed.SPACE) trace('fired: ' + callbacks.getFired() + ' unfired:' + callbacks.getUnfired()); #end } - function onLoad() + function onLoad():Void { if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); FlxG.switchState(target); } - static function getSongPath() + static function getSongPath():String { - return Paths.inst(PlayState.currentSong.song); + return Paths.inst(PlayState.instance.currentSong.songId); } - static function getVocalPath(?suffix:String) + inline static public function loadAndSwitchState(nextState:FlxState, shouldStopMusic = false):Void { - return Paths.voices(PlayState.currentSong.song, suffix); + FlxG.switchState(getNextState(nextState, shouldStopMusic)); } - inline static public function loadAndSwitchState(target:FlxState, stopMusic = false) + static function getNextState(nextState:FlxState, shouldStopMusic = false):FlxState { - FlxG.switchState(getNextState(target, stopMusic)); - } + Paths.setCurrentLevel(PlayStatePlaylist.campaignId); - static function getNextState(target:FlxState, stopMusic = false):FlxState - { - if (PlayState.storyWeek == 0) - { - Paths.setCurrentLevel('tutorial'); - } - else if (PlayState.storyWeek == 8) - { - // TODO: Refactor this code. - Paths.setCurrentLevel("weekend1"); - } - else - { - Paths.setCurrentLevel("week" + PlayState.storyWeek); - } #if NO_PRELOAD_ALL - var loaded = isSoundLoaded(getSongPath()) - && (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath())) - && isLibraryLoaded("shared"); - - if (!loaded) return new LoadingState(target, stopMusic); + // var loaded = isSoundLoaded(getSongPath()) + // && (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath())) + // && isLibraryLoaded('shared'); + // + if (true) return new LoadingState(nextState, shouldStopMusic); #end - if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); + if (shouldStopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); - return target; + return nextState; } #if NO_PRELOAD_ALL @@ -212,16 +196,16 @@ class LoadingState extends MusicBeatState } #end - override function destroy() + override function destroy():Void { super.destroy(); callbacks = null; } - static function initSongsManifest() + static function initSongsManifest():Future { - var id = "songs"; + var id = 'songs'; var promise = new Promise(); var library = LimeAssets.getLibrary(id); @@ -243,10 +227,10 @@ class LoadingState extends MusicBeatState } else { - if (path.endsWith(".bundle")) + if (path.endsWith('.bundle')) { rootPath = path; - path += "/library.json"; + path += '/library.json'; } else { @@ -259,7 +243,7 @@ class LoadingState extends MusicBeatState AssetManifest.loadFromFile(path, rootPath).onComplete(function(manifest) { if (manifest == null) { - promise.error("Cannot parse asset manifest for library \"" + id + "\""); + promise.error('Cannot parse asset manifest for library \'' + id + '\''); return; } @@ -267,7 +251,7 @@ class LoadingState extends MusicBeatState if (library == null) { - promise.error("Cannot open library \"" + id + "\""); + promise.error('Cannot open library \'' + id + '\''); } else { @@ -277,7 +261,7 @@ class LoadingState extends MusicBeatState promise.completeWith(Future.withValue(library)); } }).onError(function(_) { - promise.error("There is no asset library with an ID of \"" + id + "\""); + promise.error('There is no asset library with an ID of \'' + id + '\''); }); return promise.future; @@ -300,7 +284,7 @@ class MultiCallback this.logId = logId; } - public function add(id = "untitled") + public function add(id = 'untitled'):Void->Void { id = '$length:$id'; length++; @@ -333,9 +317,9 @@ class MultiCallback if (logId != null) trace('$logId: $msg'); } - public function getFired() + public function getFired():Array return fired.copy(); - public function getUnfired() + public function getUnfired():ArrayVoid> return unfired.array(); } diff --git a/source/funkin/Note.hx b/source/funkin/Note.hx index 91f45e973..71c63a94e 100644 --- a/source/funkin/Note.hx +++ b/source/funkin/Note.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.play.Strumline.StrumlineArrow; import flixel.FlxSprite; import flixel.math.FlxMath; import funkin.noteStuff.NoteBasic.NoteData; @@ -215,6 +216,24 @@ class Note extends FlxSprite } } + public function alignToSturmlineArrow(arrow:StrumlineArrow):Void + { + x = arrow.x; + + if (isSustainNote && prevNote != null) + { + if (prevNote.isSustainNote) + { + x = prevNote.x; + } + else + { + x += prevNote.width / 2; + x -= width / 2; + } + } + } + override function destroy() { prevNote = null; diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx index 890b51cb7..77fdfabf1 100644 --- a/source/funkin/PauseSubState.hx +++ b/source/funkin/PauseSubState.hx @@ -1,9 +1,10 @@ package funkin; +import funkin.play.PlayStatePlaylist; import flixel.FlxSprite; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.sound.FlxSound; +import flixel.system.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; @@ -20,9 +21,9 @@ class PauseSubState extends MusicBeatSubState 'Restart Song', 'Change Difficulty', 'Toggle Practice Mode', - 'Exit to menu' + 'Exit to Menu' ]; - var difficultyChoices:Array = ['EASY', 'NORMAL', 'HARD', 'BACK']; + var difficultyChoices:Array = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK']; var menuItems:Array = []; var curSelected:Int = 0; @@ -41,10 +42,14 @@ class PauseSubState extends MusicBeatSubState menuItems = pauseOG; - if (PlayState.storyWeek == 6) // consistent with logic that decides asset lib!! + if (PlayStatePlaylist.campaignId == 'week6') + { pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true); + } else + { pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true); + } pauseMusic.volume = 0; pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); @@ -58,43 +63,38 @@ class PauseSubState extends MusicBeatSubState metaDataGrp = new FlxTypedGroup(); add(metaDataGrp); - var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32); + var levelInfo:FlxText = new FlxText(20, 15, 0, '', 32); if (PlayState.instance.currentChart != null) { levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; } - else - { - levelInfo.text += PlayState.currentSong.song; - } levelInfo.scrollFactor.set(); - levelInfo.setFormat(Paths.font("vcr.ttf"), 32); + levelInfo.setFormat(Paths.font('vcr.ttf'), 32); levelInfo.updateHitbox(); metaDataGrp.add(levelInfo); - var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, "", 32); - levelDifficulty.text += CoolUtil.difficultyString(); + var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, '', 32); + levelDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); levelDifficulty.scrollFactor.set(); levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32); levelDifficulty.updateHitbox(); metaDataGrp.add(levelDifficulty); - var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, "", 32); - deathCounter.text = "Blue balled: " + PlayState.deathCounter; - deathCounter.text += "\n" + Highscore.tallies.totalNotesHit; - deathCounter.text += "\n" + Highscore.tallies.totalNotes; - deathCounter.text += "\n" + Std.string(Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes); + var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, '', 32); + deathCounter.text = 'Blue balled: ${PlayState.instance.deathCounter}'; + FlxG.watch.addQuick('totalNotesHit', Highscore.tallies.totalNotesHit); + FlxG.watch.addQuick('totalNotes', Highscore.tallies.totalNotes); deathCounter.scrollFactor.set(); deathCounter.setFormat(Paths.font('vcr.ttf'), 32); deathCounter.updateHitbox(); metaDataGrp.add(deathCounter); - practiceText = new FlxText(20, 15 + 64 + 32, 0, "PRACTICE MODE", 32); + practiceText = new FlxText(20, 15 + 64 + 32, 0, 'PRACTICE MODE', 32); practiceText.scrollFactor.set(); practiceText.setFormat(Paths.font('vcr.ttf'), 32); practiceText.updateHitbox(); practiceText.x = FlxG.width - (practiceText.width + 20); - practiceText.visible = PlayState.isPracticeMode; + practiceText.visible = PlayState.instance.isPracticeMode; metaDataGrp.add(practiceText); levelDifficulty.alpha = 0; @@ -137,7 +137,7 @@ class PauseSubState extends MusicBeatSubState changeSelection(); } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed; @@ -180,41 +180,39 @@ class PauseSubState extends MusicBeatSubState { var daSelected:String = menuItems[curSelected]; + // TODO: Why is this based on the menu item's name? Make this an enum or something. switch (daSelected) { - case "Resume": + case 'Resume': close(); - case "EASY" | 'NORMAL' | "HARD": - PlayState.currentSong = SongLoad.loadFromJson(PlayState.currentSong.song.toLowerCase(), PlayState.currentSong.song.toLowerCase()); - PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.currentSong.song.toLowerCase()); - SongLoad.curDiff = daSelected.toLowerCase(); - - PlayState.storyDifficulty = curSelected; - PlayState.storyDifficulty_NEW = daSelected.toLowerCase(); - - PlayState.needsReset = true; - - close(); - - case 'Toggle Practice Mode': - PlayState.isPracticeMode = !PlayState.isPracticeMode; - practiceText.visible = PlayState.isPracticeMode; case 'Change Difficulty': menuItems = difficultyChoices; regenMenu(); + + case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT': + PlayState.instance.currentSong = SongDataParser.fetchSong(PlayState.instance.currentSong.songId.toLowerCase()); + + PlayState.instance.currentDifficulty = daSelected.toLowerCase(); + + PlayState.instance.needsReset = true; + + close(); case 'BACK': menuItems = pauseOG; regenMenu(); - case "Restart Song": - PlayState.needsReset = true; + case 'Toggle Practice Mode': + PlayState.instance.isPracticeMode = true; + practiceText.visible = PlayState.instance.isPracticeMode; + + case 'Restart Song': + PlayState.instance.needsReset = true; close(); - // FlxG.resetState(); - case "Exit to menu": + + case 'Exit to Menu': exitingToMenu = true; - PlayState.seenCutscene = false; - PlayState.deathCounter = 0; + PlayState.instance.deathCounter = 0; for (item in grpMenuShit.members) { @@ -225,9 +223,9 @@ class PauseSubState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; - if (PlayState.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY)); + if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY)); else - openSubState(new funkin.ui.StickerSubState()); + openSubState(new funkin.ui.StickerSubState(null, FREEPLAY)); } } @@ -239,7 +237,7 @@ class PauseSubState extends MusicBeatSubState } } - override function destroy() + override function destroy():Void { pauseMusic.destroy(); @@ -260,12 +258,10 @@ class PauseSubState extends MusicBeatSubState item.targetY = index - curSelected; item.alpha = 0.6; - // item.setGraphicSize(Std.int(item.width * 0.8)); if (item.targetY == 0) { item.alpha = 1; - // item.setGraphicSize(Std.int(item.width)); } } } diff --git a/source/funkin/StoryMenuState.hx b/source/funkin/StoryMenuState.hx deleted file mode 100644 index 89d59de1f..000000000 --- a/source/funkin/StoryMenuState.hx +++ /dev/null @@ -1,497 +0,0 @@ -package funkin; - -import flixel.FlxSprite; -import flixel.addons.transition.FlxTransitionableState; -import flixel.graphics.frames.FlxAtlasFrames; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.group.FlxGroup; -import flixel.math.FlxMath; -import flixel.text.FlxText; -import flixel.tweens.FlxTween; -import flixel.util.FlxColor; -import flixel.util.FlxTimer; -import funkin.MenuItem.WeekType; -import funkin.play.PlayState; -import funkin.play.song.SongData.SongDataParser; -import lime.net.curl.CURLCode; -import openfl.Assets; -import funkin.ui.StickerSubState; -#if discord_rpc -import Discord.DiscordClient; -#end - -class StoryMenuState extends MusicBeatState -{ - var scoreText:FlxText; - - var weekData:Array> = [ - ['Tutorial'], - ['Bopeebo', 'Fresh', 'Dadbattle'], - ['Spookeez', 'South', "Monster"], - ['Pico', 'Philly', "Blammed"], - ['Satin-Panties', "High", "Milf"], - ['Cocoa', 'Eggnog', 'Winter-Horrorland'], - ['Senpai', 'Roses', 'Thorns'], - ['Ugh', 'Guns', 'Stress'], - ['Darnell', "lit-up", "2hot", "blazin"] - ]; - var curDifficulty:Int = 1; - - // TODO: This info is just hardcoded right now. - // We should probably make it so that weeks must be completed in order to unlock the next week. - public static var weekUnlocked:Array = [true, true, true, true, true, true, true, true, true]; - - var weekCharacters:Array = [ - ['dad', 'bf', 'gf'], - ['dad', 'bf', 'gf'], - ['spooky', 'bf', 'gf'], - ['pico', 'bf', 'gf'], - ['mom', 'bf', 'gf'], - ['parents-christmas', 'bf', 'gf'], - ['senpai', 'bf', 'gf'], - ['tankman', 'bf', 'gf'], - ['darnell', 'pico', 'nene'] - ]; - - var weekNames:Array = [ - "", - "Daddy Dearest", - "Spooky Month", - "PICO", - "MOMMY MUST MURDER", - "RED SNOW", - "hating simulator ft. moawling", - "TANKMAN", - "Due Debts" - ]; - - var weekType:Array = [WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEK, WEEKEND]; - var weekTypeInc:Map = new Map(); - - var txtWeekTitle:FlxText; - - var curWeek:Int = 0; - - var txtTracklist:FlxText; - - var grpWeekText:FlxTypedGroup; - var grpWeekCharacters:Array>; - - // var grpWeekCharacters:FlxTypedGroup; - var grpLocks:FlxTypedGroup; - - var difficultySelectors:FlxGroup; - var sprDifficulty:FlxSprite; - var leftArrow:FlxSprite; - var rightArrow:FlxSprite; - var yellowBG:FlxSprite; // not actually, yellow, lol! - var targetColor:Int = 0xFFF9CF51; - - var stickerSubState:StickerSubState; - - public function new(?stickers:StickerSubState = null) - { - if (stickers != null) - { - stickerSubState = stickers; - } - - super(); - } - - override function create() - { - transIn = FlxTransitionableState.defaultTransIn; - transOut = FlxTransitionableState.defaultTransOut; - - if (FlxG.sound.music != null) - { - if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu')); - } - - if (stickerSubState != null) - { - this.persistentUpdate = true; - this.persistentDraw = true; - - openSubState(stickerSubState); - stickerSubState.degenStickers(); - - // resetSubState(); - } - - persistentUpdate = persistentDraw = true; - - scoreText = new FlxText(10, 10, 0, "SCORE: 49324858"); - scoreText.setFormat("VCR OSD Mono", 32); - - txtWeekTitle = new FlxText(FlxG.width * 0.7, 10, 0, ""); - txtWeekTitle.setFormat("VCR OSD Mono", 32, FlxColor.WHITE, RIGHT); - txtWeekTitle.alpha = 0.7; - - var rankText:FlxText = new FlxText(0, 10); - rankText.text = 'RANK: GREAT'; - rankText.setFormat(Paths.font("vcr.ttf"), 32); - rankText.size = scoreText.size; - rankText.screenCenter(X); - - var ui_tex = Paths.getSparrowAtlas('campaign_menu_UI_assets'); - yellowBG = new FlxSprite(0, 56).makeGraphic(FlxG.width, 400, FlxColor.WHITE); - yellowBG.color = 0xFFF9CF51; - // 0xFF413CAE blue - // 0xFFF9CF51 yello - - grpWeekText = new FlxTypedGroup(); - add(grpWeekText); - - var blackBarThingie:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 56, FlxColor.BLACK); - add(blackBarThingie); - - // grpWeekCharacters = new FlxTypedGroup(); - grpWeekCharacters = []; - - grpLocks = new FlxTypedGroup(); - add(grpLocks); - - #if discord_rpc - // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); - #end - - for (i in 0...weekData.length) - { - if (!weekTypeInc.exists(weekType[i])) weekTypeInc[weekType[i]] = 1; - - if (i == 0 && weekType[i] == WEEK) weekTypeInc[weekType[i]] = 0; // set week to 0 by default? - - var weekThing:MenuItem = new MenuItem(0, yellowBG.y + yellowBG.height + 10, weekTypeInc[weekType[i]], weekType[i]); - weekThing.y += ((weekThing.height + 20) * i); - weekThing.targetY = i; - grpWeekText.add(weekThing); - - weekTypeInc[weekType[i]] += 1; - - weekThing.screenCenter(X); - weekThing.antialiasing = true; - // weekThing.updateHitbox(); - - // Needs an offset thingie - if (!weekUnlocked[i]) - { - var lock:FlxSprite = new FlxSprite(weekThing.width + 10 + weekThing.x); - lock.frames = ui_tex; - lock.animation.addByPrefix('lock', 'lock'); - lock.animation.play('lock'); - lock.ID = i; - lock.antialiasing = true; - grpLocks.add(lock); - } - } - - var sizeChart:Map> = new Map(); - - var sizeTxt:Array = Assets.getText(Paths.file("data/storychardata.txt")).split("\n"); - - for (item in sizeTxt) - { - var items:Array = item.split(" "); - - var stuf:Array = []; - var name:String = items.shift(); - - for (num in items) - stuf.push(Std.parseFloat(num)); - - sizeChart.set(name, stuf); - } - - for (index => week in weekCharacters) - { - grpWeekCharacters.push(new FlxTypedGroup()); - - for (char in 0...week.length) - { - var weekCharacterThing:MenuCharacter = new MenuCharacter((FlxG.width * 0.25) * (1 + char) - 150, weekCharacters[index][char]); - weekCharacterThing.y += 70; - weekCharacterThing.antialiasing = true; - - var size:Float = 0.9; - - switch (char) - { - case 0 | 2: - size = 0.5; - if (char == 0 && weekCharacterThing.character == "pico") weekCharacterThing.flipX = true; - case 1: - size = 0.9; - weekCharacterThing.x -= 80; - } - - if (sizeChart.exists(weekCharacterThing.character)) - { - var nums:Array = sizeChart[weekCharacterThing.character]; - size = nums[char]; - - // IDK, this might be busted ass null shit? - if (char != 1) - { - weekCharacterThing.x += nums[3]; - weekCharacterThing.y += nums[4]; - } - } - - weekCharacterThing.setGraphicSize(Std.int(weekCharacterThing.width * size)); - weekCharacterThing.updateHitbox(); - - grpWeekCharacters[index].add(weekCharacterThing); - trace("ADD CHARACTER"); - } - - trace(grpWeekCharacters[index].toString()); - } - - difficultySelectors = new FlxGroup(); - add(difficultySelectors); - - leftArrow = new FlxSprite(grpWeekText.members[0].x + grpWeekText.members[0].width + 10, grpWeekText.members[0].y + 10); - leftArrow.frames = ui_tex; - leftArrow.animation.addByPrefix('idle', "arrow left"); - leftArrow.animation.addByPrefix('press', "arrow push left"); - leftArrow.animation.play('idle'); - difficultySelectors.add(leftArrow); - - sprDifficulty = new FlxSprite(leftArrow.x + 130, leftArrow.y); - sprDifficulty.frames = ui_tex; - sprDifficulty.animation.addByPrefix('easy', 'EASY'); - sprDifficulty.animation.addByPrefix('normal', 'NORMAL'); - sprDifficulty.animation.addByPrefix('hard', 'HARD'); - sprDifficulty.animation.play('easy'); - changeDifficulty(); - - difficultySelectors.add(sprDifficulty); - - rightArrow = new FlxSprite(sprDifficulty.x + sprDifficulty.width + 50, leftArrow.y); - rightArrow.frames = ui_tex; - rightArrow.animation.addByPrefix('idle', 'arrow right'); - rightArrow.animation.addByPrefix('press', "arrow push right", 24, false); - rightArrow.animation.play('idle'); - difficultySelectors.add(rightArrow); - - add(yellowBG); - for (grp in grpWeekCharacters) - { - add(grp); - // trace("ADDED GRP"); - } - - // add(grpWeekCharacters); - - txtTracklist = new FlxText(FlxG.width * 0.05, yellowBG.x + yellowBG.height + 100, 0, "Tracks", 32); - txtTracklist.alignment = CENTER; - txtTracklist.font = rankText.font; - txtTracklist.color = 0xFFe55777; - add(txtTracklist); - // add(rankText); - add(scoreText); - add(txtWeekTitle); - - updateText(); - - super.create(); - } - - override function update(elapsed:Float) - { - // scoreText.setFormat('VCR OSD Mono', 32); - - yellowBG.color = FlxColor.interpolate(yellowBG.color, targetColor, 0.06); - - lerpScore = CoolUtil.coolLerp(lerpScore, intendedScore, 0.5); - - scoreText.text = "WEEK SCORE:" + Math.round(lerpScore); - - txtWeekTitle.text = weekNames[curWeek].toUpperCase(); - txtWeekTitle.x = FlxG.width - (txtWeekTitle.width + 10); - - // FlxG.watch.addQuick('font', scoreText.font); - - difficultySelectors.visible = weekUnlocked[curWeek]; - - grpLocks.forEach(function(lock:FlxSprite) { - lock.y = grpWeekText.members[lock.ID].y; - }); - - if (!movedBack) - { - if (!selectedWeek) - { - if (controls.UI_UP_P) - { - changeWeek(-1); - } - - if (controls.UI_DOWN_P) - { - changeWeek(1); - } - - if (controls.UI_RIGHT) rightArrow.animation.play('press') - else - rightArrow.animation.play('idle'); - - if (controls.UI_LEFT) leftArrow.animation.play('press'); - else - leftArrow.animation.play('idle'); - - if (controls.UI_RIGHT_P) changeDifficulty(1); - if (controls.UI_LEFT_P) changeDifficulty(-1); - } - - if (controls.ACCEPT) - { - selectWeek(); - } - } - - if (controls.BACK && !movedBack && !selectedWeek) - { - FlxG.sound.play(Paths.sound('cancelMenu')); - movedBack = true; - FlxG.switchState(new MainMenuState()); - } - - super.update(elapsed); - } - - var movedBack:Bool = false; - var selectedWeek:Bool = false; - var stopspamming:Bool = false; - - function selectWeek() - { - if (weekUnlocked[curWeek]) - { - if (stopspamming == false) - { - FlxG.sound.play(Paths.sound('confirmMenu')); - - grpWeekText.members[curWeek].startFlashing(); - grpWeekCharacters[curWeek].members[1].animation.play('bfConfirm'); - stopspamming = true; - } - - PlayState.storyPlaylist = weekData[curWeek]; - PlayState.isStoryMode = true; - selectedWeek = true; - - PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase()); - PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase()); - PlayState.storyWeek = curWeek; - PlayState.campaignScore = 0; - - PlayState.storyDifficulty = curDifficulty; - PlayState.storyDifficulty_NEW = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: - 'normal'; - }; - SongLoad.curDiff = PlayState.storyDifficulty_NEW; - - new FlxTimer().start(1, function(tmr:FlxTimer) { - LoadingState.loadAndSwitchState(new PlayState(), true); - }); - } - } - - function changeDifficulty(change:Int = 0):Void - { - curDifficulty += change; - - if (curDifficulty < 0) curDifficulty = 2; - if (curDifficulty > 2) curDifficulty = 0; - - sprDifficulty.offset.x = 0; - - switch (curDifficulty) - { - case 0: - sprDifficulty.animation.play('easy'); - sprDifficulty.offset.x = 20; - case 1: - sprDifficulty.animation.play('normal'); - sprDifficulty.offset.x = 70; - case 2: - sprDifficulty.animation.play('hard'); - sprDifficulty.offset.x = 20; - } - - sprDifficulty.alpha = 0; - - // USING THESE WEIRD VALUES SO THAT IT DOESNT FLOAT UP - sprDifficulty.y = leftArrow.y - 15; - intendedScore = Highscore.getWeekScore(curWeek, curDifficulty); - - FlxTween.tween(sprDifficulty, {y: leftArrow.y + 15, alpha: 1}, 0.07); - } - - var lerpScore:Float = 0; - var intendedScore:Int = 0; - - function changeWeek(change:Int = 0):Void - { - curWeek += change; - - if (curWeek >= weekData.length) curWeek = 0; - if (curWeek < 0) curWeek = weekData.length - 1; - - var bullShit:Int = 0; - - for (item in grpWeekText.members) - { - item.targetY = bullShit - curWeek; - if (item.targetY == Std.int(0) && weekUnlocked[curWeek]) item.alpha = 1; - else - item.alpha = 0.6; - bullShit++; - } - - FlxG.sound.play(Paths.sound('scrollMenu')); - - updateText(); - } - - function updateText() - { - switch (weekType[curWeek]) - { - case WEEK: - targetColor = 0xFFF9CF51; - case WEEKEND: - targetColor = 0xFF413CAE; - } - - for (ind => grp in grpWeekCharacters) - grp.visible = ind == curWeek; - - txtTracklist.text = "Tracks\n"; - - var trackNames:Array = weekData[curWeek]; - for (i in trackNames) - { - txtTracklist.text += '\n${i}'; - } - - txtTracklist.text = txtTracklist.text.toUpperCase(); - - txtTracklist.screenCenter(X); - txtTracklist.x -= FlxG.width * 0.35; - - intendedScore = Highscore.getWeekScore(curWeek, curDifficulty); - } -} diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index 0af0f4edf..bc6ef571d 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -283,44 +283,6 @@ class TitleState extends MusicBeatState FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); } - /* - FlxG.watch.addQuick('cur display', FlxG.stage.window.display.id); - if (FlxG.keys.justPressed.Y) - { - // trace(FlxG.stage.window.display.name); - - if (FlxG.gamepads.firstActive != null) - { - trace(FlxG.gamepads.firstActive.model); - FlxG.gamepads.firstActive.id - } - else - trace('gamepad null'); - - // FlxG.stage.window.title = Std.string(FlxG.random.int(0, 20000)); - // FlxG.stage.window.setIcon(Image.fromFile('assets/images/icon16.png')); - // FlxG.stage.window.readPixels; - - if (FlxG.stage.window.width == Std.int(FlxG.stage.window.display.bounds.width)) - { - FlxG.stage.window.width = 1280; - FlxG.stage.window.height = 720; - FlxG.stage.window.y = 30; - } - else - { - FlxG.stage.window.width = Std.int(FlxG.stage.window.display.bounds.width); - FlxG.stage.window.height = Std.int(FlxG.stage.window.display.bounds.height); - FlxG.stage.window.x = Std.int(FlxG.stage.window.display.bounds.x); - FlxG.stage.window.y = Std.int(FlxG.stage.window.display.bounds.y); - } - } - */ - - #if debug - if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new CutsceneAnimTestState()); - #end - if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time; if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen; diff --git a/source/funkin/charting/ChartingState.hx b/source/funkin/charting/ChartingState.hx deleted file mode 100644 index d81013b50..000000000 --- a/source/funkin/charting/ChartingState.hx +++ /dev/null @@ -1,1541 +0,0 @@ -package funkin.charting; - -import flixel.addons.display.FlxGridOverlay; -import flixel.addons.transition.FlxTransitionableState; -import flixel.addons.ui.FlxInputText; -import flixel.addons.ui.FlxUI; -import flixel.addons.ui.FlxUICheckBox; -import flixel.addons.ui.FlxUIDropDownMenu; -import flixel.addons.ui.FlxUIInputText; -import flixel.addons.ui.FlxUINumericStepper; -import flixel.addons.ui.FlxUITabMenu; -import flixel.FlxSprite; -import flixel.group.FlxGroup; -import flixel.math.FlxMath; -import flixel.sound.FlxSound; -import flixel.text.FlxText; -import flixel.ui.FlxButton; -import flixel.util.FlxColor; -import funkin.audio.visualize.PolygonSpectogram; -import funkin.audiovis.ABotVis; -import funkin.audiovis.SpectogramSprite; -import funkin.Conductor.BPMChangeEvent; -import funkin.graphics.rendering.MeshRender; -import funkin.noteStuff.NoteBasic.NoteData; -import funkin.play.HealthIcon; -import funkin.play.PlayState; -import funkin.Section.SwagSection; -import funkin.SongLoad.SwagSong; -import funkin.audio.VoicesGroup; -import haxe.Json; -import lime.media.AudioBuffer; -import lime.utils.Assets; -import openfl.events.Event; -import openfl.events.IOErrorEvent; -import openfl.net.FileReference; - -using Lambda; -using flixel.util.FlxSpriteUtil; // add in "compiler save" that saves the JSON directly to the debug json using File.write() stuff on windows / sys - -class ChartingState extends MusicBeatState -{ - var _file:FileReference; - - var UI_box:FlxUITabMenu; - var sidePreview:FlxSprite; - - /** - * Array of notes showing when each section STARTS in STEPS - * Usually rounded up?? - */ - var curSection:Int = 0; - - public static var lastSection:Int = 0; - - var bpmTxt:FlxText; - - var strumLine:FlxSprite; - var curSong:String = 'Dadbattle'; - var amountSteps:Int = 0; - var bullshitUI:FlxGroup; - - var highlight:FlxSprite; - - var GRID_SIZE:Int = 40; - - var dummyArrow:FlxSprite; - - var curRenderedNotes:FlxTypedGroup; - var curRenderedSustains:FlxTypedGroup; - - var gridBG:FlxSprite; - - var _song:SwagSong; - - var typingShit:FlxInputText; - /* - * WILL BE THE CURRENT / LAST PLACED NOTE - **/ - var curSelectedNote:NoteData; - - var tempBpm:Float = 0; - - var vocals:VoicesGroup; - - var leftIcon:HealthIcon; - var rightIcon:HealthIcon; - - var audioBuf:AudioBuffer = new AudioBuffer(); - - var playheadTest:FlxSprite; - - var staticSpecGrp:FlxTypedGroup; - - override function create() - { - curSection = lastSection; - - // sys.io.File.saveContent('./bitShit.txt', "swag"); - - // trace(audioBuf.sampleRate); - - gridBG = FlxGridOverlay.create(GRID_SIZE, GRID_SIZE, GRID_SIZE * 8, GRID_SIZE * 16); - trace("GRD BG: " + gridBG.height); - add(gridBG); - - leftIcon = new HealthIcon('bf'); - rightIcon = new HealthIcon('dad'); - leftIcon.scrollFactor.set(1, 1); - rightIcon.scrollFactor.set(1, 1); - - leftIcon.setGraphicSize(0, 45); - rightIcon.setGraphicSize(0, 45); - - leftIcon.autoUpdate = false; - rightIcon.autoUpdate = false; - - add(leftIcon); - add(rightIcon); - - leftIcon.setPosition(0, -100); - rightIcon.setPosition(gridBG.width / 2, -100); - - var gridBlackLine:FlxSprite = new FlxSprite(gridBG.x + gridBG.width / 2).makeGraphic(2, Std.int(gridBG.height), FlxColor.BLACK); - add(gridBlackLine); - - curRenderedNotes = new FlxTypedGroup(); - curRenderedSustains = new FlxTypedGroup(); - - if (PlayState.currentSong != null) - { - _song = SongLoad.songData = PlayState.currentSong; - trace("LOADED A PLAYSTATE SONGFILE"); - } - else - { - _song = SongLoad.songData = SongLoad.getDefaultSwagSong(); - } - - FlxG.mouse.visible = true; - FlxG.save.bind('funkin', 'ninjamuffin99'); - - tempBpm = _song.bpm; - - addSection(); - - // sections = SongLoad.getSong(); - - updateGrid(); - - loadSong(_song.song); - // Conductor.bpm = _song.bpm; - Conductor.mapBPMChanges(_song); - - bpmTxt = new FlxText(1000, 50, 0, "", 16); - bpmTxt.scrollFactor.set(); - add(bpmTxt); - - strumLine = new FlxSprite(0, 50).makeGraphic(Std.int(GRID_SIZE * 8), 4); - add(strumLine); - - dummyArrow = new FlxSprite().makeGraphic(GRID_SIZE, GRID_SIZE, 0xFFCC2288); - dummyArrow.alpha = 0.3; - add(dummyArrow); - - var tabs = [ - {name: "Song", label: 'Song'}, - {name: "Section", label: 'Section'}, - {name: "Note", label: 'Note'} - ]; - - UI_box = new FlxUITabMenu(null, tabs, true); - - UI_box.resize(300, 400); - UI_box.x = (FlxG.width / 4) * 3; - UI_box.y = 120; - add(UI_box); - - addSongUI(); - addSectionUI(); - addNoteUI(); - - add(curRenderedNotes); - add(curRenderedSustains); - - changeSection(); - super.create(); - } - - function addSongUI():Void - { - var UI_songTitle = new FlxUIInputText(10, 10, 70, _song.song, 8); - typingShit = UI_songTitle; - - var check_voices = new FlxUICheckBox(10, 25, null, null, "Has voice track", 100); - check_voices.checked = _song.needsVoices; - // _song.needsVoices = check_voices.checked; - check_voices.callback = function() { - _song.needsVoices = check_voices.checked; - trace('CHECKED!'); - }; - - var check_mute_inst = new FlxUICheckBox(10, 200, null, null, "Mute Instrumental (in editor)", 100); - check_mute_inst.checked = false; - check_mute_inst.callback = function() { - var vol:Float = 1; - - if (check_mute_inst.checked) vol = 0; - - FlxG.sound.music.volume = vol; - }; - - var saveButton:FlxButton = new FlxButton(110, 8, "Save", function() { - saveLevel(); - }); - - var saveCompiler:FlxButton = new FlxButton(110, 30, "Save compile", function() { - saveLevel(true); - }); - - var reloadSong:FlxButton = new FlxButton(saveButton.x + saveButton.width + 10, saveButton.y, "Reload Audio", function() { - loadSong(_song.song); - }); - - var reloadSongJson:FlxButton = new FlxButton(reloadSong.x, saveButton.y + 30, "Reload JSON", function() { - FlxTransitionableState.skipNextTransIn = true; - FlxTransitionableState.skipNextTransOut = true; - loadJson(_song.song.toLowerCase()); - }); - - var loadAutosaveBtn:FlxButton = new FlxButton(reloadSongJson.x, reloadSongJson.y + 30, 'load autosave', loadAutosave); - - var stepperSpeed:FlxUINumericStepper = new FlxUINumericStepper(10, 80, 0.1, 1, 0.1, 10, 2); - stepperSpeed.value = SongLoad.getSpeed(); - // stepperSpeed.value = _song.speed[SongLoad.curDiff]; - stepperSpeed.name = 'song_speed'; - - var stepperBPM:FlxUINumericStepper = new FlxUINumericStepper(10, 65, 1, 100, 1, 999, 3); - stepperBPM.value = Conductor.bpm; - stepperBPM.name = 'song_bpm'; - - var characters:Array = CoolUtil.coolTextFile(Paths.txt('characterList')); - - var player1DropDown = new FlxUIDropDownMenu(10, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characters, true), function(character:String) { - _song.player1 = characters[Std.parseInt(character)]; - updateHeads(); - }); - player1DropDown.selectedLabel = _song.player1; - - var player2DropDown = new FlxUIDropDownMenu(140, 100, FlxUIDropDownMenu.makeStrIdLabelArray(characters, true), function(character:String) { - _song.player2 = characters[Std.parseInt(character)]; - updateHeads(); - }); - player2DropDown.selectedLabel = _song.player2; - - var difficultyDropDown = new FlxUIDropDownMenu(10, 230, FlxUIDropDownMenu.makeStrIdLabelArray(_song.difficulties, true), function(diff:String) { - SongLoad.curDiff = _song.difficulties[Std.parseInt(diff)]; - SongLoad.checkAndCreateNotemap(SongLoad.curDiff); - - while (SongLoad.getSong()[curSection] == null) - addSection(); - - updateGrid(); - }); - difficultyDropDown.selectedLabel = SongLoad.curDiff; - - var difficultyAdder = new FlxUIInputText(130, 230, 100, "", 12); - - var addDiff:FlxButton = new FlxButton(130, 250, "Add Difficulty", function() { - difficultyAdder.text = ""; - // something to regenerate difficulties - }); - - var tab_group_song = new FlxUI(null, UI_box); - tab_group_song.name = "Song"; - tab_group_song.add(UI_songTitle); - - tab_group_song.add(check_voices); - tab_group_song.add(check_mute_inst); - tab_group_song.add(saveButton); - tab_group_song.add(saveCompiler); - tab_group_song.add(reloadSong); - tab_group_song.add(reloadSongJson); - tab_group_song.add(loadAutosaveBtn); - tab_group_song.add(stepperBPM); - tab_group_song.add(stepperSpeed); - tab_group_song.add(player1DropDown); - tab_group_song.add(player2DropDown); - tab_group_song.add(difficultyDropDown); - tab_group_song.add(difficultyAdder); - tab_group_song.add(addDiff); - - UI_box.addGroup(tab_group_song); - UI_box.scrollFactor.set(); - - FlxG.camera.focusOn(gridBG.getGraphicMidpoint()); - } - - var stepperLength:FlxUINumericStepper; - var check_mustHitSection:FlxUICheckBox; - var check_changeBPM:FlxUICheckBox; - var stepperSectionBPM:FlxUINumericStepper; - var check_altAnim:FlxUICheckBox; - - function addSectionUI():Void - { - var tab_group_section = new FlxUI(null, UI_box); - tab_group_section.name = 'Section'; - - stepperLength = new FlxUINumericStepper(10, 10, 4, 0, 0, 999, 0); - stepperLength.value = SongLoad.getSong()[curSection].lengthInSteps; - stepperLength.name = "section_length"; - - stepperSectionBPM = new FlxUINumericStepper(10, 80, 1, Conductor.bpm, 1, 999, 3); - stepperSectionBPM.value = Conductor.bpm; - stepperSectionBPM.name = 'section_bpm'; - - var stepperCopy:FlxUINumericStepper = new FlxUINumericStepper(110, 130, 1, 1, -999, 999, 0); - - var copyButton:FlxButton = new FlxButton(10, 130, "Copy last section", function() { - copySection(Std.int(stepperCopy.value)); - }); - - var clearSectionButton:FlxButton = new FlxButton(10, 150, "Clear", clearSection); - - var swapSection:FlxButton = new FlxButton(10, 170, "Swap section", function() { - for (i in 0...SongLoad.getSong()[curSection].sectionNotes.length) - { - var note:Note = new Note(0, 0); - note.data = SongLoad.getSong()[curSection].sectionNotes[i]; - note.data.noteData = (note.data.noteData + 4) % 8; - SongLoad.getSong()[curSection].sectionNotes[i] = note.data; - updateGrid(); - } - }); - - check_mustHitSection = new FlxUICheckBox(10, 30, null, null, "Must hit section", 100); - check_mustHitSection.name = 'check_mustHit'; - check_mustHitSection.checked = true; - // _song.needsVoices = check_mustHit.checked; - - check_altAnim = new FlxUICheckBox(10, 400, null, null, "Alt Animation", 100); - check_altAnim.name = 'check_altAnim'; - - check_changeBPM = new FlxUICheckBox(10, 60, null, null, 'Change BPM', 100); - check_changeBPM.name = 'check_changeBPM'; - - tab_group_section.add(stepperLength); - tab_group_section.add(stepperSectionBPM); - tab_group_section.add(stepperCopy); - tab_group_section.add(check_mustHitSection); - tab_group_section.add(check_altAnim); - tab_group_section.add(check_changeBPM); - tab_group_section.add(copyButton); - tab_group_section.add(clearSectionButton); - tab_group_section.add(swapSection); - - UI_box.addGroup(tab_group_section); - } - - var stepperSusLength:FlxUINumericStepper; - var stepperPerNoteSpeed:FlxUINumericStepper; - - function addNoteUI():Void - { - var tab_group_note = new FlxUI(null, UI_box); - tab_group_note.name = 'Note'; - - stepperSusLength = new FlxUINumericStepper(10, 10, Conductor.stepCrochet / 2, 0, 0, Conductor.stepCrochet * 16); - stepperSusLength.value = 0; - stepperSusLength.name = 'note_susLength'; - - stepperPerNoteSpeed = new FlxUINumericStepper(10, 40, 0.1, 1, 0.01, 100, 2); - stepperPerNoteSpeed.value = 1; - stepperPerNoteSpeed.name = "note_PerNoteSpeed"; - - var noteSpeedName:FlxText = new FlxText(40, stepperPerNoteSpeed.y, 0, "Note Speed Multiplier"); - - var applyLength:FlxButton = new FlxButton(100, 10, 'Apply'); - - tab_group_note.add(stepperSusLength); - tab_group_note.add(stepperPerNoteSpeed); - tab_group_note.add(noteSpeedName); - tab_group_note.add(applyLength); - - UI_box.addGroup(tab_group_note); - } - - // var spec:SpectogramSprite; - - function loadSong(daSong:String):Void - { - if (FlxG.sound.music != null) - { - FlxG.sound.music.stop(); - // vocals.stop(); - } - - var pathShit = Paths.inst(daSong); - - if (!openfl.utils.Assets.cache.hasSound(pathShit)) - { - var library = Assets.getLibrary("songs"); - var symbolPath = pathShit.split(":").pop(); - // @:privateAccess - // library.types.set(symbolPath, SOUND); - // @:privateAccess - // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]); - // var callback = callbacks.add("song:" + pathShit); - openfl.utils.Assets.loadSound(pathShit).onComplete(function(_) { - // callback(); - }); - } - - FlxG.sound.playMusic(Paths.inst(daSong), 0.6); - - var musSpec:PolygonSpectogram = new PolygonSpectogram(FlxG.sound.music, FlxColor.RED, FlxG.height / 2, Math.floor(FlxG.height / 2)); - musSpec.x += 170; - musSpec.scrollFactor.set(); - musSpec.waveAmplitude = 50; - // musSpec.visType = FREQUENCIES; - add(musSpec); - - sidePreview = new FlxSprite(0, 0).makeGraphic(40, FlxG.height, FlxColor.GRAY); - sidePreview.scrollFactor.set(); - add(sidePreview); - - // trace(audioBuf.data.length); - playheadTest = new FlxSprite(0, 0).makeGraphic(30, 2, FlxColor.RED); - playheadTest.scrollFactor.set(); - playheadTest.alpha = 0.5; - add(playheadTest); - - // WONT WORK FOR TUTORIAL OR TEST SONG!!! REDO LATER - vocals = new VoicesGroup(); - // vocals = new FlxSound().loadEmbedded(Paths.voices(daSong)); - // FlxG.sound.list.add(vocals); - - staticSpecGrp = new FlxTypedGroup(); - add(staticSpecGrp); - - var aBoy:ABotVis = new ABotVis(FlxG.sound.music); - // add(aBoy); - - for (index => voc in vocals.members) - { - var vocalSpec:SpectogramSprite = new SpectogramSprite(voc, FlxG.random.color(0xFFAAAAAA, FlxColor.WHITE, 100), musSpec.daHeight, - Math.floor(FlxG.height / 2)); - vocalSpec.x = 70 - (50 * index); - // vocalSpec.visType = FREQUENCIES; - vocalSpec.daHeight = musSpec.daHeight; - vocalSpec.y = vocalSpec.daHeight; - vocalSpec.scrollFactor.set(); - add(vocalSpec); - - var staticVocal:SpectogramSprite = new SpectogramSprite(voc, FlxG.random.color(0xFFAAAAAA, FlxColor.WHITE, 100), GRID_SIZE * 16, GRID_SIZE * 8); - if (index == 0) staticVocal.x -= 150; - - if (index == 1) staticVocal.x = gridBG.width; - - staticVocal.visType = STATIC; - staticSpecGrp.add(staticVocal); - } - - FlxG.sound.music.pause(); - - vocals.pause(); - - FlxG.sound.music.onComplete = function() { - vocals.pause(); - vocals.time = 0; - FlxG.sound.music.pause(); - FlxG.sound.music.time = 0; - changeSection(); - }; - } - - function generateUI():Void - { - while (bullshitUI.members.length > 0) - { - bullshitUI.remove(bullshitUI.members[0], true); - } - - // general shit - var title:FlxText = new FlxText(UI_box.x + 20, UI_box.y + 20, 0); - bullshitUI.add(title); - /* - var loopCheck = new FlxUICheckBox(UI_box.x + 10, UI_box.y + 50, null, null, "Loops", 100, ['loop check']); - loopCheck.checked = notes[0]elected.doesLoop; - tooltips.add(loopCheck, {title: 'Section looping', body: "Whether or not it's a simon says style section", style: tooltipType}); - bullshitUI.add(loopCheck); - - */ - } - - override function getEvent(id:String, sender:Dynamic, data:Dynamic, ?params:Array) - { - if (id == FlxUICheckBox.CLICK_EVENT) - { - var check:FlxUICheckBox = cast sender; - var label = check.getLabel().text; - switch (label) - { - case 'Must hit section': - SongLoad.getSong()[curSection].mustHitSection = check.checked; - - updateHeads(); - - case 'Change BPM': - SongLoad.getSong()[curSection].changeBPM = check.checked; - FlxG.log.add('changed bpm shit'); - case "Alt Animation": - SongLoad.getSong()[curSection].altAnim = check.checked; - } - } - else if (id == FlxUINumericStepper.CHANGE_EVENT && (sender is FlxUINumericStepper)) - { - var nums:FlxUINumericStepper = cast sender; - var wname = nums.name; - FlxG.log.add(wname); - if (wname == 'section_length') - { - SongLoad.getSong()[curSection].lengthInSteps = Std.int(nums.value); - updateGrid(); - } - else if (wname == 'song_speed') - { - // _song.speed[SongLoad.curDiff] = nums.value; - _song.speed.normal = nums.value; - } - else if (wname == 'song_bpm') - { - tempBpm = nums.value; - Conductor.mapBPMChanges(_song); - Conductor.forceBPM(nums.value); - } - else if (wname == 'note_susLength') - { - curSelectedNote.sustainLength = nums.value; - updateGrid(); - } - else if (wname == 'section_bpm') - { - SongLoad.getSong()[curSection].bpm = nums.value; - updateGrid(); - } - } - - // FlxG.log.add(id + " WEED " + sender + " WEED " + data + " WEED " + params); - } - - var updatedSection:Bool = false; - - /* this function got owned LOL - function lengthBpmBullshit():Float - { - if (SongLoad.getSong()[curSection].changeBPM) - return SongLoad.getSong()[curSection].lengthInSteps * (SongLoad.getSong()[curSection].bpm / _song.bpm); - else - return SongLoad.getSong()[curSection].lengthInSteps; - }*/ - /** - * Gets the start time of section, defaults to the curSection - * @param section - * @return position of the song in... either seconds or milliseconds.... woops - */ - function sectionStartTime(?funnySection:Int):Float - { - if (funnySection == null) funnySection = curSection; - - var daBPM:Float = _song.bpm; - var daPos:Float = 0; - for (i in 0...funnySection) - { - if (SongLoad.getSong()[i].changeBPM) - { - daBPM = SongLoad.getSong()[i].bpm; - } - daPos += 4 * sectionCalc(daBPM); - } - return daPos; - } - - function measureStartTime():Float - { - var daBPM:Float = _song.bpm; - var daPos:Float = sectionStartTime(); - - daPos = Math.floor(FlxG.sound.music.time / sectionCalc(daBPM)) * sectionCalc(daBPM); - return daPos; - } - - function sectionCalc(bpm:Float) - { - return (1000 * 60 / bpm); - } - - var p1Muted:Bool = false; - var p2Muted:Bool = false; - - override function update(elapsed:Float) - { - // FlxG.camera.followLerp = CoolUtil.camLerpShit(0.05); - - FlxG.sound.music.pan = FlxMath.remapToRange(FlxG.mouse.screenX, 0, FlxG.width, -1, 1) * 10; - - // curStep = recalculateSteps(); - - Conductor.songPosition = FlxG.sound.music.time; - _song.song = typingShit.text; - - playheadTest.y = CoolUtil.coolLerp(playheadTest.y, FlxMath.remapToRange(Conductor.songPosition, 0, FlxG.sound.music.length, 0, FlxG.height), 0.5); - - var strumLinePos:Float = getYfromStrum((Conductor.songPosition - sectionStartTime()) % (Conductor.stepCrochet * SongLoad.getSong()[curSection].lengthInSteps)); - - if (FlxG.sound.music != null) - { - if (FlxG.sound.music.playing) strumLine.y = strumLinePos; - else - strumLine.y = CoolUtil.coolLerp(strumLine.y, strumLinePos, 0.5); - } - - /* if (FlxG.sound.music.playing) - { - var normalizedShitIDK:Int = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, FlxG.sound.music.length, 0, audioBuf.data.length)); - FlxG.watch.addQuick('WEIRD AUDIO SHIT LOL', audioBuf.data[normalizedShitIDK]); - // leftIcon.scale.x = FlxMath.remapToRange(audioBuf.data[normalizedShitIDK], 0, 255, 1, 2); - }*/ - - if (FlxG.keys.justPressed.X) toggleAltAnimNote(); - - if (false) // (curBeat % 4 == 0 && curStep >= 16 * (curSection + 1)) - { - // trace(curStep); - // trace((SongLoad.getSong()[curSection].lengthInSteps) * (curSection + 1)); - trace('DUMBSHIT'); - - if (SongLoad.getSong()[curSection + 1] == null) - { - addSection(); - } - - changeSection(curSection + 1, false); - } - - FlxG.watch.addQuick('daBeat', Conductor.currentBeat); - FlxG.watch.addQuick('daStep', Conductor.currentStep); - - if (FlxG.mouse.pressedMiddle && FlxG.mouse.overlaps(gridBG)) - { - if (FlxG.sound.music.playing) - { - FlxG.sound.music.pause(); - vocals.pause(); - } - - FlxG.sound.music.time = CoolUtil.coolLerp(FlxG.sound.music.time, getStrumTime(FlxG.mouse.y) + sectionStartTime(), 0.5); - vocals.time = FlxG.sound.music.time; - } - - if (FlxG.mouse.pressed) - { - if (FlxG.keys.pressed.ALT && FlxG.mouse.overlaps(gridBG)) // same shit as middle click / hold on grid - { - if (FlxG.sound.music.playing) - { - FlxG.sound.music.pause(); - vocals.pause(); - } - - FlxG.sound.music.time = getStrumTime(FlxG.mouse.y) + sectionStartTime(); - vocals.time = FlxG.sound.music.time; - } - else - { - if (FlxG.mouse.screenX <= 30 && FlxMath.inBounds(FlxG.mouse.screenY, 0, FlxG.height)) - { - if (FlxG.sound.music.playing) - { - FlxG.sound.music.pause(); - vocals.pause(); - } - - FlxG.sound.music.time = CoolUtil.coolLerp(FlxG.sound.music.time, - FlxMath.remapToRange(FlxG.mouse.screenY, 0, FlxG.height, 0, FlxG.sound.music.length), 0.5); - vocals.time = FlxG.sound.music.time; - } - if (FlxG.mouse.justPressed) - { - if (FlxG.mouse.overlaps(leftIcon)) - { - if (leftIcon.characterId == _song.player1) - { - p1Muted = !p1Muted; - leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; - } - else - { - p2Muted = !p2Muted; - - leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; - } - - vocals.members[0].volume = p1Muted ? 0 : 1; - - // null check jus in case using old shit? - if (vocals.members[1] != null) vocals.members[1].volume = p2Muted ? 0 : 1; - } - - // sloppy copypaste lol deal with it! - if (FlxG.mouse.overlaps(rightIcon)) - { - if (rightIcon.characterId == _song.player1) - { - p1Muted = !p1Muted; - rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; - } - else - { - rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; - p2Muted = !p2Muted; - } - - vocals.members[0].volume = p1Muted ? 0 : 1; - - // null check jus in case using old shit? - if (vocals.members[1] != null) vocals.members[1].volume = p2Muted ? 0 : 1; - } - - if (FlxG.mouse.overlaps(curRenderedNotes)) - { - curRenderedNotes.forEach(function(note:Note) { - if (FlxG.mouse.overlaps(note)) - { - selectNote(note); - } - }); - } - else - { - if (FlxG.mouse.overlaps(gridBG)) - { - FlxG.log.add('added note'); - addNote(); - } - } - } - } - } - - if (FlxG.mouse.pressedRight) - { - if (FlxG.mouse.overlaps(curRenderedNotes)) - { - curRenderedNotes.forEach(function(note:Note) { - if (FlxG.mouse.overlaps(note)) - { - trace('tryin to delete note...'); - deleteNote(note); - } - }); - } - } - - if (FlxG.mouse.justReleased) justPlacedNote = false; - - if (FlxG.mouse.overlaps(gridBG)) - { - if (justPlacedNote && FlxG.mouse.pressed && FlxG.mouse.y > getYfromStrum(curSelectedNote.strumTime)) - { - var minusStuff:Float = FlxG.mouse.y - getYfromStrum(curSelectedNote.strumTime); - minusStuff -= GRID_SIZE; - minusStuff += GRID_SIZE / 2; - minusStuff = Math.floor(minusStuff / GRID_SIZE) * GRID_SIZE; - minusStuff = FlxMath.remapToRange(minusStuff, 0, 40, 0, Conductor.stepCrochet); - - curSelectedNote.sustainLength = minusStuff; - - updateNoteUI(); - updateGrid(); - } - - dummyArrow.x = Math.floor(FlxG.mouse.x / GRID_SIZE) * GRID_SIZE; - if (FlxG.keys.pressed.SHIFT) dummyArrow.y = FlxG.mouse.y; - else - dummyArrow.y = Math.floor(FlxG.mouse.y / GRID_SIZE) * GRID_SIZE; - } - - if (FlxG.keys.justPressed.ENTER) - { - autosaveSong(); - - lastSection = curSection; - - PlayState.currentSong = _song; - - // JUST FOR DEBUG DARNELL STUFF, GENERALIZE THIS FOR BETTER LOADING ELSEWHERE TOO! - PlayState.storyWeek = 8; - - FlxG.sound.music.stop(); - vocals.stop(); - LoadingState.loadAndSwitchState(new PlayState()); - // FlxG.switchState(new PlayState()); - } - - if (FlxG.keys.justPressed.E) - { - changeNoteSustain(Conductor.stepCrochet); - } - if (FlxG.keys.justPressed.Q) - { - changeNoteSustain(-Conductor.stepCrochet); - } - - if (FlxG.keys.justPressed.TAB) - { - if (FlxG.keys.pressed.SHIFT) - { - UI_box.selected_tab -= 1; - if (UI_box.selected_tab < 0) UI_box.selected_tab = 2; - } - else - { - UI_box.selected_tab += 1; - if (UI_box.selected_tab >= 3) UI_box.selected_tab = 0; - } - } - - if (!typingShit.hasFocus) - { - if (FlxG.keys.justPressed.SPACE) - { - if (FlxG.sound.music.playing) - { - FlxG.sound.music.pause(); - vocals.pause(); - } - else - { - vocals.play(); - FlxG.sound.music.play(); - } - } - - if (FlxG.keys.justPressed.R) - { - if (FlxG.keys.pressed.CONTROL) resetSection(BEGINNING); - else if (FlxG.keys.pressed.SHIFT) resetSection(MEASURE); - else - resetSection(SECTION); - } - - if (FlxG.mouse.wheel != 0) - { - FlxG.sound.music.pause(); - vocals.pause(); - - var ctrlMod:Float = FlxG.keys.pressed.CONTROL ? 0.1 : 1; - var shiftMod:Float = FlxG.keys.pressed.SHIFT ? 2 : 1; - - FlxG.sound.music.time -= (FlxG.mouse.wheel * Conductor.stepCrochet * 0.4 * ctrlMod * shiftMod); - vocals.time = FlxG.sound.music.time; - } - - if (FlxG.keys.justReleased.S) - { - FlxG.sound.music.pause(); - vocals.pause(); - - #if HAS_PITCH - FlxG.sound.music.pitch = 1; - vocals.pitch = 1; - #end - } - - if (!FlxG.keys.pressed.SHIFT) - { - if (FlxG.keys.pressed.W || FlxG.keys.pressed.S) - { - var daTime:Float = 700 * elapsed; - - if (FlxG.keys.pressed.CONTROL) daTime *= 0.2; - - if (FlxG.keys.pressed.W) - { - FlxG.sound.music.pause(); - vocals.pause(); - FlxG.sound.music.time -= daTime; - vocals.time = FlxG.sound.music.time; - } - else - { - if (FlxG.keys.justPressed.S) - { - FlxG.sound.music.play(); - vocals.play(); - - #if HAS_PITCH - FlxG.sound.music.pitch = 0.5; - vocals.pitch = 0.5; - #end - } - } - // FlxG.sound.music.time += daTime; - - // vocals.time = FlxG.sound.music.time; - } - } - else - { - if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S) - { - var daTime:Float = Conductor.stepCrochet * 2; - - if (FlxG.keys.justPressed.W) - { - FlxG.sound.music.pause(); - vocals.pause(); - - FlxG.sound.music.time -= daTime; - vocals.time = FlxG.sound.music.time; - } - else - { - if (FlxG.keys.justPressed.S) - { - // FlxG.sound.music.time += daTime; - - FlxG.sound.music.pause(); - vocals.pause(); - - FlxG.sound.music.time += daTime; - vocals.time = FlxG.sound.music.time; - - // FlxG.sound.music.play(); - // vocals.play(); - - #if HAS_PITCH - FlxG.sound.music.pitch = 0.2; - vocals.pitch = 0.2; - #end - } - } - } - } - } - - _song.bpm = tempBpm; - - /* if (FlxG.keys.justPressed.UP) - Conductor.bpm = Conductor.bpm + 1; - if (FlxG.keys.justPressed.DOWN) - Conductor.bpm = Conductor.bpm - 1; */ - - var shiftThing:Int = 1; - if (FlxG.keys.pressed.SHIFT) shiftThing = 4; - if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.D) changeSection(curSection + shiftThing); - if (FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.A) changeSection(curSection - shiftThing); - - bpmTxt.text = bpmTxt.text = Std.string(FlxMath.roundDecimal(Conductor.songPosition / 1000, 3)) - + " / " - + Std.string(FlxMath.roundDecimal(FlxG.sound.music.length / 1000, 3)) - + "\nSection: " - + curSection; - super.update(elapsed); - } - - function changeNoteSustain(value:Float):Void - { - if (curSelectedNote != null) - { - curSelectedNote.sustainLength += value; - curSelectedNote.sustainLength = Math.max(curSelectedNote.sustainLength, 0); - } - - updateNoteUI(); - updateGrid(); - } - - function toggleAltAnimNote():Void - { - if (curSelectedNote != null) - { - trace('ALT NOTE SHIT'); - curSelectedNote.noteKind = (curSelectedNote.noteKind == "alt") ? "" : "alt"; - trace(curSelectedNote.noteKind); - } - } - - function recalculateSteps():Float - { - var lastChange:BPMChangeEvent = - { - stepTime: 0, - songTime: 0, - bpm: 0 - } - for (i in 0...Conductor.bpmChangeMap.length) - { - if (FlxG.sound.music.time > Conductor.bpmChangeMap[i].songTime) lastChange = Conductor.bpmChangeMap[i]; - } - - // curStep = lastChange.stepTime + Math.floor((FlxG.sound.music.time - lastChange.songTime) / Conductor.stepCrochet); - // updateBeat(); - - return Conductor.currentStep; - } - - function resetSection(songBeginning:SongResetType = SECTION):Void - { - updateGrid(); - - FlxG.sound.music.pause(); - vocals.pause(); - - switch (songBeginning) - { - case SECTION: - // Basically old shit from changeSection??? - FlxG.sound.music.time = sectionStartTime(); - case BEGINNING: - FlxG.sound.music.time = 0; - curSection = 0; - case MEASURE: - FlxG.sound.music.time = measureStartTime(); // Math.floor(FlxG.mouse.y / GRID_SIZE) * GRID_SIZE - default: - } - - vocals.time = FlxG.sound.music.time; - // updateCurStep(); - - updateGrid(); - updateSectionUI(); - } - - function changeSection(sec:Int = 0, ?updateMusic:Bool = true):Void - { - // trace('changing section' + sec); - - if (SongLoad.getSong()[sec] != null) - { - curSection = sec; - - updateGrid(); - - if (updateMusic) - { - FlxG.sound.music.pause(); - vocals.pause(); - - /*var daNum:Int = 0; - var daLength:Float = 0; - while (daNum <= sec) - { - daLength += lengthBpmBullshit(); - daNum++; - }*/ - - FlxG.sound.music.time = sectionStartTime(); - vocals.time = FlxG.sound.music.time; - // updateCurStep(); - } - - updateGrid(); - updateSectionUI(); - } - } - - function copySection(?sectionNum:Int = 1) - { - var daSec = FlxMath.maxInt(curSection, sectionNum); - - for (noteShit in SongLoad.getSong()[daSec - sectionNum].sectionNotes) - { - var strum = noteShit.strumTime + Conductor.stepCrochet * (SongLoad.getSong()[daSec].lengthInSteps * sectionNum); - - var copiedNote:Note = new Note(strum, noteShit.noteData); - copiedNote.data.sustainLength = noteShit.sustainLength; - SongLoad.getSong()[daSec].sectionNotes.push(copiedNote.data); - } - - updateGrid(); - } - - function updateSectionUI():Void - { - var sec = SongLoad.getSong()[curSection]; - - stepperLength.value = sec.lengthInSteps; - check_mustHitSection.checked = sec.mustHitSection; - check_altAnim.checked = sec.altAnim; - check_changeBPM.checked = sec.changeBPM; - stepperSectionBPM.value = sec.bpm; - - updateHeads(); - } - - function updateHeads():Void - { - if (check_mustHitSection.checked) - { - leftIcon.characterId = (_song.player1); - rightIcon.characterId = (_song.player2); - - // leftIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; - // rightIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; - // - // leftIcon.playAnimation(p1Muted ? LOSING : IDLE); - // rightIcon.playAnimation(p2Muted ? LOSING : IDLE); - } - else - { - leftIcon.characterId = (_song.player2); - rightIcon.characterId = (_song.player1); - - // leftIcon.playAnimation(p2Muted ? LOSING : IDLE); - // rightIcon.playAnimation(p1Muted ? LOSING : IDLE); - - // leftIcon.animation.curAnim.curFrame = p2Muted ? 1 : 0; - // rightIcon.animation.curAnim.curFrame = p1Muted ? 1 : 0; - } - leftIcon.setGraphicSize(0, 45); - rightIcon.setGraphicSize(0, 45); - - leftIcon.height *= 0.6; - rightIcon.height *= 0.6; - } - - function updateNoteUI():Void - { - if (curSelectedNote != null) stepperSusLength.value = curSelectedNote.sustainLength; - } - - function updateGrid():Void - { - // null if checks jus cuz i put updateGrid() in some weird places! - if (staticSpecGrp != null) - { - staticSpecGrp.forEach(function(spec) { - if (spec != null) spec.generateSection(sectionStartTime(), (Conductor.stepCrochet * 32) / 1000); - }); - } - - while (curRenderedNotes.members.length > 0) - { - curRenderedNotes.remove(curRenderedNotes.members[0], true); - } - - while (curRenderedSustains.members.length > 0) - { - curRenderedSustains.remove(curRenderedSustains.members[0], true); - } - - // generates the cool sidebar shit - if (sidePreview != null && sidePreview.active) - { - sidePreview.drawRect(0, 0, 40, FlxG.height, 0xFF444444); - - /* - var sectionsNeeded:Int = Std.int(FlxG.sound.music.length / (sectionCalc(_song.bpm) * 4)); - - while (sectionsNeeded > 0) - { - sidePreview.drawRect(0, sectionsNeeded * (FlxG.height / sectionsNeeded), 40, FlxG.height / sectionsNeeded, - (sectionsNeeded % 2 == 0 ? 0xFF000000 : 0xFFFFFFFF)); - - sectionsNeeded--; - } - */ - - for (secIndex => sideSection in SongLoad.getSong()) - { - for (notes in sideSection.sectionNotes) - { - var col:Int = Note.codeColors[notes.noteData % 4]; - - var noteFlip:Int = (sideSection.mustHitSection ? 1 : -1); - var noteX:Float = 5 * (((notes.noteData - 4) * noteFlip) + 4); - - sidePreview.drawRect(noteX, FlxMath.remapToRange(notes.strumTime, 0, FlxG.sound.music.length, 0, FlxG.height), 5, 1, col); - } - } - } - - var sectionInfo:Array = SongLoad.getSong()[curSection].sectionNotes; - - if (SongLoad.getSong()[curSection].changeBPM && SongLoad.getSong()[curSection].bpm > 0) - { - Conductor.forceBPM(SongLoad.getSong()[curSection].bpm); - FlxG.log.add('CHANGED BPM!'); - } - else - { - // get last bpm - var daBPM:Float = _song.bpm; - for (i in 0...curSection) - if (SongLoad.getSong()[i].changeBPM) daBPM = SongLoad.getSong()[i].bpm; - Conductor.forceBPM(daBPM); - } - - /* // PORT BULLSHIT, INCASE THERE'S NO SUSTAIN DATA FOR A NOTE - for (sec in 0...SongLoad.getSong().length) - { - for (notesse in 0...SongLoad.getSong()[sec].sectionNotes.length) - { - if (SongLoad.getSong()[sec].sectionNotes[notesse][2] == null) - { - trace('SUS NULL'); - SongLoad.getSong()[sec].sectionNotes[notesse][2] = 0; - } - } - } - */ - - for (i in sectionInfo) - { - var daNoteInfo = i.noteData; - var daStrumTime = i.strumTime; - var daSus = i.sustainLength; - - var note:Note = new Note(daStrumTime, daNoteInfo % 4); - note.data.sustainLength = daSus; - note.setGraphicSize(GRID_SIZE, GRID_SIZE); - note.updateHitbox(); - note.x = Math.floor(daNoteInfo * GRID_SIZE); - note.y = Math.floor(getYfromStrum((daStrumTime - sectionStartTime()) % (Conductor.stepCrochet * SongLoad.getSong()[curSection].lengthInSteps))); - - curRenderedNotes.add(note); - - if (daSus > 0) - { - var sustainVis:FlxSprite = new FlxSprite(note.x + (GRID_SIZE / 2), - note.y + GRID_SIZE).makeGraphic(8, Math.floor(FlxMath.remapToRange(daSus, 0, Conductor.stepCrochet * 16, 0, gridBG.height))); - sustainVis.x -= sustainVis.width / 2; - sustainVis.color = Note.codeColors[note.data.noteData % 4]; - curRenderedSustains.add(sustainVis); - } - } - } - - function addSection(lengthInSteps:Int = 16):Void - { - var sec:SwagSection = - { - lengthInSteps: lengthInSteps, - bpm: _song.bpm, - changeBPM: false, - mustHitSection: true, - sectionNotes: [], - typeOfSection: 0, - altAnim: false - }; - - SongLoad.getSong().push(sec); - } - - function selectNote(note:Note):Void - { - var swagNum:Int = 0; - - for (i in SongLoad.getSong()[curSection].sectionNotes) - { - if (i.strumTime == note.data.strumTime && i.noteData % 4 == note.data.noteData) - { - curSelectedNote = SongLoad.getSong()[curSection].sectionNotes[swagNum]; - } - - swagNum += 1; - } - - updateGrid(); - updateNoteUI(); - } - - function deleteNote(note:Note):Void - { - for (i in SongLoad.getSong()[curSection].sectionNotes) - { - if (i.strumTime == note.data.strumTime && i.noteData % 4 == note.data.noteData) - { - var placeIDK:Int = Std.int(((Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE)) / 40); - - placeIDK = Std.int(Math.min(placeIDK, 15)); - placeIDK = Std.int(Math.max(placeIDK, 1)); - - trace(placeIDK); - FlxG.sound.play(Paths.sound('funnyNoise/funnyNoise-0' + placeIDK)); - - FlxG.log.add('FOUND EVIL NUMBER'); - SongLoad.getSong()[curSection].sectionNotes.remove(i); - } - } - - updateGrid(); - } - - function clearSection():Void - { - SongLoad.getSong()[curSection].sectionNotes = []; - - updateGrid(); - } - - function clearSong():Void - { - for (daSection in 0...SongLoad.getSong().length) - { - SongLoad.getSong()[daSection].sectionNotes = []; - } - - updateGrid(); - } - - /** - * Is true if clicked and placed a note, set reset to false when releasing mouse button! - */ - var justPlacedNote:Bool = false; - - function addNote():Void - { - var noteStrum = getStrumTime(dummyArrow.y) + sectionStartTime(); - var noteData = Math.floor(FlxG.mouse.x / GRID_SIZE); - var noteSus = 0; - var noteKind = ""; - - justPlacedNote = true; - - // FlxG.sound.play(Paths.sound('pianoStuff/piano-00' + FlxG.random.int(1, 9)), FlxG.random.float(0.01, 0.3)); - - function makeAndPlayChord(soundsToPlay:Array) - { - var bullshit:Int = Std.int((Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE) / 40); - soundsToPlay.push('00' + Std.string((bullshit % 8) + 1)); - - for (key in soundsToPlay) - { - var snd:FlxSound = FlxG.sound.list.recycle(FlxSound).loadEmbedded(FlxG.sound.cache(Paths.sound("pianoStuff/piano-" + key))); - snd.autoDestroy = true; - FlxG.sound.list.add(snd); - snd.volume = FlxG.random.float(0.05, 0.7); - snd.pan = noteData - 2; // .... idk why tf panning doesnt work? (as of 2022/01/25) busted ass bullshit. I only went thru this fuss of creating FlxSound just for the panning! - - // snd.proximity(FlxG.mouse.x, FlxG.mouse.y, gridBG, gridBG.width / 2); - - snd.play(); - } - } - - switch (noteData) - { - case 0: - makeAndPlayChord(["015", "013", "009"]); - case 1: - makeAndPlayChord(["015", "012", "009"]); - case 2: - makeAndPlayChord(["015", "011", "009"]); - case 3: - makeAndPlayChord(["014", "011", "010"]); - } - - // trace('bullshit $bullshit'); // trace(Math.floor(dummyArrow.y / GRID_SIZE) * GRID_SIZE); - - var daNewNote:Note = new Note(noteStrum, noteData); - daNewNote.data.sustainLength = noteSus; - daNewNote.data.noteKind = noteKind; - SongLoad.getSong()[curSection].sectionNotes.push(daNewNote.data); - - curSelectedNote = SongLoad.getSong()[curSection].sectionNotes[SongLoad.getSong()[curSection].sectionNotes.length - 1]; - - if (FlxG.keys.pressed.CONTROL) - { - // SongLoad.getSong()[curSection].sectionNotes.push([noteStrum, (noteData + 4) % 8, noteSus, noteAlt]); - } - - trace(noteStrum); - trace(curSection); - - updateGrid(); - updateNoteUI(); - - autosaveSong(); - } - - function getStrumTime(yPos:Float):Float - { - return FlxMath.remapToRange(yPos, gridBG.y, gridBG.y + gridBG.height, 0, 16 * Conductor.stepCrochet); - } - - function getYfromStrum(strumTime:Float):Float - { - return FlxMath.remapToRange(strumTime, 0, 16 * Conductor.stepCrochet, gridBG.y, gridBG.y + gridBG.height); - } - - /* - function calculateSectionLengths(?sec:SwagSection):Int - { - var daLength:Int = 0; - - for (i in SongLoad.getSong()) - { - var swagLength = i.lengthInSteps; - - if (i.typeOfSection == Section.COPYCAT) - swagLength * 2; - - daLength += swagLength; - - if (sec != null && sec == i) - { - trace('swag loop??'); - break; - } - } - - return daLength; - }*/ - var daSpacing:Float = 0.3; - - function loadLevel():Void - { - trace(SongLoad.getSong()); - } - - function getNotes():Array - { - var noteData:Array = []; - - for (i in SongLoad.getSong()) - { - noteData.push(i.sectionNotes); - } - - return noteData; - } - - function loadJson(song:String):Void - { - PlayState.currentSong = SongLoad.loadFromJson(song.toLowerCase(), song.toLowerCase()); - LoadingState.loadAndSwitchState(new ChartingState()); - } - - function loadAutosave():Void - { - PlayState.currentSong = FlxG.save.data.autosave; - FlxG.resetState(); - } - - function autosaveSong():Void - { - FlxG.save.data.autosave = _song; - // trace(FlxG.save.data.autosave); - FlxG.save.flush(); - } - - function saveLevel(?debugSavepath:Bool = false) - { - // Right now the note data is saved as a Note.NoteData typedef / object or whatev - // we want to format it to an ARRAY. We turn it back into the typedef / object at the end of this function hehe - - for (key in _song.noteMap.keys()) - SongLoad.castNoteDataToArray(_song.noteMap[key]); - - // SongLoad.castNoteDataToArray(_song.notes.easy); - // SongLoad.castNoteDataToArray(_song.notes.normal); - // SongLoad.castNoteDataToArray(_song.notes.hard); - - var json = {"song": _song}; - var data:String = Json.stringify(json, null, "\t"); - - #if sys - // quick workaround, since it easier to load into hashlink, thus quicker/nicer to test? - // should get this auto-saved into a file or somethin - var filename = _song.song.toLowerCase(); - - if (debugSavepath) - { - // file path to assumingly your assets folder in your SOURCE CODE assets folder!!! - // update this later so the save button ONLY appears when you compile in debug mode! - sys.io.File.saveContent('../../../../assets/preload/data/$filename/$filename.json', data); - } - else - sys.io.File.saveContent('./$filename.json', data); - #else - if ((data != null) && (data.length > 0)) - { - _file = new FileReference(); - _file.addEventListener(Event.COMPLETE, onSaveComplete); - _file.addEventListener(Event.CANCEL, onSaveCancel); - _file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file.save(data.trim(), _song.song.toLowerCase() + ".json"); - } - #end - - for (key in _song.noteMap.keys()) - SongLoad.castArrayToNoteData(_song.noteMap[key]); - - // turn the array data back to Note.NoteData typedef - // SongLoad.castArrayToNoteData(_song.notes.easy); - // SongLoad.castArrayToNoteData(_song.notes.normal); - // SongLoad.castArrayToNoteData(_song.notes.hard); - } - - function onSaveComplete(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - FlxG.log.notice("Successfully saved LEVEL DATA."); - } - - /** - * Called when the save file dialog is cancelled. - */ - function onSaveCancel(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - } - - /** - * Called if there is an error while saving the gameplay recording. - */ - function onSaveError(_):Void - { - _file.removeEventListener(Event.COMPLETE, onSaveComplete); - _file.removeEventListener(Event.CANCEL, onSaveCancel); - _file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file = null; - FlxG.log.error("Problem saving Level data"); - } -} - -enum SongResetType -{ - BEGINNING; - MEASURE; // not sure if measure is 1/4 of a "SECTION" which is definitely a... bar.. right? its nerd shit whatever - SECTION; -} diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 44838a6e1..ef67ba64a 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -22,7 +22,7 @@ class ScriptEvent * * This event is not cancelable. */ - public static inline final CREATE:ScriptEventType = "CREATE"; + public static inline final CREATE:ScriptEventType = 'CREATE'; /** * Called when the relevant object is destroyed. @@ -30,7 +30,7 @@ class ScriptEvent * * This event is not cancelable. */ - public static inline final DESTROY:ScriptEventType = "DESTROY"; + public static inline final DESTROY:ScriptEventType = 'DESTROY'; /** * Called when the relevent object is added to the game state. @@ -46,35 +46,35 @@ class ScriptEvent * * This event is not cancelable. */ - public static inline final UPDATE:ScriptEventType = "UPDATE"; + public static inline final UPDATE:ScriptEventType = 'UPDATE'; /** * Called when the player moves to pause the game. * * This event IS cancelable! Canceling the event will prevent the game from pausing. */ - public static inline final PAUSE:ScriptEventType = "PAUSE"; + public static inline final PAUSE:ScriptEventType = 'PAUSE'; /** * Called when the player moves to unpause the game while paused. * * This event IS cancelable! Canceling the event will prevent the game from resuming. */ - public static inline final RESUME:ScriptEventType = "RESUME"; + public static inline final RESUME:ScriptEventType = 'RESUME'; /** * Called once per step in the song. This happens 4 times per measure. * * This event is not cancelable. */ - public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT"; + public static inline final SONG_BEAT_HIT:ScriptEventType = 'BEAT_HIT'; /** * Called once per step in the song. This happens 16 times per measure. * * This event is not cancelable. */ - public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT"; + public static inline final SONG_STEP_HIT:ScriptEventType = 'STEP_HIT'; /** * Called when a character hits a note. @@ -83,7 +83,7 @@ class ScriptEvent * This event IS cancelable! Canceling this event prevents the note from being hit, * and will likely result in a miss later. */ - public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT"; + public static inline final NOTE_HIT:ScriptEventType = 'NOTE_HIT'; /** * Called when a character misses a note. @@ -92,7 +92,7 @@ class ScriptEvent * This event IS cancelable! Canceling this event prevents the note from being considered missed, * avoiding a combo break and lost health. */ - public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS"; + public static inline final NOTE_MISS:ScriptEventType = 'NOTE_MISS'; /** * Called when a character presses a note when there was none there, causing them to lose health. @@ -101,7 +101,7 @@ class ScriptEvent * This event IS cancelable! Canceling this event prevents the note from being considered missed, * avoiding lost health/score and preventing the miss animation. */ - public static inline final NOTE_GHOST_MISS:ScriptEventType = "NOTE_GHOST_MISS"; + public static inline final NOTE_GHOST_MISS:ScriptEventType = 'NOTE_GHOST_MISS'; /** * Called when a song event is reached in the chart. @@ -109,21 +109,21 @@ class ScriptEvent * This event IS cancelable! Cancelling this event prevents the event from being triggered, * thus blocking its normal functionality. */ - public static inline final SONG_EVENT:ScriptEventType = "SONG_EVENT"; + public static inline final SONG_EVENT:ScriptEventType = 'SONG_EVENT'; /** * Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin. * * This event is not cancelable. */ - public static inline final SONG_START:ScriptEventType = "SONG_START"; + public static inline final SONG_START:ScriptEventType = 'SONG_START'; /** * Called when the song ends. This happens as the instrumental and vocals end. * * This event is not cancelable. */ - public static inline final SONG_END:ScriptEventType = "SONG_END"; + public static inline final SONG_END:ScriptEventType = 'SONG_END'; /** * Called when the countdown begins. This occurs before the song starts. @@ -132,7 +132,7 @@ class ScriptEvent * - The song will not start until you call Countdown.performCountdown() later. * - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it. */ - public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START"; + public static inline final COUNTDOWN_START:ScriptEventType = 'COUNTDOWN_START'; /** * Called when a step of the countdown happens. @@ -141,21 +141,21 @@ class ScriptEvent * This event IS cancelable! Canceling this event will pause the countdown. * - The countdown will not resume until you call PlayState.resumeCountdown(). */ - public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP"; + public static inline final COUNTDOWN_STEP:ScriptEventType = 'COUNTDOWN_STEP'; /** * Called when the countdown is done but just before the song starts. * * This event is not cancelable. */ - public static inline final COUNTDOWN_END:ScriptEventType = "COUNTDOWN_END"; + public static inline final COUNTDOWN_END:ScriptEventType = 'COUNTDOWN_END'; /** * Called before the game over screen triggers and the death animation plays. * * This event is not cancelable. */ - public static inline final GAME_OVER:ScriptEventType = "GAME_OVER"; + public static inline final GAME_OVER:ScriptEventType = 'GAME_OVER'; /** * Called after the player presses a key to restart the game. @@ -163,21 +163,21 @@ class ScriptEvent * * This event IS cancelable! Canceling this event will prevent the game from restarting. */ - public static inline final SONG_RETRY:ScriptEventType = "SONG_RETRY"; + public static inline final SONG_RETRY:ScriptEventType = 'SONG_RETRY'; /** * Called when the player pushes down any key on the keyboard. * * This event is not cancelable. */ - public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN"; + public static inline final KEY_DOWN:ScriptEventType = 'KEY_DOWN'; /** * Called when the player releases a key on the keyboard. * * This event is not cancelable. */ - public static inline final KEY_UP:ScriptEventType = "KEY_UP"; + public static inline final KEY_UP:ScriptEventType = 'KEY_UP'; /** * Called when the game has finished loading the notes from JSON. @@ -185,49 +185,49 @@ class ScriptEvent * * This event is not cancelable. */ - public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED"; + public static inline final SONG_LOADED:ScriptEventType = 'SONG_LOADED'; /** * Called when the game is about to switch the current FlxState. * * This event is not cancelable. */ - public static inline final STATE_CHANGE_BEGIN:ScriptEventType = "STATE_CHANGE_BEGIN"; + public static inline final STATE_CHANGE_BEGIN:ScriptEventType = 'STATE_CHANGE_BEGIN'; /** * Called when the game has finished switching the current FlxState. * * This event is not cancelable. */ - public static inline final STATE_CHANGE_END:ScriptEventType = "STATE_CHANGE_END"; + public static inline final STATE_CHANGE_END:ScriptEventType = 'STATE_CHANGE_END'; /** * Called when the game is about to open a new FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = "SUBSTATE_OPEN_BEGIN"; + public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = 'SUBSTATE_OPEN_BEGIN'; /** * Called when the game has finished opening a new FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_OPEN_END:ScriptEventType = "SUBSTATE_OPEN_END"; + public static inline final SUBSTATE_OPEN_END:ScriptEventType = 'SUBSTATE_OPEN_END'; /** * Called when the game is about to close the current FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = "SUBSTATE_CLOSE_BEGIN"; + public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = 'SUBSTATE_CLOSE_BEGIN'; /** * Called when the game has finished closing the current FlxSubState. * * This event is not cancelable. */ - public static inline final SUBSTATE_CLOSE_END:ScriptEventType = "SUBSTATE_CLOSE_END"; + public static inline final SUBSTATE_CLOSE_END:ScriptEventType = 'SUBSTATE_CLOSE_END'; /** * Called when the game is exiting the current FlxState. @@ -276,9 +276,12 @@ class ScriptEvent } } + /** + * Cancel this event. + * This is an alias for cancelEvent() but I make this typo all the time. + */ public function cancel():Void { - // This typo happens enough that I just added this. cancelEvent(); } @@ -316,11 +319,17 @@ class NoteScriptEvent extends ScriptEvent */ public var comboCount(default, null):Int; + /** + * Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`). + */ + public var playSound(default, default):Bool; + public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void { super(type, cancelable); this.note = note; this.comboCount = comboCount; + this.playSound = true; } public override function toString():String @@ -468,7 +477,7 @@ class CountdownScriptEvent extends ScriptEvent */ public var step(default, null):CountdownStep; - public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void + public function new(type:ScriptEventType, step:CountdownStep, cancelable:Bool = true):Void { super(type, cancelable); this.step = step; diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index d9e2fda4c..016b21c6c 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -37,7 +37,7 @@ class Countdown // Stop any existing countdown. stopCountdown(); - PlayState.isInCountdown = true; + PlayState.instance.isInCountdown = true; Conductor.songPosition = Conductor.crochet * -5; // Handle onBeatHit events manually @:privateAccess diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 35365beb6..140a4fbc8 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -153,11 +153,9 @@ class GameOverSubState extends MusicBeatSubState // KEYBOARD ONLY: Return to the menu when pressing the assigned key. if (controls.BACK) { - PlayState.deathCounter = 0; - PlayState.seenCutscene = false; gameOverMusic.stop(); - if (PlayState.isStoryMode) FlxG.switchState(new StoryMenuState()); + if (PlayStatePlaylist.isStoryMode) FlxG.switchState(new StoryMenuState()); else FlxG.switchState(new FreeplayState()); } @@ -171,11 +169,11 @@ class GameOverSubState extends MusicBeatSubState else { // Music hasn't started yet. - switch (PlayState.storyWeek) + switch (PlayStatePlaylist.campaignId) { // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. // This will simplify the class and make it easier for mods to add death quotes. - case 7: + case "week7": if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) { playingJeffQuote = true; @@ -214,7 +212,7 @@ class GameOverSubState extends MusicBeatSubState FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { // ...close the GameOverSubState. FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); - PlayState.needsReset = true; + PlayState.instance.needsReset = true; // Readd Boyfriend to the stage. boyfriend.isDead = false; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index cdc968609..e8cfb1402 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,17 +1,18 @@ package funkin.play; -import funkin.play.song.SongData.SongDataParser; import flixel.sound.FlxSound; +import funkin.ui.story.StoryMenuState; +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 +21,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 +42,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 +53,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 +87,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 +161,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 +233,10 @@ class PlayState extends MusicBeatState */ var inactiveNotes:Array; + /** + * The Array containing the upcoming song events. + * The `update()` function regularly shifts these out to trigger events. + */ var songEvents:Array; /** @@ -196,17 +251,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 +332,8 @@ class PlayState extends MusicBeatState */ public var camCutscene:FlxCamera; + var skipTimer:FlxPieDial; + /** * PROPERTIES */ @@ -287,24 +353,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 = []; - 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; - var talking:Bool = true; - var doof:DialogueBox; var grpNoteSplashes:FlxTypedGroup; var comboPopUps:PopUpStuff; var perfectMode:Bool = false; @@ -320,28 +371,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 +462,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 +504,8 @@ class PlayState extends MusicBeatState comboPopUps.cameras = [camHUD]; add(comboPopUps); + buildStrumlines(); + grpNoteSplashes = new FlxTypedGroup(); var noteSplash:NoteSplash = new NoteSplash(100, 100, 0); @@ -452,24 +514,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 +551,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 +587,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 +602,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 +618,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 +644,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 = 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 +685,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 +706,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 +761,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 +773,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 +799,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 +809,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 +819,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 +845,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,23 +861,14 @@ 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; trace('Playing vocals...'); + add(vocals); vocals.play(); #if discord_rpc @@ -1051,28 +876,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(); activeNotes.zIndex = 1000; add(activeNotes); @@ -1082,144 +905,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(); - 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; - - // 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 +960,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); @@ -1313,27 +983,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 +998,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 +1023,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 +1036,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 +1059,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 +1088,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 +1103,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 +1114,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 +1141,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 +1160,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 +1226,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 +1275,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 +1292,7 @@ class PlayState extends MusicBeatState inactiveNotes.shift(); } + // Iterate over active notes. if (generatedMusic && playerStrumline != null) { activeNotes.forEachAlive(function(daNote:Note) { @@ -1679,19 +1308,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 +1360,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 +1401,7 @@ class PlayState extends MusicBeatState }); } + // Query and activate song events. if (songEvents != null && songEvents.length > 0) { var songEventsToActivate:Array = SongEventParser.queryEvents(songEvents, Conductor.songPosition); @@ -1790,17 +1422,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 = [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 +1477,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 +1495,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 +1535,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 +1547,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()); - } + // TODO: Rework week unlock logic. + // StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; - // if () - 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 +1593,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 +1605,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 +1762,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array = []; for (i in 0...pressArray.length) { @@ -2083,63 +1794,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 +1821,9 @@ class PlayState extends MusicBeatState // if (pressArray.contains(true)) // { // var lol:Array = 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 +1837,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 = []; // notes that can be hit var directionList:Array = []; // directions that can be hit @@ -2222,7 +1877,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 +1924,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 +2015,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array = []; for (i in 0...pressArray.length) { @@ -2347,6 +2062,7 @@ class PlayState extends MusicBeatState controls.NOTE_UP_P, controls.NOTE_RIGHT_P ]; + var indices:Array = []; for (i in 0...pressArray.length) { @@ -2381,6 +2097,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 +2147,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 +2176,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 +2194,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) { @@ -2538,12 +2232,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 +2273,7 @@ class PlayState extends MusicBeatState add(playerStrumline); playerStrumline.cameras = [camHUD]; - if (!isStoryMode) + if (!PlayStatePlaylist.isStoryMode) { playerStrumline.fadeInArrows(); } @@ -2592,7 +2286,7 @@ class PlayState extends MusicBeatState add(enemyStrumline); enemyStrumline.cameras = [camHUD]; - if (!isStoryMode) + if (!PlayStatePlaylist.isStoryMode) { enemyStrumline.fadeInArrows(); } @@ -2604,7 +2298,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 +2324,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 +2334,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 +2365,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 +2382,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 +2394,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 +2419,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 +2445,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; } } diff --git a/source/funkin/play/PlayStatePlaylist.hx b/source/funkin/play/PlayStatePlaylist.hx new file mode 100644 index 000000000..acfd26752 --- /dev/null +++ b/source/funkin/play/PlayStatePlaylist.hx @@ -0,0 +1,56 @@ +package funkin.play; + +import funkin.util.Constants; + +/** + * Manages playback of multiple songs in a row. + * + * TODO: Add getters/setters for all these properties to validate them. + */ +class PlayStatePlaylist +{ + /** + * Whether the game is currently in Story Mode. If false, we are in Free Play Mode. + */ + public static var isStoryMode(default, default):Bool = false; + + /** + * The loist of upcoming songs to be played. + * When the user completes a song in Story Mode, the first entry in this list is played. + * When this list is empty, move to the Results screen instead. + */ + public static var playlistSongIds:Array = []; + + /** + * The cumulative score for all the songs in the playlist. + */ + public static var campaignScore:Int = 0; + + /** + * The title of this playlist, for example `Week 4` or `Weekend 1` + */ + public static var campaignTitle:String = 'UNKNOWN'; + + /** + * The internal ID of the current playlist, for example `week4` or `weekend-1`. + */ + public static var campaignId:String = 'unknown'; + + /** + * The current difficulty selected for this level (as a named ID). + */ + public static var currentDifficulty(default, default):String = Constants.DEFAULT_DIFFICULTY; + + /** + * Resets the playlist to its default state. + */ + public static function reset():Void + { + isStoryMode = false; + playlistSongIds = []; + campaignScore = 0; + campaignTitle = 'UNKNOWN'; + campaignId = 'unknown'; + currentDifficulty = Constants.DEFAULT_DIFFICULTY; + } +} diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 6b9167846..12be46fc8 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -118,16 +118,16 @@ class ResultState extends MusicBeatSubState difficulty = new FlxSprite(555); - var diffSpr:String = switch (CoolUtil.difficultyString()) + var diffSpr:String = switch (PlayState.instance.currentDifficulty) { - case "EASY": - "difEasy"; - case "NORMAL": - "difNormal"; - case "HARD": - "difHard"; + case 'EASY': + 'difEasy'; + case 'NORMAL': + 'difNormal'; + case 'HARD': + 'difHard'; case _: - "difNormal"; + 'difNormal'; } difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr)); @@ -144,7 +144,7 @@ class ResultState extends MusicBeatSubState } else { - songName.text += PlayState.currentSong.song; + songName.text += PlayState.instance.currentSong.songId; } songName.antialiasing = true; diff --git a/source/funkin/play/cutscene/VanillaCutscenes.hx b/source/funkin/play/cutscene/VanillaCutscenes.hx index a0cbd965b..a332d0795 100644 --- a/source/funkin/play/cutscene/VanillaCutscenes.hx +++ b/source/funkin/play/cutscene/VanillaCutscenes.hx @@ -1,13 +1,10 @@ package funkin.play.cutscene; -// import hxcodec.flixel.FlxVideoSprite; -// import hxcodec.flixel.FlxCutsceneState; import flixel.FlxSprite; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; -import funkin.graphics.video.FlxVideo; /** * Static methods for playing cutscenes in the PlayState. @@ -15,112 +12,46 @@ import funkin.graphics.video.FlxVideo; */ class VanillaCutscenes { - /** - * Well, well, well, what have we got here? - */ - public static function playUghCutscene():Void - { - playVideoCutscene('music/ughCutscene.mp4'); - } - - /** - * Nice bars for an ugly, boring teenager! - */ - public static function playGunsCutscene():Void - { - playVideoCutscene('music/gunsCutscene.mp4'); - } - - /** - * Don't you have a school to shoot up? - */ - public static function playStressCutscene():Void - { - playVideoCutscene('music/stressCutscene.mp4'); - } - static var blackScreen:FlxSprite; - /** - * Plays a cutscene from a video file, then starts the countdown once the video is done. - * TODO: Cutscene is currently skipped on native platforms. - */ - static function playVideoCutscene(path:String):Void - { - // Tell PlayState to stop the song until the video is done. - PlayState.isInCutscene = true; - PlayState.instance.camHUD.visible = false; - - // Display a black screen to hide the game while the video is playing. - blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); - blackScreen.scrollFactor.set(0, 0); - blackScreen.cameras = [PlayState.instance.camCutscene]; - PlayState.instance.add(blackScreen); - - #if html5 - // Video displays OVER the FlxState. - vid = new FlxVideo(path); - vid.finishCallback = finishCutscene.bind(0.5); - #else - // Video displays OVER the FlxState. - // vid = new FlxVideoSprite(0, 0); - - vid.cameras = [PlayState.instance.camCutscene]; - - PlayState.instance.add(vid); - - vid.playVideo(Paths.file(path), false); - vid.onEndReached.add(finishCutscene.bind(0.5)); - #end - } - - static var vid:#if html5 FlxVideo #else Dynamic /**FlxVideoSprite **/ #end; + static final TWEEN_DURATION:Float = 2.0; /** - * Does the cleanup to start the countdown after the video is done. - * Gets called immediately if the video can't be played. - */ - public static function finishCutscene(?transitionTime:Float = 2.5):Void - { - trace('ALERT: Finish cutscene called!'); - - #if html5 - #else - vid.stop(); - PlayState.instance.remove(vid); - #end - - PlayState.instance.camHUD.visible = true; - - FlxTween.tween(blackScreen, {alpha: 0}, transitionTime, - { - ease: FlxEase.quadInOut, - onComplete: function(twn:FlxTween) { - PlayState.instance.remove(blackScreen); - blackScreen = null; - } - }); - FlxTween.tween(FlxG.camera, {zoom: PlayState.defaultCameraZoom}, transitionTime, - { - ease: FlxEase.quadInOut, - onComplete: function(twn:FlxTween) { - PlayState.instance.startCountdown(); - } - }); - } - - /** - * FNF corruption mod??? + * Plays the cutscene that appears at the start of Winter Horrorland. + * TODO: Move this to `winter-horrorland.hxc` */ public static function playHorrorStartCutscene():Void { - PlayState.isInCutscene = true; + PlayState.instance.isInCutscene = true; PlayState.instance.camHUD.visible = false; blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); blackScreen.scrollFactor.set(0, 0); + blackScreen.zIndex = 1000000; PlayState.instance.add(blackScreen); - new FlxTimer().start(0.1, _ -> finishCutscene(2.5)); + new FlxTimer().start(0.1, function(_) { + trace('Playing horrorland cutscene...'); + PlayState.instance.remove(blackScreen); + + // Force set the camera position and zoom. + PlayState.instance.cameraFollowPoint.setPosition(400, -2050); + PlayState.instance.resetCamera(); + FlxG.camera.zoom = 2.5; + + // Play the Sound effect. + FlxG.sound.play(Paths.sound('Lights_Turn_On'), function() { + // Fade in the HUD. + trace('SFX done...'); + PlayState.instance.camHUD.visible = true; + PlayState.instance.camHUD.alpha = 0.0; // Use alpha rather than visible to let us fade it in. + FlxTween.tween(PlayState.instance.camHUD, {alpha: 1.0}, TWEEN_DURATION, {ease: FlxEase.quadInOut}); + + // Start the countdown. + trace('Zoom out done...'); + trace('Begin countdown (ends cutscene)'); + PlayState.instance.startCountdown(); + }); + }); } } diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx new file mode 100644 index 000000000..652ca0287 --- /dev/null +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -0,0 +1,155 @@ +package funkin.play.cutscene; + +import funkin.play.PlayState; +import flixel.FlxSprite; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import flixel.util.FlxTimer; +#if html5 +import funkin.graphics.video.FlxVideo; +#else +import hxcodec.flixel.FlxVideoSprite; +#end + +/** + * Assumes you are in the PlayState. + */ +class VideoCutscene +{ + static var blackScreen:FlxSprite; + + /** + * Play a video cutscene. + * TODO: Currently this is hardcoded to start the countdown after the video is done. + * @param path The path to the video file. Use Paths.file(path) to get the correct path. + */ + public static function play(filePath:String):Void + { + if (PlayState.instance == null) return; + + if (!openfl.Assets.exists(filePath)) + { + trace('ERROR: Video file does not exist: ${filePath}'); + return; + } + + // Trigger the cutscene. Don't play the song in the background. + PlayState.instance.isInCutscene = true; + PlayState.instance.camHUD.visible = false; + PlayState.instance.camCutscene.visible = true; + + // Display a black screen to hide the game while the video is playing. + blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); + blackScreen.scrollFactor.set(0, 0); + blackScreen.cameras = [PlayState.instance.camCutscene]; + PlayState.instance.add(blackScreen); + + #if html5 + playVideoHTML5(filePath); + #else + playVideoNative(filePath); + #end + } + + public static function isPlaying():Bool + { + return vid != null; + } + + #if html5 + static var vid:FlxVideo; + + static function playVideoHTML5(filePath:String):Void + { + // Video displays OVER the FlxState. + vid = new FlxVideo(filePath); + if (vid != null) + { + vid.zIndex = 0; + + vid.finishCallback = finishVideo.bind(0.5); + + vid.cameras = [PlayState.instance.camCutscene]; + + PlayState.instance.add(vid); + + PlayState.instance.refresh(); + } + else + { + trace('ALERT: Video is null! Could not play cutscene!'); + } + } + #else + static var vid:FlxVideoSprite; + + static function playVideoNative(filePath:String):Void + { + // Video displays OVER the FlxState. + vid = new FlxVideoSprite(0, 0); + + if (vid != null) + { + vid.zIndex = 0; + vid.bitmap.onEndReached.add(finishVideo.bind(0.5)); + + vid.cameras = [PlayState.instance.camCutscene]; + + PlayState.instance.add(vid); + + PlayState.instance.refresh(); + vid.play(filePath, false); + } + else + { + trace('ALERT: Video is null! Could not play cutscene!'); + } + } + #end + + public static function finishVideo(?transitionTime:Float = 0.5):Void + { + trace('ALERT: Finish video cutscene called!'); + + #if html5 + if (vid != null) + { + PlayState.instance.remove(vid); + } + #else + if (vid != null) + { + vid.stop(); + PlayState.instance.remove(vid); + } + #end + vid.destroy(); + vid = null; + + PlayState.instance.camCutscene.visible = true; + PlayState.instance.camHUD.visible = true; + + FlxTween.tween(blackScreen, {alpha: 0}, transitionTime, + { + ease: FlxEase.quadInOut, + onComplete: function(twn:FlxTween) { + PlayState.instance.remove(blackScreen); + blackScreen = null; + } + }); + FlxTween.tween(FlxG.camera, {zoom: PlayState.instance.defaultCameraZoom}, transitionTime, + { + ease: FlxEase.quadInOut, + onComplete: function(twn:FlxTween) { + PlayState.instance.startCountdown(); + } + }); + } +} + +/* + trace('Video playback failed (${e})'); + vid = null; + finishCutscene(0.5); + */ diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 45c01e739..8d677118b 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -1,7 +1,9 @@ package funkin.play.event; -import funkin.play.event.SongEvent; import funkin.play.song.SongData; +import funkin.play.event.SongEvent; +import funkin.play.event.SongEventData.SongEventFieldType; +import funkin.play.event.SongEventData.SongEventSchema; /** * This class represents a handler for a type of song event. diff --git a/source/funkin/play/event/PlayAnimationSongEvent.hx b/source/funkin/play/event/PlayAnimationSongEvent.hx index 73937e61a..a187ca285 100644 --- a/source/funkin/play/event/PlayAnimationSongEvent.hx +++ b/source/funkin/play/event/PlayAnimationSongEvent.hx @@ -3,6 +3,8 @@ package funkin.play.event; import flixel.FlxSprite; import funkin.play.character.BaseCharacter; import funkin.play.event.SongEvent; +import funkin.play.event.SongEventData.SongEventFieldType; +import funkin.play.event.SongEventData.SongEventSchema; import funkin.play.song.SongData; class PlayAnimationSongEvent extends SongEvent diff --git a/source/funkin/play/event/SetCameraBopSongEvent.hx b/source/funkin/play/event/SetCameraBopSongEvent.hx new file mode 100644 index 000000000..6f8a0645d --- /dev/null +++ b/source/funkin/play/event/SetCameraBopSongEvent.hx @@ -0,0 +1,90 @@ +package funkin.play.event; + +import funkin.util.Constants; +import flixel.tweens.FlxTween; +import flixel.FlxCamera; +import flixel.tweens.FlxEase; +import funkin.play.event.SongEvent; +import funkin.play.song.SongData; +import funkin.play.event.SongEventData; +import funkin.play.event.SongEventData.SongEventFieldType; + +/** + * This class represents a handler for configuring camera bop intensity and rate. + * + * Example: Bop the camera twice as hard, once per beat (rather than once every four beats). + * ``` + * { + * 'e': 'SetCameraBop', + * 'v': { + * 'intensity': 2.0, + * 'rate': 1, + * } + * } + * ``` + * + * Example: Reset the camera bop to default values. + * ``` + * { + * 'e': 'SetCameraBop', + * 'v': {} + * } + * ``` + */ +class SetCameraBopSongEvent extends SongEvent +{ + public function new() + { + super('SetCameraBop'); + } + + public override function handleEvent(data:SongEventData):Void + { + // Does nothing if there is no PlayState camera or stage. + if (PlayState.instance == null) return; + + var rate:Null = data.getInt('rate'); + if (rate == null) rate = Constants.DEFAULT_ZOOM_RATE; + var intensity:Null = data.getFloat('intensity'); + if (intensity == null) intensity = 1.0; + + PlayState.instance.cameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity; + PlayState.instance.hudCameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity * 2.0; + PlayState.instance.cameraZoomRate = rate; + trace('Set camera zoom rate to ${PlayState.instance.cameraZoomRate}'); + } + + public override function getTitle():String + { + return 'Set Camera Bop'; + } + + /** + * ``` + * { + * 'intensity': FLOAT, // Zoom amount + * 'rate': INT, // Zoom rate (beats/zoom) + * } + * ``` + * @return SongEventSchema + */ + public override function getEventSchema():SongEventSchema + { + return [ + { + name: 'intensity', + title: 'Intensity', + defaultValue: 1.0, + step: 0.1, + type: SongEventFieldType.FLOAT + }, + { + name: 'rate', + title: 'Rate (beats/zoom)', + defaultValue: 4, + step: 1, + type: SongEventFieldType.INTEGER, + } + ]; + } +} diff --git a/source/funkin/play/event/SongEvent.hx b/source/funkin/play/event/SongEvent.hx index 24b4d51bf..098a84e12 100644 --- a/source/funkin/play/event/SongEvent.hx +++ b/source/funkin/play/event/SongEvent.hx @@ -1,7 +1,7 @@ package funkin.play.event; -import funkin.util.macro.ClassMacro; import funkin.play.song.SongData.SongEventData; +import funkin.play.event.SongEventData.SongEventSchema; /** * This class represents a handler for a type of song event. @@ -52,232 +52,3 @@ class SongEvent return 'SongEvent(${this.id})'; } } - -/** - * This class statically handles the parsing of internal and scripted song event handlers. - */ -class SongEventParser -{ - /** - * Every built-in event class must be added to this list. - * Thankfully, with the power of `SongEventMacro`, this is done automatically. - */ - static final BUILTIN_EVENTS:List> = ClassMacro.listSubclassesOf(SongEvent); - - /** - * Map of internal handlers for song events. - * These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`. - */ - static final eventCache:Map = new Map(); - - public static function loadEventCache():Void - { - clearEventCache(); - - // - // BASE GAME EVENTS - // - registerBaseEvents(); - registerScriptedEvents(); - } - - static function registerBaseEvents() - { - trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...'); - for (eventCls in BUILTIN_EVENTS) - { - var eventClsName:String = Type.getClassName(eventCls); - if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent') continue; - - var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]); - - if (event != null) - { - trace(' Loaded built-in song event: (${event.id})'); - eventCache.set(event.id, event); - } - else - { - trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}'); - } - } - } - - static function registerScriptedEvents() - { - var scriptedEventClassNames:Array = ScriptedSongEvent.listScriptClasses(); - if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return; - - trace('Instantiating ${scriptedEventClassNames.length} scripted song events...'); - for (eventCls in scriptedEventClassNames) - { - var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN"); - - if (event != null) - { - trace(' Loaded scripted song event: ${event.id}'); - eventCache.set(event.id, event); - } - else - { - trace(' Failed to instantiate scripted song event class: ${eventCls}'); - } - } - } - - public static function listEventIds():Array - { - return eventCache.keys().array(); - } - - public static function listEvents():Array - { - return eventCache.values(); - } - - public static function getEvent(id:String):SongEvent - { - return eventCache.get(id); - } - - public static function getEventSchema(id:String):SongEventSchema - { - var event:SongEvent = getEvent(id); - if (event == null) return null; - - return event.getEventSchema(); - } - - static function clearEventCache() - { - eventCache.clear(); - } - - public static function handleEvent(data:SongEventData):Void - { - var eventType:String = data.event; - var eventHandler:SongEvent = eventCache.get(eventType); - - if (eventHandler != null) - { - eventHandler.handleEvent(data); - } - else - { - trace('WARNING: No event handler for event with id: ${eventType}'); - } - - data.activated = true; - } - - public static inline function handleEvents(events:Array):Void - { - for (event in events) - { - handleEvent(event); - } - } - - /** - * Given a list of song events and the current timestamp, - * return a list of events that should be handled. - */ - public static function queryEvents(events:Array, currentTime:Float):Array - { - return events.filter(function(event:SongEventData):Bool { - // If the event is already activated, don't activate it again. - if (event.activated) return false; - - // If the event is in the future, don't activate it. - if (event.time > currentTime) return false; - - return true; - }); - } - - /** - * Reset activation of all the provided events. - */ - public static function resetEvents(events:Array):Void - { - for (event in events) - { - event.activated = false; - // TODO: Add an onReset() method to SongEvent? - } - } -} - -enum abstract SongEventFieldType(String) from String to String -{ - /** - * The STRING type will display as a text field. - */ - var STRING = "string"; - - /** - * The INTEGER type will display as a text field that only accepts numbers. - */ - var INTEGER = "integer"; - - /** - * The FLOAT type will display as a text field that only accepts numbers. - */ - var FLOAT = "float"; - - /** - * The BOOL type will display as a checkbox. - */ - var BOOL = "bool"; - - /** - * The ENUM type will display as a dropdown. - * Make sure to specify the `keys` field in the schema. - */ - var ENUM = "enum"; -} - -typedef SongEventSchemaField = -{ - /** - * The name of the property as it should be saved in the event data. - */ - name:String, - - /** - * The title of the field to display in the UI. - */ - title:String, - - /** - * The type of the field. - */ - type:SongEventFieldType, - - /** - * Used for ENUM values. - * The key is the display name and the value is the actual value. - */ - ?keys:Map, - /** - * Used for INTEGER and FLOAT values. - * The minimum value that can be entered. - */ - ?min:Float, - /** - * Used for INTEGER and FLOAT values. - * The maximum value that can be entered. - */ - ?max:Float, - /** - * Used for INTEGER and FLOAT values. - * The step value that will be used when incrementing/decrementing the value. - */ - ?step:Float, - /** - * An optional default value for the field. - */ - ?defaultValue:Dynamic, -} - -typedef SongEventSchema = Array; diff --git a/source/funkin/play/event/SongEventData.hx b/source/funkin/play/event/SongEventData.hx new file mode 100644 index 000000000..8c157b52a --- /dev/null +++ b/source/funkin/play/event/SongEventData.hx @@ -0,0 +1,235 @@ +package funkin.play.event; + +import funkin.play.event.SongEventData.SongEventSchema; +import funkin.play.song.SongData.SongEventData; +import funkin.util.macro.ClassMacro; +import funkin.play.event.ScriptedSongEvent; + +/** + * This class statically handles the parsing of internal and scripted song event handlers. + */ +class SongEventParser +{ + /** + * Every built-in event class must be added to this list. + * Thankfully, with the power of `SongEventMacro`, this is done automatically. + */ + static final BUILTIN_EVENTS:List> = ClassMacro.listSubclassesOf(SongEvent); + + /** + * Map of internal handlers for song events. + * These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`. + */ + static final eventCache:Map = new Map(); + + public static function loadEventCache():Void + { + clearEventCache(); + + // + // BASE GAME EVENTS + // + registerBaseEvents(); + registerScriptedEvents(); + } + + static function registerBaseEvents() + { + trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...'); + for (eventCls in BUILTIN_EVENTS) + { + var eventClsName:String = Type.getClassName(eventCls); + if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent') continue; + + var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]); + + if (event != null) + { + trace(' Loaded built-in song event: (${event.id})'); + eventCache.set(event.id, event); + } + else + { + trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}'); + } + } + } + + static function registerScriptedEvents() + { + var scriptedEventClassNames:Array = ScriptedSongEvent.listScriptClasses(); + if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return; + + trace('Instantiating ${scriptedEventClassNames.length} scripted song events...'); + for (eventCls in scriptedEventClassNames) + { + var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN"); + + if (event != null) + { + trace(' Loaded scripted song event: ${event.id}'); + eventCache.set(event.id, event); + } + else + { + trace(' Failed to instantiate scripted song event class: ${eventCls}'); + } + } + } + + public static function listEventIds():Array + { + return eventCache.keys().array(); + } + + public static function listEvents():Array + { + return eventCache.values(); + } + + public static function getEvent(id:String):SongEvent + { + return eventCache.get(id); + } + + public static function getEventSchema(id:String):SongEventSchema + { + var event:SongEvent = getEvent(id); + if (event == null) return null; + + return event.getEventSchema(); + } + + static function clearEventCache() + { + eventCache.clear(); + } + + public static function handleEvent(data:SongEventData):Void + { + var eventType:String = data.event; + var eventHandler:SongEvent = eventCache.get(eventType); + + if (eventHandler != null) + { + eventHandler.handleEvent(data); + } + else + { + trace('WARNING: No event handler for event with id: ${eventType}'); + } + + data.activated = true; + } + + public static inline function handleEvents(events:Array):Void + { + for (event in events) + { + handleEvent(event); + } + } + + /** + * Given a list of song events and the current timestamp, + * return a list of events that should be handled. + */ + public static function queryEvents(events:Array, currentTime:Float):Array + { + return events.filter(function(event:SongEventData):Bool { + // If the event is already activated, don't activate it again. + if (event.activated) return false; + + // If the event is in the future, don't activate it. + if (event.time > currentTime) return false; + + return true; + }); + } + + /** + * Reset activation of all the provided events. + */ + public static function resetEvents(events:Array):Void + { + for (event in events) + { + event.activated = false; + // TODO: Add an onReset() method to SongEvent? + } + } +} + +enum abstract SongEventFieldType(String) from String to String +{ + /** + * The STRING type will display as a text field. + */ + var STRING = "string"; + + /** + * The INTEGER type will display as a text field that only accepts numbers. + */ + var INTEGER = "integer"; + + /** + * The FLOAT type will display as a text field that only accepts numbers. + */ + var FLOAT = "float"; + + /** + * The BOOL type will display as a checkbox. + */ + var BOOL = "bool"; + + /** + * The ENUM type will display as a dropdown. + * Make sure to specify the `keys` field in the schema. + */ + var ENUM = "enum"; +} + +typedef SongEventSchemaField = +{ + /** + * The name of the property as it should be saved in the event data. + */ + name:String, + + /** + * The title of the field to display in the UI. + */ + title:String, + + /** + * The type of the field. + */ + type:SongEventFieldType, + + /** + * Used for ENUM values. + * The key is the display name and the value is the actual value. + */ + ?keys:Map, + /** + * Used for INTEGER and FLOAT values. + * The minimum value that can be entered. + */ + ?min:Float, + /** + * Used for INTEGER and FLOAT values. + * The maximum value that can be entered. + */ + ?max:Float, + /** + * Used for INTEGER and FLOAT values. + * The step value that will be used when incrementing/decrementing the value. + */ + ?step:Float, + /** + * An optional default value for the field. + */ + ?defaultValue:Dynamic, +} + +typedef SongEventSchema = Array; diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx new file mode 100644 index 000000000..e6e9c843d --- /dev/null +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -0,0 +1,147 @@ +package funkin.play.event; + +import flixel.tweens.FlxTween; +import flixel.FlxCamera; +import flixel.tweens.FlxEase; +import funkin.play.event.SongEvent; +import funkin.play.song.SongData; +import funkin.play.event.SongEventData; +import funkin.play.event.SongEventData.SongEventFieldType; + +/** + * This class represents a handler for camera zoom events. + * + * Example: Zoom to 1.3x: + * ``` + * { + * 'e': 'ZoomCamera', + * 'v': 1.3 + * } + * ``` + * + * Example: Zoom to 1.3x + * ``` + * { + * 'e': 'FocusCamera', + * 'v': { + * 'char': 2, + * 'y': -10, + * } + * } + * ``` + * + * Example: Focus on (100, 100): + * ``` + * { + * 'e': 'FocusCamera', + * 'v': { + * 'char': -1, + * 'x': 100, + * 'y': 100, + * } + * } + * ``` + */ +class ZoomCameraSongEvent extends SongEvent +{ + public function new() + { + super('ZoomCamera'); + } + + public override function handleEvent(data:SongEventData):Void + { + // Does nothing if there is no PlayState camera or stage. + if (PlayState.instance == null) return; + + var zoom:Null = data.getFloat('zoom'); + if (zoom == null) zoom = 1.0; + var duration:Null = data.getFloat('duration'); + if (duration == null) duration = 4.0; + + var ease:Null = data.getString('ease'); + if (ease == null) ease = 'linear'; + + // If it's a string, check the value. + switch (ease) + { + case 'INSTANT': + // Set the zoom. Use defaultCameraZoom to prevent breaking camera bops. + PlayState.instance.defaultCameraZoom = zoom * FlxCamera.defaultZoom; + default: + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); + if (easeFunction == null) + { + trace('Invalid ease function: $ease'); + return; + } + + FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepCrochet * duration / 1000), {ease: easeFunction}); + } + } + + public override function getTitle():String + { + return 'Zoom Camera'; + } + + /** + * ``` + * { + * 'zoom': FLOAT, // Target zoom level. + * 'duration': FLOAT, // Optional duration in steps + * 'ease': ENUM, // Optional easing function + * } + * @return SongEventSchema + */ + public override function getEventSchema():SongEventSchema + { + return [ + { + name: 'zoom', + title: 'Zoom Level', + defaultValue: 1.0, + step: 0.1, + type: SongEventFieldType.FLOAT + }, + { + name: 'duration', + title: 'Duration (in steps)', + defaultValue: 4.0, + step: 0.5, + type: SongEventFieldType.FLOAT, + }, + { + name: 'ease', + title: 'Easing Type', + defaultValue: 'linear', + type: SongEventFieldType.ENUM, + keys: [ + 'Linear' => 'linear', + 'Instant' => 'INSTANT', + 'Quad In' => 'quadIn', + 'Quad Out' => 'quadOut', + 'Quad In/Out' => 'quadInOut', + 'Cube In' => 'cubeIn', + 'Cube Out' => 'cubeOut', + 'Cube In/Out' => 'cubeInOut', + 'Quart In' => 'quartIn', + 'Quart Out' => 'quartOut', + 'Quart In/Out' => 'quartInOut', + 'Quint In' => 'quintIn', + 'Quint Out' => 'quintOut', + 'Quint In/Out' => 'quintInOut', + 'Smooth Step In' => 'smoothStepIn', + 'Smooth Step Out' => 'smoothStepOut', + 'Smooth Step In/Out' => 'smoothStepInOut', + 'Sine In' => 'sineIn', + 'Sine Out' => 'sineOut', + 'Sine In/Out' => 'sineInOut', + 'Elastic In' => 'elasticIn', + 'Elastic Out' => 'elasticOut', + 'Elastic In/Out' => 'elasticInOut', + ] + } + ]; + } +} diff --git a/source/funkin/ui/StickerSubState.hx b/source/funkin/ui/StickerSubState.hx index 333d4bf57..981c79dfa 100644 --- a/source/funkin/ui/StickerSubState.hx +++ b/source/funkin/ui/StickerSubState.hx @@ -36,7 +36,8 @@ class StickerSubState extends MusicBeatSubState add(grpStickers); // makes the stickers on the most recent camera, which is more often than not... a UI camera!! - grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + // grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + grpStickers.cameras = FlxG.cameras.list; if (oldStickers != null) { @@ -208,8 +209,10 @@ class StickerSubState extends MusicBeatSubState FlxG.switchState(new FreeplayState(this)); case STORY: FlxG.switchState(new StoryMenuState(this)); + case MAIN_MENU: + FlxG.switchState(new MainMenuState()); default: - FlxG.switchState(new FreeplayState(this)); + FlxG.switchState(new MainMenuState()); } } @@ -354,6 +357,7 @@ typedef StickerShit = enum abstract NEXTSTATE(String) { + var MAIN_MENU = 'mainmenu'; var FREEPLAY = 'freeplay'; var STORY = 'story'; } diff --git a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx index 359cbb56e..5a903481e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx @@ -3,6 +3,7 @@ package funkin.ui.debug.charting; import haxe.ui.data.ArrayDataSource; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.event.SongEvent; +import funkin.play.event.SongEventData; import funkin.play.song.SongData.SongTimeChange; import funkin.play.song.SongSerializer; import funkin.ui.haxeui.components.CharacterPlayer; diff --git a/source/funkin/ui/stageBuildShit/StageEditorCommand.hx b/source/funkin/ui/stageBuildShit/StageEditorCommand.hx index 1e19914da..3248d16d8 100644 --- a/source/funkin/ui/stageBuildShit/StageEditorCommand.hx +++ b/source/funkin/ui/stageBuildShit/StageEditorCommand.hx @@ -1,5 +1,6 @@ package funkin.ui.stageBuildShit; +import funkin.ui.stageBuildShit.StageOffsetSubState; import flixel.FlxSprite; /** diff --git a/source/funkin/ui/stageBuildShit/StageOffsetSubState.hx b/source/funkin/ui/stageBuildShit/StageOffsetSubState.hx index 4a36bb21d..dff0cca6b 100644 --- a/source/funkin/ui/stageBuildShit/StageOffsetSubState.hx +++ b/source/funkin/ui/stageBuildShit/StageOffsetSubState.hx @@ -260,7 +260,7 @@ class StageOffsetSubState extends HaxeUISubState // if (uiStuff != null) remove(uiStuff); // uiStuff = null; - PlayState.disableKeys = false; + PlayState.instance.disableKeys = false; PlayState.instance.resetCamera(); FlxG.mouse.visible = false; close(); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 18c791995..b61f1bdee 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -13,6 +13,8 @@ import funkin.data.level.LevelRegistry; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.PlayState; +import funkin.play.PlayStatePlaylist; +import funkin.play.song.Song; import funkin.play.song.SongData.SongDataParser; import funkin.util.Constants; @@ -112,8 +114,8 @@ class StoryMenuState extends MusicBeatState { FlxG.sound.playMusic(Paths.music('freakyMenu')); FlxG.sound.music.fadeIn(4, 0, 0.7); - Conductor.forceBPM(Constants.FREAKY_MENU_BPM); } + Conductor.forceBPM(Constants.FREAKY_MENU_BPM); if (stickerSubState != null) { @@ -474,24 +476,25 @@ class StoryMenuState extends MusicBeatState prop.playConfirm(); } - PlayState.storyPlaylist = currentLevel.getSongs(); - PlayState.isStoryMode = true; + Paths.setCurrentLevel(currentLevel.id); - PlayState.currentSong = SongLoad.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.storyPlaylist[0].toLowerCase()); - PlayState.currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase()); + PlayStatePlaylist.playlistSongIds = currentLevel.getSongs(); + PlayStatePlaylist.isStoryMode = true; + PlayStatePlaylist.campaignScore = 0; - // TODO: Fix this. - PlayState.storyWeek = 0; - PlayState.campaignScore = 0; + var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift(); - // TODO: Fix this. - PlayState.storyDifficulty = 0; - PlayState.storyDifficulty_NEW = currentDifficultyId; + var targetSong:Song = SongDataParser.fetchSong(targetSongId); - SongLoad.curDiff = PlayState.storyDifficulty_NEW; + PlayStatePlaylist.campaignId = currentLevel.id; + PlayStatePlaylist.campaignTitle = currentLevel.getTitle(); new FlxTimer().start(1, function(tmr:FlxTimer) { - LoadingState.loadAndSwitchState(new PlayState(), true); + LoadingState.loadAndSwitchState(new PlayState( + { + targetSong: targetSong, + targetDifficulty: currentDifficultyId, + }), true); }); } diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 4379fe48e..c1bac76c4 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -14,7 +14,7 @@ class Constants * The title of the game, for debug printing purposes. * Change this if you're making an engine. */ - public static final TITLE = "Friday Night Funkin'"; + public static final TITLE:String = "Friday Night Funkin'"; /** * The current version number of the game. @@ -26,7 +26,7 @@ class Constants * A suffix to add to the game version. * Add a suffix to prototype builds and remove it for releases. */ - public static final VERSION_SUFFIX = ' PROTOTYPE'; + public static final VERSION_SUFFIX:String = ' PROTOTYPE'; #if debug static function get_VERSION():String @@ -48,12 +48,12 @@ class Constants /** * Link to download the game on Itch.io. */ - public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin/purchase"; + public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin/purchase'; /** * Link to the game's page on Kickstarter. */ - public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/"; + public static final URL_KICKSTARTER:String = 'https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/'; /** * GIT REPO DATA @@ -64,12 +64,12 @@ class Constants /** * The current Git branch. */ - public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch(); + public static final GIT_BRANCH:String = funkin.util.macro.GitCommit.getGitBranch(); /** * The current Git commit hash. */ - public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash(); + public static final GIT_HASH:String = funkin.util.macro.GitCommit.getGitCommitHash(); #end /** @@ -87,27 +87,70 @@ class Constants */ public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33; + /** + * Default variation for charts. + */ + public static final DEFAULT_VARIATION:String = 'default'; + + /** + * STAGE DEFAULTS + */ + // ============================== + + /** + * Default difficulty for charts. + */ + public static final DEFAULT_DIFFICULTY:String = 'normal'; + + /** + * Default player character for charts. + */ + public static final DEFAULT_CHARACTER:String = 'bf'; + + /** + * Default stage for charts. + */ + public static final DEFAULT_STAGE:String = 'mainStage'; + + /** + * Default song for if the PlayState messes up. + */ + public static final DEFAULT_SONG:String = 'tutorial'; + /** * OTHER */ // ============================== + /** + * All MP3 decoders introduce a playback delay of `528` samples, + * which at 44,100 Hz (samples per second) is ~12 ms. + */ + public static final MP3_DELAY_MS:Float = 528 / 44100 * 1000; + /** * The scale factor to use when increasing the size of pixel art graphics. */ - public static final PIXEL_ART_SCALE = 6; + public static final PIXEL_ART_SCALE:Float = 6; /** * The BPM of the title screen and menu music. * TODO: Move to metadata file. */ - public static final FREAKY_MENU_BPM = 102; + public static final FREAKY_MENU_BPM:Float = 102; /** * The volume at which to play the countdown before the song starts. */ - public static final COUNTDOWN_VOLUME = 0.6; + public static final COUNTDOWN_VOLUME:Float = 0.6; - public static final DEFAULT_VARIATION = 'default'; - public static final DEFAULT_DIFFICULTY = 'normal'; + /** + * The default intensity for camera zooms. + */ + public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015; + + /** + * The default rate for camera zooms (in beats per zoom). + */ + public static final DEFAULT_ZOOM_RATE:Int = 4; }