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",
"type": "haxe-eval",
"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':
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!
case 'BACK':

View file

@ -1,13 +1,15 @@
package funkin.data;
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.LegacyNoteSection;
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.
@ -23,7 +25,8 @@ class DataParse
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
* @param json Contains the `pos` and `value` 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
{
@ -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.
* @param json

View file

@ -1,6 +1,8 @@
package funkin.data;
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.
@ -9,9 +11,30 @@ import funkin.util.SerializerUtil;
*/
class DataWrite
{
/**
* `@:jcustomwrite(funkin.data.DataWrite.dynamicValue)`
* @param value
* @return String
*/
public static function dynamicValue(value:Dynamic):String
{
// Is this cheating? Yes. Do I care? No.
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)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version;
@:default("Unknown")
@ -203,6 +205,8 @@ class SongMusicData
*
*/
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
public var version:Version;
@:default("Unknown")
@ -367,6 +371,8 @@ class SongCharacterData
class SongChartData
{
@: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 scrollSpeed:Map<String, Float>;

View file

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

View file

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

View file

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

View file

@ -154,6 +154,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
{
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,
// (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.

View file

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

View file

@ -404,7 +404,6 @@ class ChartEditorDialogHandler
{
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
{
trace('Selected file: ' + selectedFile.fullPath);
#if !mac
NotificationManager.instance.addNotification(
{
@ -415,13 +414,12 @@ class ChartEditorDialogHandler
});
#end
state.switchToCurrentInstrumental();
dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile);
}
else
{
trace('Failed to load instrumental (${selectedFile.fullPath})');
#if !mac
NotificationManager.instance.addNotification(
{
@ -452,6 +450,7 @@ class ChartEditorDialogHandler
});
#end
state.switchToCurrentInstrumental();
dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile);
}
@ -570,6 +569,12 @@ class ChartEditorDialogHandler
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);
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
inputSongName.onChange = function(event:UIEvent) {

View file

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

View file

@ -38,6 +38,19 @@ class ArrayTools
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.
* @param array The array to clear.