From 21f44edf1d6eeb38ae66f823ef9b4038ea91e8ef Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Aug 2023 15:03:29 -0400 Subject: [PATCH] New crash handler + Additional null safety for ChartEditorState (#130) * A bunch of smaller syntax tweaks. * New crash handler catches and logs critical errors! * Chart editor now has null safety enabled. * Fix -W build issue. * Actually update hmm.json to use the crash handling branch * Fix issues causing crash handler to trigger --- Project.xml | 28 +- hmm.json | 4 +- source/Main.hx | 11 +- source/Postbuild.hx | 11 + source/Prebuild.hx | 9 + source/funkin/Alphabet.hx | 2 +- source/funkin/CoolUtil.hx | 26 - source/funkin/Discord.hx | 2 +- source/funkin/FreeplayState.hx | 4 +- source/funkin/PauseSubState.hx | 2 +- .../graphics/adobeanimate/FlxAtlasSprite.hx | 2 +- source/funkin/play/GameOverSubState.hx | 2 +- .../play/character/AnimateAtlasCharacter.hx | 2 +- source/funkin/play/character/BaseCharacter.hx | 4 +- source/funkin/play/character/CharacterData.hx | 2 +- .../play/character/MultiSparrowCharacter.hx | 2 +- .../cutscene/dialogue/ConversationData.hx | 2 +- .../play/cutscene/dialogue/DialogueBox.hx | 8 +- .../play/cutscene/dialogue/DialogueBoxData.hx | 2 +- .../play/cutscene/dialogue/SpeakerData.hx | 4 +- source/funkin/play/notes/Strumline.hx | 2 +- .../funkin/play/notes/notestyle/NoteStyle.hx | 6 +- source/funkin/play/song/Song.hx | 4 +- source/funkin/play/song/SongData.hx | 2 +- source/funkin/play/song/SongDataUtils.hx | 4 +- source/funkin/play/song/SongValidator.hx | 24 +- source/funkin/play/stage/Bopper.hx | 2 +- source/funkin/play/stage/Stage.hx | 10 +- source/funkin/play/stage/StageData.hx | 2 +- .../ui/debug/charting/ChartEditorCommand.hx | 2 +- .../charting/ChartEditorDialogHandler.hx | 21 +- .../debug/charting/ChartEditorEventSprite.hx | 8 +- .../debug/charting/ChartEditorNoteSprite.hx | 6 +- .../ui/debug/charting/ChartEditorState.hx | 580 +++++++++++------- .../charting/ChartEditorToolboxHandler.hx | 6 +- .../ui/haxeui/components/CharacterPlayer.hx | 2 +- .../ui/stageBuildShit/StageEditorCommand.hx | 2 +- .../ui/stageBuildShit/StageOffsetSubState.hx | 7 +- source/funkin/util/ClipboardUtil.hx | 2 +- source/funkin/util/FileUtil.hx | 6 +- source/funkin/util/SerializerUtil.hx | 2 +- source/funkin/util/logging/CrashHandler.hx | 143 +++++ source/funkin/util/macro/ClassMacro.hx | 2 +- 43 files changed, 629 insertions(+), 345 deletions(-) create mode 100644 source/Postbuild.hx create mode 100644 source/Prebuild.hx create mode 100644 source/funkin/util/logging/CrashHandler.hx diff --git a/Project.xml b/Project.xml index 8dbb43618..a83db1677 100644 --- a/Project.xml +++ b/Project.xml @@ -20,6 +20,7 @@ + @@ -96,8 +97,9 @@ + - + @@ -150,8 +152,11 @@ + + + @@ -159,7 +164,6 @@ - @@ -172,7 +176,6 @@ -
@@ -182,18 +185,21 @@
+ + +
- - - + + --> + --> +
@@ -213,12 +219,4 @@
-
- - - - - - -
diff --git a/hmm.json b/hmm.json index 150a4f242..a3226281b 100644 --- a/hmm.json +++ b/hmm.json @@ -47,7 +47,7 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "3590c94858fc6dbcf9b4d522cd644ad571269677", + "ref": "f5daafe93bdfa957538f199294a54e0476c805b7", "url": "https://github.com/haxeui/haxeui-core/" }, { @@ -128,7 +128,7 @@ "name": "openfl", "type": "git", "dir": null, - "ref": "d33d489a137ff8fdece4994cf1302f0b6334ed08", + "ref": "1591a6c5f1f72e65d711f7e17e8055df41424d94", "url": "https://github.com/EliteMasterEric/openfl" }, { diff --git a/source/Main.hx b/source/Main.hx index 1d7b73bb8..72209cd30 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -2,12 +2,13 @@ package; import flixel.FlxGame; import flixel.FlxState; +import funkin.util.logging.CrashHandler; import funkin.MemoryCounter; import haxe.ui.Toolkit; -import openfl.Lib; import openfl.display.FPS; import openfl.display.Sprite; import openfl.events.Event; +import openfl.Lib; import openfl.media.Video; import openfl.net.NetStream; @@ -77,10 +78,18 @@ class Main extends Sprite * -Eric */ + CrashHandler.initialize(); + + CrashHandler.queryStatus(); + initHaxeUI(); addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen)); + #if hxcpp_debug_server + trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.'); + #end + #if debug fpsCounter = new FPS(10, 3, 0xFFFFFF); addChild(fpsCounter); diff --git a/source/Postbuild.hx b/source/Postbuild.hx new file mode 100644 index 000000000..d48b670a4 --- /dev/null +++ b/source/Postbuild.hx @@ -0,0 +1,11 @@ +package source; // Yeah, I know... + +class Postbuild +{ + static function main() + { + trace('Postbuild'); + + // TODO: Maybe put a 'Build took X seconds' message here? + } +} diff --git a/source/Prebuild.hx b/source/Prebuild.hx new file mode 100644 index 000000000..63782fc56 --- /dev/null +++ b/source/Prebuild.hx @@ -0,0 +1,9 @@ +package source; // Yeah, I know... + +class Prebuild +{ + static function main() + { + trace('Prebuild'); + } +} diff --git a/source/funkin/Alphabet.hx b/source/funkin/Alphabet.hx index 670496727..45e9a2aee 100644 --- a/source/funkin/Alphabet.hx +++ b/source/funkin/Alphabet.hx @@ -38,7 +38,7 @@ class Alphabet extends FlxSpriteGroup var isBold:Bool = false; - public function new(x:Float = 0.0, y:Float = 0.0, text:String = "", ?bold:Bool = false, typed:Bool = false) + public function new(x:Float = 0.0, y:Float = 0.0, text:String = "", bold:Bool = false, typed:Bool = false) { super(x, y); diff --git a/source/funkin/CoolUtil.hx b/source/funkin/CoolUtil.hx index 93fa937da..d07bb4e22 100644 --- a/source/funkin/CoolUtil.hx +++ b/source/funkin/CoolUtil.hx @@ -12,7 +12,6 @@ import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import funkin.play.PlayState; import funkin.shaderslmfao.ScreenWipeShader; -import haxe.Json; import haxe.format.JsonParser; import lime.math.Rectangle; import lime.utils.Assets; @@ -120,31 +119,6 @@ class CoolUtil FlxG.camera.setFilters([new ShaderFilter(screenWipeShit)]); } - /** - * Just saves the json with some default values hehe - * @param json - * @return String - */ - public static inline function jsonStringify(data:Dynamic):String - { - return Json.stringify(data, null, "\t"); - } - - /** - * Hashlink json encoding fix for some wacky bullshit - * https://github.com/HaxeFoundation/haxe/issues/6930#issuecomment-384570392 - */ - public static function coolJSON(fileData:String) - { - var cont = fileData; - function is(n:Int, what:Int) - return cont.charCodeAt(n) == what; - return JsonParser.parse(cont.substr(if (is(0, 65279)) /// looks like a HL target, skipping only first character here: - 1 else if (is(0, 239) && is(1, 187) && is(2, 191)) /// it seems to be Neko or PHP, start from position 3: - 3 else /// all other targets, that prepare the UTF string correctly - 0)); - } - /* * frame dependant lerp kinda lol */ diff --git a/source/funkin/Discord.hx b/source/funkin/Discord.hx index 4fb6e9dcf..d2cf12535 100644 --- a/source/funkin/Discord.hx +++ b/source/funkin/Discord.hx @@ -64,7 +64,7 @@ class DiscordClient trace("Discord Client initialized"); } - public static function changePresence(details:String, state:Null, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) + public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) { var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0; diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index dd87e7d36..c31e8c77b 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -464,7 +464,7 @@ class FreeplayState extends MusicBeatSubState }); } - public function generateSongList(?filterStuff:SongFilter, ?force:Bool = false) + public function generateSongList(?filterStuff:SongFilter, force:Bool = false) { curSelected = 0; @@ -1045,7 +1045,7 @@ class FreeplaySongData public var songCharacter:String = ""; public var isFav:Bool = false; - public function new(song:String, levelId:String, songCharacter:String, ?isFav:Bool = false) + public function new(song:String, levelId:String, songCharacter:String, isFav:Bool = false) { this.songName = song; this.levelId = levelId; diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx index 9133a8fab..791a4bb9a 100644 --- a/source/funkin/PauseSubState.hx +++ b/source/funkin/PauseSubState.hx @@ -41,7 +41,7 @@ class PauseSubState extends MusicBeatSubState var isChartingMode:Bool; - public function new(?isChartingMode:Bool = false) + public function new(isChartingMode:Bool = false) { super(); diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index 74b348142..ae7a5708c 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -82,7 +82,7 @@ class FlxAtlasSprite extends FlxAnimate * @param restart Whether to restart the animation if it is already playing. * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing */ - public function playAnimation(id:String, ?restart:Bool = false, ?ignoreOther:Bool = false):Void + public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false):Void { // Skip if not allowed to play animations. if ((!canPlayOtherAnims && !ignoreOther)) return; diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index b53937361..15ed0421e 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -255,7 +255,7 @@ class GameOverSubState extends MusicBeatSubState * Starts the death music at the appropriate volume. * @param startingVolume */ - function startDeathMusic(?startingVolume:Float = 1, ?force:Bool = false):Void + function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void { var musicPath = Paths.music('gameOver' + musicSuffix); if (isEnding) diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index 4f4b3f8f7..3523ec994 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -81,7 +81,7 @@ class AnimateAtlasCharacter extends BaseCharacter super.onCreate(event); } - public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reverse:Bool = false):Void + public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void { if ((!canPlayOtherAnims && !ignoreOther)) return; diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 72f968538..c7b58c393 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -570,7 +570,7 @@ class BaseCharacter extends Bopper * @param miss If true, play the miss animation instead of the sing animation. * @param suffix A suffix to append to the animation name, like `alt`. */ - public function playSingAnimation(dir:NoteDirection, ?miss:Bool = false, ?suffix:String = ''):Void + public function playSingAnimation(dir:NoteDirection, miss:Bool = false, ?suffix:String = ''):Void { var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}'; @@ -578,7 +578,7 @@ class BaseCharacter extends Bopper playAnimation(anim, true); } - public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reversed:Bool = false):Void + public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void { FlxG.watch.addQuick('playAnim(${characterName})', name); // trace('playAnim(${characterName}): ${name}'); diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 710eb884b..f1b316b7f 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -190,7 +190,7 @@ class CharacterDataParser * @param charId The character ID to fetch. * @return The character instance, or null if the character was not found. */ - public static function fetchCharacter(charId:String, ?debug:Bool = false):Null + public static function fetchCharacter(charId:String, debug:Bool = false):Null { if (charId == null || charId == '' || !characterCache.exists(charId)) { diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx index 34d89362f..968f613ff 100644 --- a/source/funkin/play/character/MultiSparrowCharacter.hx +++ b/source/funkin/play/character/MultiSparrowCharacter.hx @@ -181,7 +181,7 @@ class MultiSparrowCharacter extends BaseCharacter trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}'); } - public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reverse:Bool = false):Void + public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void { // Make sure we ignore other animations if we're currently playing a forced one, // unless we're forcing a new animation. diff --git a/source/funkin/play/cutscene/dialogue/ConversationData.hx b/source/funkin/play/cutscene/dialogue/ConversationData.hx index d2e3b74cf..749f1b7a1 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationData.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationData.hx @@ -208,7 +208,7 @@ class OutroData public var type:OutroType; public var data:Dynamic; - public function new(typeStr:Null, data:Dynamic) + public function new(?typeStr:String, data:Dynamic) { this.type = typeStr ?? OutroType.NONE; this.data = data; diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx index 52564010a..bfc0e9233 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx @@ -172,7 +172,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass /** * Set the sprite scale to the appropriate value. - * @param scale + * @param scale */ public function setScale(scale:Null):Void { @@ -218,7 +218,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass * @param name The name of the current animation. * @param frameNumber The number of the current frame. * @param frameIndex The index of the current frame. - * + * * For example, if an animation was defined as having the indexes [3, 0, 1, 2], * then the first callback would have frameNumber = 0 and frameIndex = 3. */ @@ -253,7 +253,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass * @param restart Whether to restart the animation if it is already playing. * @param reversed If true, play the animation backwards, from the last frame to the first. */ - public function playAnimation(name:String, restart:Bool = false, ?reversed:Bool = false):Void + public function playAnimation(name:String, restart:Bool = false, reversed:Bool = false):Void { var correctName:String = correctAnimationName(name); if (correctName == null) return; @@ -266,7 +266,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass /** * Ensure that a given animation exists before playing it. * Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play. - * @param name + * @param name */ function correctAnimationName(name:String):String { diff --git a/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx b/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx index 537a27129..801a01dd7 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx @@ -93,7 +93,7 @@ class DialogueBoxTextData public var shadowColor:Null; public var shadowWidth:Null; - public function new(offsets:Null>, width:Null, size:Null, color:String, shadowColor:Null, shadowWidth:Null) + public function new(offsets:Null>, ?width:Int, ?size:Int, color:String, ?shadowColor:String, shadowWidth:Null) { this.offsets = offsets ?? [0, 0]; this.width = width ?? 300; diff --git a/source/funkin/play/cutscene/dialogue/SpeakerData.hx b/source/funkin/play/cutscene/dialogue/SpeakerData.hx index a0f9a3300..88883ead8 100644 --- a/source/funkin/play/cutscene/dialogue/SpeakerData.hx +++ b/source/funkin/play/cutscene/dialogue/SpeakerData.hx @@ -19,8 +19,8 @@ class SpeakerData public var scale:Float; public var animations:Array; - public function new(version:String, name:String, assetPath:String, animations:Array, ?offsets:Array, ?flipX:Bool = false, - ?isPixel:Bool = false, ?scale:Float = 1.0) + public function new(version:String, name:String, assetPath:String, animations:Array, ?offsets:Array, flipX:Bool = false, + isPixel:Bool = false, ?scale:Float = 1.0) { this.version = version; this.name = name; diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index b343bee86..8847636bd 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -271,7 +271,7 @@ class Strumline extends FlxSpriteGroup * @param strumTime * @return Float */ - static function calculateNoteYPos(strumTime:Float, ?vwoosh:Bool = true):Float + static function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float { // Make the note move faster visually as it moves offscreen. var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0; diff --git a/source/funkin/play/notes/notestyle/NoteStyle.hx b/source/funkin/play/notes/notestyle/NoteStyle.hx index fd45342a0..97871b657 100644 --- a/source/funkin/play/notes/notestyle/NoteStyle.hx +++ b/source/funkin/play/notes/notestyle/NoteStyle.hx @@ -113,7 +113,7 @@ class NoteStyle implements IRegistryEntry return noteFrames; } - function getNoteAssetPath(?raw:Bool = false):String + function getNoteAssetPath(raw:Bool = false):String { if (raw) { @@ -161,7 +161,7 @@ class NoteStyle implements IRegistryEntry return (result == null) ? fallback.fetchNoteAnimationData(dir) : result; } - public function getHoldNoteAssetPath(?raw:Bool = false):String + public function getHoldNoteAssetPath(raw:Bool = false):String { if (raw) { @@ -209,7 +209,7 @@ class NoteStyle implements IRegistryEntry target.antialiasing = !_data.assets.noteStrumline.isPixel; } - function getStrumlineAssetPath(?raw:Bool = false):String + function getStrumlineAssetPath(raw:Bool = false):String { if (raw) { diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index ec89d8706..63610950f 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -72,7 +72,7 @@ class Song implements IPlayStateScriptedClass @:allow(funkin.play.song.Song) public static function buildRaw(songId:String, metadata:Array, variations:Array, charts:Map, - ?validScore:Bool = false):Song + validScore:Bool = false):Song { var result:Song = new Song(songId, true); @@ -150,7 +150,7 @@ class Song implements IPlayStateScriptedClass /** * Parse and cache the chart for all difficulties of this song. */ - public function cacheCharts(?force:Bool = false):Void + public function cacheCharts(force:Bool = false):Void { if (force) { diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index 938ee0708..bf574c399 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -920,7 +920,7 @@ typedef RawSongTimeChange = */ abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange { - public function new(timeStamp:Float, beatTime:Null, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array) + public function new(timeStamp:Float, ?beatTime:Float, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array) { this = { diff --git a/source/funkin/play/song/SongDataUtils.hx b/source/funkin/play/song/SongDataUtils.hx index 750d5f54b..a7cbd1b6c 100644 --- a/source/funkin/play/song/SongDataUtils.hx +++ b/source/funkin/play/song/SongDataUtils.hx @@ -123,7 +123,7 @@ class SongDataUtils /** * Sort an array of notes by strum time. */ - public static function sortNotes(notes:Array, ?desc:Bool = false):Array + public static function sortNotes(notes:Array, desc:Bool = false):Array { // TODO: Modifies the array in place. Is this okay? notes.sort(function(a:SongNoteData, b:SongNoteData):Int { @@ -135,7 +135,7 @@ class SongDataUtils /** * Sort an array of events by strum time. */ - public static function sortEvents(events:Array, ?desc:Bool = false):Array + public static function sortEvents(events:Array, desc:Bool = false):Array { // TODO: Modifies the array in place. Is this okay? events.sort(function(a:SongEventData, b:SongEventData):Int { diff --git a/source/funkin/play/song/SongValidator.hx b/source/funkin/play/song/SongValidator.hx index d91dda1d9..16ea88664 100644 --- a/source/funkin/play/song/SongValidator.hx +++ b/source/funkin/play/song/SongValidator.hx @@ -63,9 +63,15 @@ class SongValidator } input.timeChanges = validateTimeChanges(input.timeChanges, songId); + if (input.timeChanges == null) + { + trace('[SONGDATA] Song ${songId} is missing a timeChanges field. '); + return null; + } + input.playData = validatePlayData(input.playData, songId); - input.variation = ''; + if (input.variation == null) input.variation = ''; return input; } @@ -79,6 +85,12 @@ class SongValidator */ public static function validatePlayData(input:SongPlayData, songId:String = 'unknown'):SongPlayData { + if (input == null) + { + trace('[SONGDATA] Could not parse metadata.playData for song ${songId}'); + return null; + } + return input; } @@ -91,6 +103,12 @@ class SongValidator */ public static function validateTimeChange(input:SongTimeChange, songId:String = 'unknown'):SongTimeChange { + if (input == null) + { + trace('[SONGDATA] Could not parse metadata.timeChange for song ${songId}'); + return null; + } + return input; } @@ -101,8 +119,8 @@ class SongValidator { if (input == null) { - trace('[SONGDATA] Song ${songId} is missing a timeChanges field. '); - return []; + trace('[SONGDATA] Could not parse metadata.timeChange for song ${songId}'); + return null; } input = input.map((timeChange) -> validateTimeChange(timeChange, songId)); diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index a144026f5..187b5ec32 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -268,7 +268,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing * @param reversed If true, play the animation backwards, from the last frame to the first. */ - public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reversed:Bool = false):Void + public function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void { if (!canPlayOtherAnims && !ignoreOther) return; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index f4f380a0b..1ac9b0b67 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -450,7 +450,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * @param pop If true, the character will be removed from the stage as well. * @return The Boyfriend character. */ - public function getBoyfriend(?pop:Bool = false):BaseCharacter + public function getBoyfriend(pop:Bool = false):BaseCharacter { if (pop) { @@ -473,7 +473,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * @param pop If true, the character will be removed from the stage as well. * @return The player/Boyfriend character. */ - public function getPlayer(?pop:Bool = false):BaseCharacter + public function getPlayer(pop:Bool = false):BaseCharacter { return getBoyfriend(pop); } @@ -483,7 +483,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * @param pop If true, the character will be removed from the stage as well. * @return The Girlfriend character. */ - public function getGirlfriend(?pop:Bool = false):BaseCharacter + public function getGirlfriend(pop:Bool = false):BaseCharacter { if (pop) { @@ -506,7 +506,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * @param pop If true, the character will be removed from the stage as well. * @return The Dad character. */ - public function getDad(?pop:Bool = false):BaseCharacter + public function getDad(pop:Bool = false):BaseCharacter { if (pop) { @@ -529,7 +529,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass * @param pop If true, the character will be removed from the stage as well. * @return The opponent character. */ - public function getOpponent(?pop:Bool = false):BaseCharacter + public function getOpponent(pop:Bool = false):BaseCharacter { return getDad(pop); } diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index 867c6e1a5..c14e05aaf 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -503,7 +503,7 @@ typedef StageDataCharacter = * Again, just like CSS. * @default 0 */ - zIndex:Null, + ?zIndex:Int, /** * The position to render the character at. diff --git a/source/funkin/ui/debug/charting/ChartEditorCommand.hx b/source/funkin/ui/debug/charting/ChartEditorCommand.hx index bf4d710dd..fd179c481 100644 --- a/source/funkin/ui/debug/charting/ChartEditorCommand.hx +++ b/source/funkin/ui/debug/charting/ChartEditorCommand.hx @@ -225,7 +225,7 @@ class AddEventsCommand implements ChartEditorCommand var events:Array; var appendToSelection:Bool; - public function new(events:Array, ?appendToSelection:Bool = false) + public function new(events:Array, appendToSelection:Bool = false) { this.events = events; this.appendToSelection = appendToSelection; diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index df5a25b62..63dc8bd92 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -258,7 +258,7 @@ class ChartEditorDialogHandler * @return The dialog that was opened. */ @:haxe.warning("-WVarInit") - public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog + public static function openUploadInstDialog(state:ChartEditorState, closable:Bool = true):Dialog { var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable); @@ -578,7 +578,7 @@ class ChartEditorDialogHandler * @param closable Whether the dialog can be closed by the user. * @return The dialog that was opened. */ - public static function openUploadVocalsDialog(state:ChartEditorState, ?closable:Bool = true):Dialog + public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog { var charIdsForVocals:Array = []; @@ -692,7 +692,7 @@ class ChartEditorDialogHandler * @return The dialog that was opened. */ @:haxe.warning('-WVarInit') - public static function openChartDialog(state:ChartEditorState, ?closable:Bool = true):Dialog + public static function openChartDialog(state:ChartEditorState, closable:Bool = true):Dialog { var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT, true, closable); @@ -765,6 +765,19 @@ class ChartEditorDialogHandler var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import'); songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import'); + if (songMetadataVariation == null) + { + // Tell the user the load was not successful. + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Could not load metadata file (${path.file}.${path.ext})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + return; + } + songMetadata.set(variation, songMetadataVariation); // Tell the user the load was successful. @@ -879,7 +892,7 @@ class ChartEditorDialogHandler * @param closable * @return Dialog */ - public static function openImportChartDialog(state:ChartEditorState, format:String, ?closable:Bool = true):Dialog + public static function openImportChartDialog(state:ChartEditorState, format:String, closable:Bool = true):Dialog { var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT, true, closable); diff --git a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx index 2cd9ab2fe..2524f014c 100644 --- a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx @@ -26,12 +26,12 @@ class ChartEditorEventSprite extends FlxSprite * The note data that this sprite represents. * You can set this to null to kill the sprite and flag it for recycling. */ - public var eventData(default, set):SongEventData; + public var eventData(default, set):Null = null; /** * The image used for all song events. Cached for performance. */ - static var eventSpriteBasic:BitmapData; + static var eventSpriteBasic:Null = null; public function new(parent:ChartEditorState) { @@ -49,7 +49,7 @@ class ChartEditorEventSprite extends FlxSprite * Build a set of animations to allow displaying different types of chart events. * @param force `true` to force rebuilding the frames. */ - static function buildFrames(?force:Bool = false):FlxFramesCollection + static function buildFrames(force:Bool = false):FlxFramesCollection { static var eventFrames:FlxFramesCollection = null; @@ -112,7 +112,7 @@ class ChartEditorEventSprite extends FlxSprite this.updateHitbox(); } - function set_eventData(value:SongEventData):SongEventData + function set_eventData(value:Null):Null { this.eventData = value; diff --git a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx index 14ffa3a76..0adbf1a20 100644 --- a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx @@ -27,7 +27,7 @@ class ChartEditorNoteSprite extends FlxSprite * The note data that this sprite represents. * You can set this to null to kill the sprite and flag it for recycling. */ - public var noteData(default, set):SongNoteData; + public var noteData(default, set):Null; /** * The name of the note style currently in use. @@ -70,7 +70,7 @@ class ChartEditorNoteSprite extends FlxSprite this.animation.addByPrefix('tapRightPixel', 'pixel7'); } - static var noteFrameCollection:FlxFramesCollection = null; + static var noteFrameCollection:Null = null; /** * We load all the note frames once, then reuse them. @@ -108,7 +108,7 @@ class ChartEditorNoteSprite extends FlxSprite } } - function set_noteData(value:SongNoteData):SongNoteData + function set_noteData(value:Null):Null { this.noteData = value; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index aa6e70714..83c052050 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,5 +1,6 @@ package funkin.ui.debug.charting; +import flixel.system.FlxAssets.FlxSoundAsset; import flixel.math.FlxMath; import haxe.ui.components.TextField; import haxe.ui.components.DropDown; @@ -85,6 +86,7 @@ using Lambda; */ // Give other classes access to private instance fields +@:nullSafety @:allow(funkin.ui.debug.charting.ChartEditorCommand) @:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) @:allow(funkin.ui.debug.charting.ChartEditorThemeHandler) @@ -238,7 +240,7 @@ class ChartEditorState extends HaxeUIState * 40 means the playhead is 1 grid length below the base position. * -40 means the playhead is 1 grid length above the base position. */ - var playheadPositionInPixels(default, set):Float; + var playheadPositionInPixels(default, set):Float = 0.0; function set_playheadPositionInPixels(value:Float):Float { @@ -258,28 +260,40 @@ class ChartEditorState extends HaxeUIState * playheadPosition, converted to steps. * NOT dependant on BPM, because the size of a grid square does not change with BPM. */ - var playheadPositionInSteps(get, null):Float; + var playheadPositionInSteps(get, set):Float; function get_playheadPositionInSteps():Float { return playheadPositionInPixels / GRID_SIZE; } + function set_playheadPositionInSteps(value:Float):Float + { + playheadPositionInPixels = value * GRID_SIZE; + return value; + } + /** * playheadPosition, converted to milliseconds. * DEPENDANT on BPM, because the duration of a grid square changes with BPM. */ - var playheadPositionInMs(get, null):Float; + var playheadPositionInMs(get, set):Float; function get_playheadPositionInMs():Float { return Conductor.getStepTimeInMs(playheadPositionInSteps); } + function set_playheadPositionInMs(value:Float):Float + { + playheadPositionInSteps = Conductor.getTimeInSteps(value); + return value; + } + /** * songLength, in milliseconds. */ - @:isVar var songLengthInMs(get, set):Float; + @:isVar var songLengthInMs(get, set):Float = 0; function get_songLengthInMs():Float { @@ -337,7 +351,7 @@ class ChartEditorState extends HaxeUIState * Dictates the appearance of many UI elements. * Currently hardcoded to just Light and Dark. */ - var currentTheme(default, set):ChartEditorTheme = null; + var currentTheme(default, set):ChartEditorTheme = ChartEditorTheme.Light; function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme { @@ -350,9 +364,10 @@ class ChartEditorState extends HaxeUIState /** * Whether a skip button has been pressed on the playbar, and which one. + * `null` if no button has been pressed. * This will be used to update the scrollPosition (in the same function that handles the scroll wheel), then cleared. */ - var playbarButtonPressed:String = null; + var playbarButtonPressed:Null = null; /** * Whether the head of the playbar is currently being dragged with the mouse by the user. @@ -392,13 +407,15 @@ class ChartEditorState extends HaxeUIState /** * The character sprite in the Player Preview window. + * `null` until accessed. */ - var currentPlayerCharacterPlayer:CharacterPlayer = null; + var currentPlayerCharacterPlayer:Null = null; /** * The character sprite in the Opponent Preview window. + * `null` until accessed. */ - var currentOpponentCharacterPlayer:CharacterPlayer = null; + var currentOpponentCharacterPlayer:Null = null; /** * The currently selected live input style. @@ -431,7 +448,7 @@ class ChartEditorState extends HaxeUIState /** * Whether hitsounds are enabled for at least one character. */ - var hitsoundsEnabled(get, null):Bool; + var hitsoundsEnabled(get, never):Bool; function get_hitsoundsEnabled():Bool { @@ -452,14 +469,14 @@ class ChartEditorState extends HaxeUIState * Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI. * If so, ignore mouse events underneath. */ - var isCursorOverHaxeUI(get, null):Bool; + var isCursorOverHaxeUI(get, never):Bool; function get_isCursorOverHaxeUI():Bool { return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY); } - var isCursorOverHaxeUIButton(get, null):Bool; + var isCursorOverHaxeUIButton(get, never):Bool; function get_isCursorOverHaxeUIButton():Bool { @@ -575,10 +592,13 @@ class ChartEditorState extends HaxeUIState } else { - // Stop the auto-save timer. - autoSaveTimer.cancel(); - autoSaveTimer.destroy(); - autoSaveTimer = null; + if (autoSaveTimer != null) + { + // Stop the auto-save timer. + autoSaveTimer.cancel(); + autoSaveTimer.destroy(); + autoSaveTimer = null; + } } return saveDataDirty = value; @@ -587,7 +607,7 @@ class ChartEditorState extends HaxeUIState /** * A timer used to auto-save the chart after a period of inactivity. */ - var autoSaveTimer:FlxTimer; + var autoSaveTimer:Null = null; /** * Whether the difficulty tree view in the toolbox has been modified and needs to be updated. @@ -672,9 +692,10 @@ class ChartEditorState extends HaxeUIState /** * The position where the user clicked to start a selection. + * `null` if the user isn't currently selecting anything. * The selection box extends from this point to the current mouse position. */ - var selectionBoxStartPos:FlxPoint = null; + var selectionBoxStartPos:Null = null; /** * Whether the user's last mouse click was on the playhead scroll area. @@ -685,13 +706,14 @@ class ChartEditorState extends HaxeUIState * Where the user's last mouse click was on the note preview scroll area. * `null` if the user isn't clicking on the note preview. */ - var notePreviewScrollAreaStartPos:FlxPoint = null; + var notePreviewScrollAreaStartPos:Null = null; /** * The SongNoteData which is currently being placed. + * `null` if the user isn't currently placing a note. * As the user drags, we will update this note's sustain length. */ - var currentPlaceNoteData:SongNoteData = null; + var currentPlaceNoteData:Null = null; /** * The Dialog components representing the currently available tool windows. @@ -706,18 +728,21 @@ class ChartEditorState extends HaxeUIState /** * The audio track for the instrumental. + * `null` until an instrumental track is loaded. */ - var audioInstTrack:FlxSound; + var audioInstTrack:Null = null; /** * The raw byte data for the instrumental audio track. + * `null` until an instrumental track is loaded. */ - var audioInstTrackData:Bytes = null; + var audioInstTrackData:Null = null; /** * The audio track for the vocals. + * `null` until vocal track(s) are loaded. */ - var audioVocalTrackGroup:VoicesGroup; + var audioVocalTrackGroup:Null = null; /** * A map of the audio tracks for each character's vocals. @@ -738,12 +763,12 @@ class ChartEditorState extends HaxeUIState * - Keys are the variation IDs. At least one (`default`) must exist. * - Values are the relevant metadata, ready to be serialized to JSON. */ - var songMetadata:Map; + var songMetadata:Map = []; /** * Retrieves the list of variations for the current song. */ - var availableVariations(get, null):Array; + var availableVariations(get, never):Array; function get_availableVariations():Array { @@ -756,21 +781,28 @@ class ChartEditorState extends HaxeUIState * Retrieves the list of difficulties for the current variation of the current song. * ONLY CONTAINS DIFFICULTIES FOR THE CURRENT VARIATION so if on the default variation, erect/nightmare won't be included. */ - var availableDifficulties(get, null):Array; + var availableDifficulties(get, never):Array; function get_availableDifficulties():Array { - return songMetadata.get(selectedVariation).playData.difficulties; + var m:Null = songMetadata.get(selectedVariation); + return m?.playData?.difficulties ?? []; } /** * Retrieves the list of difficulties for ALL variations of the current song. */ - var allDifficulties(get, null):Array; + var allDifficulties(get, never):Array; function get_allDifficulties():Array { - var result:Array> = [for (x in availableVariations) songMetadata.get(x).playData.difficulties]; + var result:Array> = [ + for (x in availableVariations) + { + var m:Null = songMetadata.get(x); + m?.playData?.difficulties ?? []; + } + ]; return result.flatten(); } @@ -779,7 +811,7 @@ class ChartEditorState extends HaxeUIState * - Keys are the variation IDs. At least one (`default`) must exist. * - Values are the relevant chart data, ready to be serialized to JSON. */ - var songChartData:Map; + var songChartData:Map = []; /** * Convenience property to get the chart data for the current variation. @@ -788,7 +820,7 @@ class ChartEditorState extends HaxeUIState function get_currentSongMetadata():SongMetadata { - var result:SongMetadata = songMetadata.get(selectedVariation); + var result:Null = songMetadata.get(selectedVariation); if (result == null) { result = new SongMetadata('Dad Battle', 'Kawai Sprite', selectedVariation); @@ -810,7 +842,7 @@ class ChartEditorState extends HaxeUIState function get_currentSongChartData():SongChartData { - var result:SongChartData = songChartData.get(selectedVariation); + var result:Null = songChartData.get(selectedVariation); if (result == null) { result = new SongChartData(1.0, [], []); @@ -944,7 +976,7 @@ class ChartEditorState extends HaxeUIState return currentSongMetadata.songName = value; } - var currentSongId(get, null):String; + var currentSongId(get, never):String; function get_currentSongId():String { @@ -968,7 +1000,7 @@ class ChartEditorState extends HaxeUIState return currentSongMetadata.artist = value; } - var currentSongPlayableCharacters(get, null):Array; + var currentSongPlayableCharacters(get, never):Array; function get_currentSongPlayableCharacters():Array { @@ -1025,7 +1057,7 @@ class ChartEditorState extends HaxeUIState * SIGNALS */ // ============================== - // public var onDifficultyChange(default, null):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + // public var onDifficultyChange(default, never):FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); /** * RENDER OBJECTS */ @@ -1034,7 +1066,7 @@ class ChartEditorState extends HaxeUIState /** * The IMAGE used for the grid. Updated by ChartEditorThemeHandler. */ - var gridBitmap:BitmapData; + var gridBitmap:Null = null; /** * The IMAGE used for the selection squares. Updated by ChartEditorThemeHandler. @@ -1042,100 +1074,114 @@ class ChartEditorState extends HaxeUIState * 1. A sprite is given this bitmap and placed over selected notes. * 2. The image is split and used for a 9-slice sprite for the selection box. */ - var selectionSquareBitmap:BitmapData = null; + var selectionSquareBitmap:Null = null; /** * The IMAGE used for the note preview bitmap. Updated by ChartEditorThemeHandler. * The image is split and used for a 9-slice sprite for the box over the note preview. */ - var notePreviewViewportBitmap:BitmapData = null; + var notePreviewViewportBitmap:Null = null; /** * The tiled sprite used to display the grid. * The height is the length of the song, and scrolling is done by simply the sprite. */ - var gridTiledSprite:FlxSprite; + var gridTiledSprite:Null = null; /** * The playhead representing the current position in the song. * Can move around on the grid independently of the view. */ - var gridPlayhead:FlxSpriteGroup; + var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup(); - var gridPlayheadScrollArea:FlxSprite; + var gridPlayheadScrollArea:Null = null; /** * A sprite used to indicate the note that will be placed on click. */ - var gridGhostNote:ChartEditorNoteSprite; + var gridGhostNote:Null = null; /** * A sprite used to indicate the event that will be placed on click. */ - var gridGhostEvent:ChartEditorEventSprite; + var gridGhostEvent:Null = null; /** * The waveform which (optionally) displays over the grid, underneath the notes and playhead. */ - var gridSpectrogram:PolygonSpectogram; + var gridSpectrogram:Null = null; /** * The sprite used to display the note preview area. * We move this up and down to scroll the preview. */ - var notePreview:ChartEditorNotePreview; + var notePreview:Null = null; /** * The rectangular sprite used for representing the current viewport on the note preview. * We move this up and down and resize it to represent the visible area. */ - var notePreviewViewport:FlxSliceSprite; + var notePreviewViewport:Null = null; /** * The rectangular sprite used for rendering the selection box. * Uses a 9-slice to stretch the selection box to the correct size without warping. */ - var selectionBoxSprite:FlxSliceSprite; + var selectionBoxSprite:Null = null; /** * The opponent's health icon. */ - var healthIconDad:HealthIcon; + var healthIconDad:Null = null; /** * The player's health icon. */ - var healthIconBF:HealthIcon; + var healthIconBF:Null = null; /** * The purple background sprite. */ - var menuBG:FlxSprite; + var menuBG:Null = null; + + /** + * The layout containing the playbar head slider. + */ + var playbarHeadLayout:Null = null; + + /** + * The playbar head slider. + */ + var playbarHead:Null = null; + + /** + * The current process that is lerping the scroll position. + * Used to cancel the previous lerp if the user scrolls again. + */ + var currentScrollEase:Null; /** * The sprite group containing the note graphics. * Only displays a subset of the data from `currentSongChartNoteData`, * and kills notes that are off-screen to be recycled later. */ - var renderedNotes:FlxTypedSpriteGroup; + var renderedNotes:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(); /** * The sprite group containing the hold note graphics. * Only displays a subset of the data from `currentSongChartNoteData`, * and kills notes that are off-screen to be recycled later. */ - var renderedHoldNotes:FlxTypedSpriteGroup; + var renderedHoldNotes:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(); /** * The sprite group containing the song events. * Only displays a subset of the data from `currentSongChartEventData`, * and kills events that are off-screen to be recycled later. */ - var renderedEvents:FlxTypedSpriteGroup; + var renderedEvents:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(); - var renderedSelectionSquares:FlxTypedSpriteGroup; - - var playbarHead:Slider; + var renderedSelectionSquares:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(); public function new() { @@ -1159,7 +1205,7 @@ class ChartEditorState extends HaxeUIState buildBackground(); - currentTheme = ChartEditorTheme.Light; + ChartEditorThemeHandler.updateTheme(this); buildGrid(); // buildSpectrogram(audioInstTrack); @@ -1213,6 +1259,8 @@ class ChartEditorState extends HaxeUIState */ function buildGrid():Void { + if (gridBitmap == null) throw 'ERROR: Tried to build grid, but gridBitmap is null! Check ChartEditorThemeHandler.updateTheme().'; + gridTiledSprite = new FlxTiledSprite(gridBitmap, gridBitmap.width, 1000, false, true); gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid. gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar. @@ -1241,7 +1289,6 @@ class ChartEditorState extends HaxeUIState gridPlayheadScrollArea.zIndex = 25; // The playhead that show the current position in the song. - gridPlayhead = new FlxSpriteGroup(); add(gridPlayhead); gridPlayhead.zIndex = 30; @@ -1279,6 +1326,8 @@ class ChartEditorState extends HaxeUIState function buildSelectionBox():Void { + if (selectionBoxSprite == null) throw 'ERROR: Tried to build selection box, but selectionBoxSprite is null! Check ChartEditorThemeHandler.updateTheme().'; + selectionBoxSprite.scrollFactor.set(0, 0); add(selectionBoxSprite); selectionBoxSprite.zIndex = 30; @@ -1288,6 +1337,9 @@ class ChartEditorState extends HaxeUIState function setSelectionBoxBounds(bounds:FlxRect = null):Void { + if (selectionBoxSprite == null) + throw 'ERROR: Tried to set selection box bounds, but selectionBoxSprite is null! Check ChartEditorThemeHandler.updateTheme().'; + if (bounds == null) { selectionBoxSprite.visible = false; @@ -1312,6 +1364,8 @@ class ChartEditorState extends HaxeUIState notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; add(notePreview); + if (notePreviewViewport == null) throw 'ERROR: Tried to build note preview, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().'; + notePreviewViewport.scrollFactor.set(0, 0); add(notePreviewViewport); notePreviewViewport.zIndex = 30; @@ -1323,6 +1377,9 @@ class ChartEditorState extends HaxeUIState { var bounds:FlxRect = new FlxRect(); + // Return 0, 0, 0, 0 if the note preview doesn't exist for some reason. + if (notePreview == null) return bounds; + // Horizontal position and width are constant. bounds.x = notePreview.x; bounds.width = notePreview.width; @@ -1356,6 +1413,9 @@ class ChartEditorState extends HaxeUIState function setNotePreviewViewportBounds(bounds:FlxRect = null):Void { + if (notePreviewViewport == null) + throw 'ERROR: Tried to set note preview viewport bounds, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().'; + if (bounds == null) { notePreviewViewport.visible = false; @@ -1387,32 +1447,31 @@ class ChartEditorState extends HaxeUIState */ function buildNoteGroup():Void { - renderedHoldNotes = new FlxTypedSpriteGroup(); + if (gridTiledSprite == null) throw 'ERROR: Tried to build note groups, but gridTiledSprite is null! Check ChartEditorState.buildGrid().'; + renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedHoldNotes); renderedHoldNotes.zIndex = 24; - renderedNotes = new FlxTypedSpriteGroup(); renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedNotes); renderedNotes.zIndex = 25; - renderedEvents = new FlxTypedSpriteGroup(); renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedEvents); renderedNotes.zIndex = 25; - renderedSelectionSquares = new FlxTypedSpriteGroup(); renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedSelectionSquares); renderedNotes.zIndex = 26; } - var playbarHeadLayout:Component; - function buildAdditionalUI():Void { playbarHeadLayout = buildComponent(CHART_EDITOR_PLAYBARHEAD_LAYOUT); + + if (playbarHeadLayout == null) throw 'ERROR: Failed to construct playbarHeadLayout! Check "${CHART_EDITOR_PLAYBARHEAD_LAYOUT}".'; + playbarHeadLayout.zIndex = 110; playbarHeadLayout.width = FlxG.width - 8; @@ -1421,6 +1480,7 @@ class ChartEditorState extends HaxeUIState playbarHeadLayout.y = FlxG.height - 48 - 8; playbarHead = playbarHeadLayout.findComponent('playbarHead', Slider); + if (playbarHead == null) throw 'ERROR: Failed to fetch playbarHead from playbarHeadLayout! Check "${CHART_EDITOR_PLAYBARHEAD_LAYOUT}".'; playbarHead.allowFocus = false; playbarHead.width = FlxG.width; playbarHead.height = 10; @@ -1445,7 +1505,7 @@ class ChartEditorState extends HaxeUIState playbarHeadDragging = false; // Set the song position to where the playhead was moved to. - scrollPositionInPixels = songLengthInPixels * (playbarHead.value / 100); + scrollPositionInPixels = songLengthInPixels * (playbarHead?.value ?? 0 / 100); // Update the conductor and audio tracks to match. moveSongToScrollPosition(); @@ -1584,31 +1644,40 @@ class ChartEditorState extends HaxeUIState addUIChangeListener('menubarItemOpponentHitsounds', event -> hitsoundsEnabledOpponent = event.value); setUICheckboxSelected('menubarItemOpponentHitsounds', hitsoundsEnabledOpponent); - var instVolumeLabel:Label = findComponent('menubarLabelVolumeInstrumental', Label); - addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) { - var volume:Float = event.value / 100.0; - if (audioInstTrack != null) audioInstTrack.volume = volume; - instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%'; - }); + var instVolumeLabel:Null