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
This commit is contained in:
Eric 2023-08-28 15:03:29 -04:00 committed by GitHub
parent 74dac9d830
commit 21f44edf1d
43 changed files with 629 additions and 345 deletions

View File

@ -20,6 +20,7 @@
<!--Mobile-specific-->
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false" />
<!-- _____________________________ Path Settings ____________________________ -->
<set name="BUILD_DIR" value="export/debug" if="debug" />
<set name="BUILD_DIR" value="export/release" unless="debug" />
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
@ -96,8 +97,9 @@
<haxelib name="lime" /> <!-- Game engine backend -->
<haxelib name="openfl" /> <!-- Game engine backend -->
<haxelib name="flixel" /> <!-- Game engine -->
<haxedev set="webgl" />
<!--In case you want to use the addons package-->
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
<haxelib name="hscript" /> <!-- Scripting -->
<haxelib name="flixel-ui" /> <!-- UI framework (deprecate this? -->
@ -150,8 +152,11 @@
<haxeflag name="--macro" value="include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])" />
<!-- Necessary to provide stack traces for HScript. -->
<haxedef name="hscriptPos" />
<haxedef name="safeMode"/>
<haxedef name="HXCPP_CHECK_POINTER" />
<haxedef name="HXCPP_STACK_LINE" />
<haxedef name="HXCPP_STACK_TRACE" />
<haxedef name="openfl-enable-handle-error" />
<!-- This macro allows addition of new functionality to existing Flixel. -->
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
@ -159,7 +164,6 @@
<icon path="art/icon32.png" size="32" />
<icon path="art/icon64.png" size="64" />
<icon path="art/iconOG.png" />
<!-- <haxedef name="SKIP_TO_PLAYSTATE" if="debug" /> -->
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
<haxedef name="CAN_CHEAT" if="switch debug" />
<haxedef name="haxeui_no_mouse_reset" />
@ -172,7 +176,6 @@
<!-- Difficulty, only used for week or song, defaults to 1 -->
<!-- <haxedef name="dif" value="2" if="debug"/> -->
</section>
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
<section if="newgrounds">
<!-- Enables Ng.core.verbose -->
<!-- <haxedef name="NG_VERBOSE" /> -->
@ -182,18 +185,21 @@
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
</section>
<!-- Uncomment this to wipe your input settings. -->
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5">
<!--
Use the parent assets folder rather than the exported one
No more will we accidentally undo our changes!
TODO: Add a thing to disable this on builds meant for itch.io.
-->
<haxedef name="REDIRECT_ASSETS_FOLDER" />
</section>
<!-- <prebuild haxe="trace('prebuilding');"/> -->
<!-- <postbuild haxe="art/Postbuild.hx"/> -->
<!-- <config:ios allow-provisioning-updates="true" team-id="" /> -->
<!-- Run a script before and after building. -->
<postbuild haxe="source/Prebuild.hx"/> -->
<postbuild haxe="source/Postbuild.hx"/> -->
<!-- Options for Polymod -->
<section if="polymod">
<!-- Turns on additional debug logging. -->
@ -213,12 +219,4 @@
<!-- Determines the file in the mod folder used for the icon. -->
<haxedef name="POLYMOD_MOD_ICON_FILE" value="_polymod_icon.png" />
</section>
<section if="TOOLS">
<!-- Compiles tool for old song conversion shit -->
<!-- Assumes you use it on windows/desktop!!!! -->
<postbuild command="haxe -main art/SongConverter.hx --cs export/songShit" />
<assets path="export/songShit/bin/SongConverter.exe" rename="SongConverter.exe" />
<!-- <postbuild command='ren export/songShit/bin export/songShit/tools '/> -->
<!-- <postbuild command='move export/songShit/tools export/release/windows/bin'/> -->
</section>
</project>

View File

@ -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"
},
{

View File

@ -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);

11
source/Postbuild.hx Normal file
View File

@ -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?
}
}

9
source/Prebuild.hx Normal file
View File

@ -0,0 +1,9 @@
package source; // Yeah, I know...
class Prebuild
{
static function main()
{
trace('Prebuild');
}
}

View File

@ -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);

View File

@ -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
*/

View File

@ -64,7 +64,7 @@ class DiscordClient
trace("Discord Client initialized");
}
public static function changePresence(details:String, state:Null<String>, ?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;

View File

@ -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;

View File

@ -41,7 +41,7 @@ class PauseSubState extends MusicBeatSubState
var isChartingMode:Bool;
public function new(?isChartingMode:Bool = false)
public function new(isChartingMode:Bool = false)
{
super();

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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}');

View File

@ -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<BaseCharacter>
public static function fetchCharacter(charId:String, debug:Bool = false):Null<BaseCharacter>
{
if (charId == null || charId == '' || !characterCache.exists(charId))
{

View File

@ -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.

View File

@ -208,7 +208,7 @@ class OutroData
public var type:OutroType;
public var data:Dynamic;
public function new(typeStr:Null<String>, data:Dynamic)
public function new(?typeStr:String, data:Dynamic)
{
this.type = typeStr ?? OutroType.NONE;
this.data = data;

View File

@ -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<Float>):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
{

View File

@ -93,7 +93,7 @@ class DialogueBoxTextData
public var shadowColor:Null<String>;
public var shadowWidth:Null<Int>;
public function new(offsets:Null<Array<Float>>, width:Null<Int>, size:Null<Int>, color:String, shadowColor:Null<String>, shadowWidth:Null<Int>)
public function new(offsets:Null<Array<Float>>, ?width:Int, ?size:Int, color:String, ?shadowColor:String, shadowWidth:Null<Int>)
{
this.offsets = offsets ?? [0, 0];
this.width = width ?? 300;

View File

@ -19,8 +19,8 @@ class SpeakerData
public var scale:Float;
public var animations:Array<AnimationData>;
public function new(version:String, name:String, assetPath:String, animations:Array<AnimationData>, ?offsets:Array<Float>, ?flipX:Bool = false,
?isPixel:Bool = false, ?scale:Float = 1.0)
public function new(version:String, name:String, assetPath:String, animations:Array<AnimationData>, ?offsets:Array<Float>, flipX:Bool = false,
isPixel:Bool = false, ?scale:Float = 1.0)
{
this.version = version;
this.name = name;

View File

@ -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;

View File

@ -113,7 +113,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
return noteFrames;
}
function getNoteAssetPath(?raw:Bool = false):String
function getNoteAssetPath(raw:Bool = false):String
{
if (raw)
{
@ -161,7 +161,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
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<NoteStyleData>
target.antialiasing = !_data.assets.noteStrumline.isPixel;
}
function getStrumlineAssetPath(?raw:Bool = false):String
function getStrumlineAssetPath(raw:Bool = false):String
{
if (raw)
{

View File

@ -72,7 +72,7 @@ class Song implements IPlayStateScriptedClass
@:allow(funkin.play.song.Song)
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
?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)
{

View File

@ -920,7 +920,7 @@ typedef RawSongTimeChange =
*/
abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange
{
public function new(timeStamp:Float, beatTime:Null<Float>, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array<Int>)
public function new(timeStamp:Float, ?beatTime:Float, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array<Int>)
{
this =
{

View File

@ -123,7 +123,7 @@ class SongDataUtils
/**
* Sort an array of notes by strum time.
*/
public static function sortNotes(notes:Array<SongNoteData>, ?desc:Bool = false):Array<SongNoteData>
public static function sortNotes(notes:Array<SongNoteData>, desc:Bool = false):Array<SongNoteData>
{
// 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<SongEventData>, ?desc:Bool = false):Array<SongEventData>
public static function sortEvents(events:Array<SongEventData>, desc:Bool = false):Array<SongEventData>
{
// TODO: Modifies the array in place. Is this okay?
events.sort(function(a:SongEventData, b:SongEventData):Int {

View File

@ -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));

View File

@ -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;

View File

@ -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);
}

View File

@ -503,7 +503,7 @@ typedef StageDataCharacter =
* Again, just like CSS.
* @default 0
*/
zIndex:Null<Int>,
?zIndex:Int,
/**
* The position to render the character at.

View File

@ -225,7 +225,7 @@ class AddEventsCommand implements ChartEditorCommand
var events:Array<SongEventData>;
var appendToSelection:Bool;
public function new(events:Array<SongEventData>, ?appendToSelection:Bool = false)
public function new(events:Array<SongEventData>, appendToSelection:Bool = false)
{
this.events = events;
this.appendToSelection = appendToSelection;

View File

@ -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<String> = [];
@ -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);

View File

@ -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<SongEventData> = null;
/**
* The image used for all song events. Cached for performance.
*/
static var eventSpriteBasic:BitmapData;
static var eventSpriteBasic:Null<BitmapData> = 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<SongEventData>):Null<SongEventData>
{
this.eventData = value;

View File

@ -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<SongNoteData>;
/**
* 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<FlxFramesCollection> = 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<SongNoteData>):Null<SongNoteData>
{
this.noteData = value;

File diff suppressed because it is too large Load Diff

View File

@ -168,9 +168,9 @@ class ChartEditorToolboxHandler
case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:
toolbox = buildToolboxCharactersLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
toolbox = null; // buildToolboxPlayerPreviewLayout(state);
toolbox = buildToolboxPlayerPreviewLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
toolbox = null; // buildToolboxOpponentPreviewLayout(state);
toolbox = buildToolboxOpponentPreviewLayout(state);
default:
// This happens if you try to load an unknown layout.
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
@ -200,6 +200,8 @@ class ChartEditorToolboxHandler
// Initialize the toolbox without showing it.
if (toolbox == null) toolbox = initToolbox(state, id);
if (toolbox == null) throw 'ChartEditorToolboxHandler.getToolbox() - Could not retrieve or build toolbox: $id';
return toolbox;
}

View File

@ -32,7 +32,7 @@ class CharacterPlayer extends Box
{
var character:BaseCharacter;
public function new(?defaultToBf:Bool = true)
public function new(defaultToBf:Bool = true)
{
super();
_overrideSkipTransformChildren = false;

View File

@ -21,7 +21,7 @@ class MovePropCommand implements StageEditorCommand
var yDiff:Float;
var realMove:Bool; // if needs a move!
public function new(xDiff:Float = 0, yDiff:Float = 0, ?realMove:Bool = true)
public function new(xDiff:Float = 0, yDiff:Float = 0, realMove:Bool = true)
{
this.xDiff = xDiff;
this.yDiff = yDiff;

View File

@ -3,17 +3,18 @@ package funkin.ui.stageBuildShit;
import flixel.FlxSprite;
import flixel.input.mouse.FlxMouseEvent;
import flixel.math.FlxPoint;
import funkin.play.PlayState;
import funkin.play.character.BaseCharacter;
import funkin.play.PlayState;
import funkin.play.stage.StageData;
import funkin.play.stage.StageProp;
import funkin.shaderslmfao.StrokeShader;
import funkin.ui.haxeui.HaxeUISubState;
import funkin.ui.stageBuildShit.StageEditorCommand;
import haxe.ui.RuntimeComponentBuilder;
import funkin.util.SerializerUtil;
import haxe.ui.containers.ListView;
import haxe.ui.core.Component;
import haxe.ui.events.UIEvent;
import haxe.ui.RuntimeComponentBuilder;
import openfl.events.Event;
import openfl.events.IOErrorEvent;
import openfl.net.FileReference;
@ -376,6 +377,6 @@ class StageOffsetSubState extends HaxeUISubState
stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x);
stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y);
return CoolUtil.jsonStringify(stageLol);
return SerializerUtil.toJSON(stageLol);
}
}

View File

@ -14,7 +14,7 @@ class ClipboardUtil
* @param once If true, the callback will only execute once and then be deleted.
* @param priority Set the priority at which the callback will be executed. Higher values execute first.
*/
public static function addListener(callback:Void->Void, ?once:Bool = false, ?priority:Int = 0):Void
public static function addListener(callback:Void->Void, once:Bool = false, ?priority:Int = 0):Void
{
lime.system.Clipboard.onUpdate.add(callback, once, priority);
}

View File

@ -209,7 +209,7 @@ class FileUtil
* @return Whether the file dialog was opened successfully.
*/
public static function saveMultipleFiles(resources:Array<Entry>, ?onSaveAll:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String,
?force:Bool = false):Bool
force:Bool = false):Bool
{
#if desktop
// Prompt the user for a directory, then write all of the files to there.
@ -257,7 +257,7 @@ class FileUtil
* Takes an array of file entries and prompts the user to save them as a ZIP file.
*/
public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String,
?force:Bool = false):Bool
force:Bool = false):Bool
{
// Create a ZIP file.
var zipBytes:Bytes = createZIPFromEntries(resources);
@ -278,7 +278,7 @@ class FileUtil
* Use `saveFilesAsZIP` instead.
* @param force Whether to force overwrite an existing file.
*/
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, ?force:Bool = false):Bool
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, force:Bool = false):Bool
{
#if desktop
// Create a ZIP file.

View File

@ -21,7 +21,7 @@ class SerializerUtil
/**
* Convert a Haxe object to a JSON string.
*/
public static function toJSON(input:Dynamic, ?pretty:Bool = true):String
public static function toJSON(input:Dynamic, pretty:Bool = true):String
{
return Json.stringify(input, replacer, pretty ? INDENT_CHAR : null);
}

View File

@ -0,0 +1,143 @@
package funkin.util.logging;
import openfl.Lib;
import openfl.events.UncaughtErrorEvent;
/**
* A custom crash handler that writes to a log file and displays a message box.
*/
@:nullSafety
class CrashHandler
{
static final LOG_FOLDER = 'logs';
/**
* Initializes
*/
public static function initialize():Void
{
trace('[LOG] Enabling standard uncaught error handler...');
Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError);
#if cpp
trace('[LOG] Enabling C++ critical error handler...');
untyped __global__.__hxcpp_set_critical_error_handler(onCriticalError);
#end
}
/**
* Called when an uncaught error occurs.
* This handles most thrown errors, and is sufficient to handle everything alone on HTML5.
* @param error Information on the error that was thrown.
*/
static function onUncaughtError(error:UncaughtErrorEvent):Void
{
try
{
#if sys
logError(error);
#end
displayError(error);
}
catch (e:Dynamic)
{
trace('Error while handling crash: ' + e);
}
}
static function onCriticalError(message:String):Void
{
try
{
#if sys
logErrorMessage(message, true);
#end
displayErrorMessage(message);
}
catch (e:Dynamic)
{
trace('Error while handling crash: $e');
trace('Message: $message');
}
}
static function displayError(error:UncaughtErrorEvent):Void
{
displayErrorMessage(generateErrorMessage(error));
}
static function displayErrorMessage(message:String):Void
{
lime.app.Application.current.window.alert(message, "Fatal Uncaught Exception");
}
#if sys
static function logError(error:UncaughtErrorEvent):Void
{
logErrorMessage(generateErrorMessage(error));
}
static function logErrorMessage(message:String, critical:Bool = false):Void
{
FileUtil.createDirIfNotExists(LOG_FOLDER);
sys.io.File.saveContent('$LOG_FOLDER/crash${critical ? '-critical' : ''}-${DateUtil.generateTimestamp()}.log', message);
}
#end
static function generateErrorMessage(error:UncaughtErrorEvent):String
{
var errorMessage:String = "";
var callStack:Array<haxe.CallStack.StackItem> = haxe.CallStack.exceptionStack(true);
errorMessage += '${error.error}\n';
for (stackItem in callStack)
{
switch (stackItem)
{
case FilePos(innerStackItem, file, line, column):
errorMessage += ' in ${file}#${line}';
if (column != null) errorMessage += ':${column}';
case CFunction:
errorMessage += '[Function] ';
case Module(m):
errorMessage += '[Module(${m})] ';
case Method(classname, method):
errorMessage += '[Function(${classname}.${method})] ';
case LocalFunction(v):
errorMessage += '[LocalFunction(${v})] ';
}
errorMessage += '\n';
}
return errorMessage;
}
public static function queryStatus():Void
{
@:privateAccess
var currentStatus = Lib.current.stage.__uncaughtErrorEvents.__enabled;
trace('ERROR HANDLER STATUS: ' + currentStatus);
#if openfl_enable_handle_error
trace('Define: openfl_enable_handle_error is enabled');
#else
trace('Define: openfl_enable_handle_error is disabled');
#end
#if openfl_disable_handle_error
trace('Define: openfl_disable_handle_error is enabled');
#else
trace('Define: openfl_disable_handle_error is disabled');
#end
}
public static function induceBasicCrash():Void
{
throw "This is an example of an uncaught exception.";
}
}

View File

@ -22,7 +22,7 @@ class ClassMacro
* @param includeSubPackages Whether to include classes located in sub-packages of the target package.
* @return A list of classes matching the specified criteria.
*/
public static macro function listClassesInPackage(targetPackage:String, ?includeSubPackages:Bool = true):ExprOf<Iterable<Class<Dynamic>>>
public static macro function listClassesInPackage(targetPackage:String, includeSubPackages:Bool = true):ExprOf<Iterable<Class<Dynamic>>>
{
if (!onGenerateCallbackRegistered)
{