Lots of reported bugs fixed.

This commit is contained in:
EliteMasterEric 2023-10-18 01:02:10 -04:00
parent d47c5f5765
commit cdab8753ea
14 changed files with 147 additions and 26 deletions

6
.vscode/launch.json vendored
View file

@ -23,6 +23,12 @@
"name": "Haxe Eval", "name": "Haxe Eval",
"type": "haxe-eval", "type": "haxe-eval",
"request": "launch" "request": "launch"
},
{
// Attaches the debugger to an already running game
"name": "HXCPP - Attach",
"type": "hxcpp",
"request": "attach"
} }
] ]
} }

View file

@ -240,7 +240,7 @@ class PauseSubState extends MusicBeatSubState
case 'Exit to Chart Editor': case 'Exit to Chart Editor':
this.close(); this.close();
if (FlxG.sound.music != null) FlxG.sound.music.stop(); if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
PlayState.instance.close(); // This only works because PlayState is a substate! PlayState.instance.close(); // This only works because PlayState is a substate!
case 'BACK': case 'BACK':

View file

@ -1,13 +1,15 @@
package funkin.data; package funkin.data;
import funkin.data.song.importer.FNFLegacyData.LegacyNote; import funkin.data.song.importer.FNFLegacyData.LegacyNote;
import hxjsonast.Json;
import hxjsonast.Tools;
import hxjsonast.Json.JObjectField;
import haxe.ds.Either;
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
import funkin.data.song.importer.FNFLegacyData.LegacyNoteData; import funkin.data.song.importer.FNFLegacyData.LegacyNoteData;
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds; import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds;
import haxe.ds.Either;
import hxjsonast.Json;
import hxjsonast.Json.JObjectField;
import hxjsonast.Tools;
import thx.semver.Version;
import thx.semver.VersionRule;
/** /**
* `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values. * `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values.
@ -23,7 +25,8 @@ class DataParse
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)` * `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
* @param json Contains the `pos` and `value` of the property. * @param json Contains the `pos` and `value` of the property.
* @param name The name of the property. * @param name The name of the property.
* @throws If the property is not a string or is empty. * @throws Error If the property is not a string or is empty.
* @return The string value.
*/ */
public static function stringNotEmpty(json:Json, name:String):String public static function stringNotEmpty(json:Json, name:String):String
{ {
@ -37,6 +40,42 @@ class DataParse
} }
} }
/**
* `@:jcustomparse(funkin.data.DataParse.semverVersion)`
* @param json Contains the `pos` and `value` of the property.
* @param name The name of the property.
* @return The value of the property as a `thx.semver.Version`.
*/
public static function semverVersion(json:Json, name:String):Version
{
switch (json.value)
{
case JString(s):
if (s == "") throw 'Expected version property $name to be non-empty.';
return s;
default:
throw 'Expected version property $name to be a string, but it was ${json.value}.';
}
}
/**
* `@:jcustomparse(funkin.data.DataParse.semverVersionRule)`
* @param json Contains the `pos` and `value` of the property.
* @param name The name of the property.
* @return The value of the property as a `thx.semver.VersionRule`.
*/
public static function semverVersionRule(json:Json, name:String):VersionRule
{
switch (json.value)
{
case JString(s):
if (s == "") throw 'Expected version rule property $name to be non-empty.';
return s;
default:
throw 'Expected version rule property $name to be a string, but it was ${json.value}.';
}
}
/** /**
* Parser which outputs a Dynamic value, either a object or something else. * Parser which outputs a Dynamic value, either a object or something else.
* @param json * @param json

View file

@ -1,6 +1,8 @@
package funkin.data; package funkin.data;
import funkin.util.SerializerUtil; import funkin.util.SerializerUtil;
import thx.semver.Version;
import thx.semver.VersionRule;
/** /**
* `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON. * `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON.
@ -9,9 +11,30 @@ import funkin.util.SerializerUtil;
*/ */
class DataWrite class DataWrite
{ {
/**
* `@:jcustomwrite(funkin.data.DataWrite.dynamicValue)`
* @param value
* @return String
*/
public static function dynamicValue(value:Dynamic):String public static function dynamicValue(value:Dynamic):String
{ {
// Is this cheating? Yes. Do I care? No. // Is this cheating? Yes. Do I care? No.
return SerializerUtil.toJSON(value); return SerializerUtil.toJSON(value);
} }
/**
* `@:jcustomwrite(funkin.data.DataWrite.semverVersion)`
*/
public static function semverVersion(value:Version):String
{
return value.toString();
}
/**
* `@:jcustomwrite(funkin.data.DataWrite.semverVersionRule)`
*/
public static function semverVersionRule(value:VersionRule):String
{
return value.toString();
}
} }

View file

@ -12,6 +12,8 @@ class SongMetadata
* *
*/ */
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION) // @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version; public var version:Version;
@:default("Unknown") @:default("Unknown")
@ -203,6 +205,8 @@ class SongMusicData
* *
*/ */
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION) // @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version; public var version:Version;
@:default("Unknown") @:default("Unknown")
@ -367,6 +371,8 @@ class SongCharacterData
class SongChartData class SongChartData
{ {
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION) @:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version; public var version:Version;
public var scrollSpeed:Map<String, Float>; public var scrollSpeed:Map<String, Float>;

View file

@ -246,7 +246,8 @@ class SongDataUtils
typedef SongClipboardItems = typedef SongClipboardItems =
{ {
?valid:Bool, @:optional
notes:Array<SongNoteData>, var valid:Bool;
events:Array<SongEventData> var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
} }

View file

@ -24,6 +24,8 @@ class SongMetadata_v2_0_0
// ========== // ==========
// UNMODIFIED VALUES // UNMODIFIED VALUES
// ========== // ==========
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version; public var version:Version;
@:default("Unknown") @:default("Unknown")

View file

@ -129,7 +129,6 @@ class HealthIcon extends FlxSprite
if (value == characterId) return value; if (value == characterId) return value;
characterId = value ?? Constants.DEFAULT_HEALTH_ICON; characterId = value ?? Constants.DEFAULT_HEALTH_ICON;
loadCharacter(characterId);
return characterId; return characterId;
} }
@ -138,7 +137,6 @@ class HealthIcon extends FlxSprite
if (value == isPixel) return value; if (value == isPixel) return value;
isPixel = value; isPixel = value;
loadCharacter(characterId);
return isPixel; return isPixel;
} }
@ -165,8 +163,11 @@ class HealthIcon extends FlxSprite
{ {
if (data == null) if (data == null)
{ {
this.isPixel = false;
this.characterId = Constants.DEFAULT_HEALTH_ICON; this.characterId = Constants.DEFAULT_HEALTH_ICON;
this.isPixel = false;
loadCharacter(characterId);
this.size.set(1.0, 1.0); this.size.set(1.0, 1.0);
this.offset.x = 0.0; this.offset.x = 0.0;
this.offset.y = 0.0; this.offset.y = 0.0;
@ -174,8 +175,11 @@ class HealthIcon extends FlxSprite
} }
else else
{ {
this.isPixel = data.isPixel ?? false;
this.characterId = data.id; this.characterId = data.id;
this.isPixel = data.isPixel ?? false;
loadCharacter(characterId);
this.size.set(data.scale ?? 1.0, data.scale ?? 1.0); this.size.set(data.scale ?? 1.0, data.scale ?? 1.0);
this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0; this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0;
this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0; this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0;

View file

@ -698,7 +698,15 @@ class PlayState extends MusicBeatSubState
FlxG.sound.music.pause(); FlxG.sound.music.pause();
FlxG.sound.music.time = (startTimestamp); FlxG.sound.music.time = (startTimestamp);
vocals = currentChart.buildVocals(); if (!overrideMusic)
{
vocals = currentChart.buildVocals();
if (vocals.members.length == 0)
{
trace('WARNING: No vocals found for this song.');
}
}
vocals.pause(); vocals.pause();
vocals.time = 0; vocals.time = 0;

View file

@ -154,6 +154,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
{ {
if (metadata == null || metadata.playData == null) continue; if (metadata == null || metadata.playData == null) continue;
// If there are no difficulties in the metadata, there's a problem.
if (metadata.playData.difficulties.length == 0)
{
throw 'Song $id has no difficulties listed in metadata!';
}
// There may be more difficulties in the chart file than in the metadata, // There may be more difficulties in the chart file than in the metadata,
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress) // (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
// but all the difficulties in the metadata must be in the chart file. // but all the difficulties in the metadata must be in the chart file.

View file

@ -421,6 +421,8 @@ typedef RawSaveData =
/** /**
* A semantic versioning string for the save data format. * A semantic versioning string for the save data format.
*/ */
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
var version:Version; var version:Version;
var api:SaveApiData; var api:SaveApiData;

View file

@ -404,7 +404,6 @@ class ChartEditorDialogHandler
{ {
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId)) if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
{ {
trace('Selected file: ' + selectedFile.fullPath);
#if !mac #if !mac
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
{ {
@ -415,13 +414,12 @@ class ChartEditorDialogHandler
}); });
#end #end
state.switchToCurrentInstrumental();
dialog.hideDialog(DialogButton.APPLY); dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile); removeDropHandler(onDropFile);
} }
else else
{ {
trace('Failed to load instrumental (${selectedFile.fullPath})');
#if !mac #if !mac
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
{ {
@ -452,6 +450,7 @@ class ChartEditorDialogHandler
}); });
#end #end
state.switchToCurrentInstrumental();
dialog.hideDialog(DialogButton.APPLY); dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile); removeDropHandler(onDropFile);
} }
@ -570,6 +569,12 @@ class ChartEditorDialogHandler
var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default'); var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default');
newSongMetadata.playData.difficulties = switch (targetVariation)
{
case 'erect': ['erect', 'nightmare'];
default: ['easy', 'normal', 'hard'];
};
var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField); var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField);
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog'; if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
inputSongName.onChange = function(event:UIEvent) { inputSongName.onChange = function(event:UIEvent) {

View file

@ -554,6 +554,9 @@ class ChartEditorState extends HaxeUIState
notePreviewDirty = true; notePreviewDirty = true;
notePreviewViewportBoundsDirty = true; notePreviewViewportBoundsDirty = true;
// Make sure the difficulty we selected is in the list of difficulties.
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
return selectedDifficulty; return selectedDifficulty;
} }
@ -971,6 +974,7 @@ class ChartEditorState extends HaxeUIState
result = []; result = [];
trace('Initializing blank note data for difficulty ' + selectedDifficulty); trace('Initializing blank note data for difficulty ' + selectedDifficulty);
currentSongChartData.notes.set(selectedDifficulty, result); currentSongChartData.notes.set(selectedDifficulty, result);
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
return result; return result;
} }
return result; return result;
@ -979,6 +983,7 @@ class ChartEditorState extends HaxeUIState
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData> function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
{ {
currentSongChartData.notes.set(selectedDifficulty, value); currentSongChartData.notes.set(selectedDifficulty, value);
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
return value; return value;
} }
@ -4089,7 +4094,7 @@ class ChartEditorState extends HaxeUIState
} }
subStateClosed.add(fixCamera); subStateClosed.add(fixCamera);
subStateClosed.add(updateConductor); subStateClosed.add(resetConductorAfterTest);
FlxTransitionableState.skipNextTransIn = false; FlxTransitionableState.skipNextTransIn = false;
FlxTransitionableState.skipNextTransOut = false; FlxTransitionableState.skipNextTransOut = false;
@ -4122,10 +4127,9 @@ class ChartEditorState extends HaxeUIState
add(this.component); add(this.component);
} }
function updateConductor(_:FlxSubState = null):Void function resetConductorAfterTest(_:FlxSubState = null):Void
{ {
var targetPos = scrollPositionInMs; moveSongToScrollPosition();
Conductor.update(targetPos);
} }
public function postLoadInstrumental():Void public function postLoadInstrumental():Void
@ -4179,12 +4183,14 @@ class ChartEditorState extends HaxeUIState
function moveSongToScrollPosition():Void function moveSongToScrollPosition():Void
{ {
// Update the songPosition in the audio tracks. // Update the songPosition in the audio tracks.
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs; if (audioInstTrack != null)
{
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
// Update the songPosition in the Conductor.
Conductor.update(audioInstTrack.time);
}
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs; if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
// Update the songPosition in the Conductor.
Conductor.update(audioInstTrack.time);
// We need to update the note sprites because we changed the scroll position. // We need to update the note sprites because we changed the scroll position.
noteDisplayDirty = true; noteDisplayDirty = true;
} }

View file

@ -38,6 +38,19 @@ class ArrayTools
return null; return null;
} }
/**
* Push an element to the array if it is not already present.
* @param input The array to push to
* @param element The element to push
* @return Whether the element was pushed
*/
public static function pushUnique<T>(input:Array<T>, element:T):Bool
{
if (input.contains(element)) return false;
input.push(element);
return true;
}
/** /**
* Remove all elements from the array, without creating a new array. * Remove all elements from the array, without creating a new array.
* @param array The array to clear. * @param array The array to clear.