From 62c62cb53e4628f9d9b5828302abfbe0c4117222 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 25 Sep 2023 21:18:56 -0400 Subject: [PATCH 01/74] Update json2object to fork --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 47460facf..76ea0eeda 100644 --- a/hmm.json +++ b/hmm.json @@ -97,7 +97,7 @@ "name": "json2object", "type": "git", "dir": null, - "ref": "429986134031cbb1980f09d0d3d642b4b4cbcd6a", + "ref": "f4df19cfa196f85eece55c3367021fc965f1fa9a", "url": "https://github.com/elnabo/json2object" }, { @@ -160,4 +160,4 @@ "version": "0.11.0" } ] -} \ No newline at end of file +} From 42bb50882d30979c32e69e6f10c2e6abe6be99a0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 25 Sep 2023 23:24:07 -0400 Subject: [PATCH 02/74] "Add Variation" button, SongMetadata format changes, bug fixes, resolve metadata loading issues. --- assets | 2 +- hmm.json | 2 +- source/funkin/Paths.hx | 13 +- source/funkin/data/BaseRegistry.hx | 83 +-- source/funkin/data/DataError.hx | 75 ++ source/funkin/data/DataParse.hx | 107 ++- source/funkin/data/DataWrite.hx | 11 +- source/funkin/data/animation/AnimationData.hx | 3 +- source/funkin/data/level/LevelRegistry.hx | 20 + .../data/notestyle/NoteStyleRegistry.hx | 20 + source/funkin/data/song/SongData.hx | 147 +++- source/funkin/data/song/SongDataUtils.hx | 3 + source/funkin/data/song/SongRegistry.hx | 161 ++++- .../data/song/importer/FNFLegacyData.hx | 124 ++++ .../data/song/importer/FNFLegacyImporter.hx | 202 ++++++ .../data/song/migrator/SongDataMigrator.hx | 66 ++ .../data/song/migrator/SongData_v2_0_0.hx | 122 ++++ source/funkin/play/PlayState.hx | 36 +- .../cutscene/dialogue/ConversationData.hx | 6 +- .../dialogue/ConversationDataParser.hx | 1 + source/funkin/play/song/Song.hx | 79 +-- source/funkin/play/song/SongMigrator.hx | 256 ------- source/funkin/play/song/SongSerializer.hx | 134 +--- source/funkin/play/song/SongValidator.hx | 149 ---- source/funkin/play/song/formats/FNFLegacy.hx | 131 ---- .../debug/charting/ChartEditorAudioHandler.hx | 170 +++++ .../ui/debug/charting/ChartEditorCommand.hx | 18 +- .../charting/ChartEditorDialogHandler.hx | 658 ++++++++++++------ .../ui/debug/charting/ChartEditorDropdowns.hx | 129 ++++ .../ChartEditorImportExportHandler.hx | 195 ++++++ .../debug/charting/ChartEditorNoteSprite.hx | 34 +- .../ui/debug/charting/ChartEditorState.hx | 511 ++++---------- .../charting/ChartEditorToolboxHandler.hx | 116 +-- source/funkin/util/Constants.hx | 71 +- source/funkin/util/FileUtil.hx | 49 +- source/funkin/util/SerializerUtil.hx | 1 + 36 files changed, 2343 insertions(+), 1562 deletions(-) create mode 100644 source/funkin/data/DataError.hx create mode 100644 source/funkin/data/song/importer/FNFLegacyData.hx create mode 100644 source/funkin/data/song/importer/FNFLegacyImporter.hx create mode 100644 source/funkin/data/song/migrator/SongDataMigrator.hx create mode 100644 source/funkin/data/song/migrator/SongData_v2_0_0.hx delete mode 100644 source/funkin/play/song/SongMigrator.hx delete mode 100644 source/funkin/play/song/SongValidator.hx delete mode 100644 source/funkin/play/song/formats/FNFLegacy.hx create mode 100644 source/funkin/ui/debug/charting/ChartEditorAudioHandler.hx create mode 100644 source/funkin/ui/debug/charting/ChartEditorDropdowns.hx create mode 100644 source/funkin/ui/debug/charting/ChartEditorImportExportHandler.hx diff --git a/assets b/assets index a62e7e50d..7e698e3dd 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a62e7e50d59c14d256c75da651b79dea77e1620e +Subproject commit 7e698e3dd51443f0bb8aa4105596f8e87eca4a9b diff --git a/hmm.json b/hmm.json index 76ea0eeda..ce47812ad 100644 --- a/hmm.json +++ b/hmm.json @@ -98,7 +98,7 @@ "type": "git", "dir": null, "ref": "f4df19cfa196f85eece55c3367021fc965f1fa9a", - "url": "https://github.com/elnabo/json2object" + "url": "https://github.com/EliteMasterEric/json2object" }, { "name": "lime", diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index ee2dfe5fd..c8c9c79b7 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -6,9 +6,6 @@ import openfl.utils.Assets as OpenFlAssets; class Paths { - public static var SOUND_EXT = #if web "mp3" #else "ogg" #end; - public static var VIDEO_EXT = "mp4"; - static var currentLevel:String; static public function setCurrentLevel(name:String) @@ -84,7 +81,7 @@ class Paths static public function sound(key:String, ?library:String) { - return getPath('sounds/$key.$SOUND_EXT', SOUND, library); + return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library); } inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) @@ -94,24 +91,24 @@ class Paths inline static public function music(key:String, ?library:String) { - return getPath('music/$key.$SOUND_EXT', MUSIC, library); + return getPath('music/$key.${Constants.EXT_SOUND}', MUSIC, library); } inline static public function videos(key:String, ?library:String) { - return getPath('videos/$key.$VIDEO_EXT', BINARY, library); + return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library); } inline static public function voices(song:String, ?suffix:String = '') { if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files - return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.$SOUND_EXT'; + return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}'; } inline static public function inst(song:String, ?suffix:String = '') { - return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.$SOUND_EXT'; + return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}'; } inline static public function image(key:String, ?library:String) diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx index 24d0de476..70615069b 100644 --- a/source/funkin/data/BaseRegistry.hx +++ b/source/funkin/data/BaseRegistry.hx @@ -4,9 +4,6 @@ import openfl.Assets; import funkin.util.assets.DataAssets; import funkin.util.VersionUtil; import haxe.Constraints.Constructible; -import json2object.Position; -import json2object.Position.Line; -import json2object.Error; /** * The entry's constructor function must take a single argument, the entry's ID. @@ -179,6 +176,15 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo */ public abstract function parseEntryData(id:String):Null<J>; + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + */ + public abstract function parseEntryDataRaw(contents:String, ?fileName:String):Null<J>; + /** * Read, parse, and validate the JSON data and produce the corresponding data object, * accounting for old versions of the data. @@ -226,79 +232,12 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo */ abstract function createScriptedEntry(clsName:String):Null<T>; - function printErrors(errors:Array<Error>, id:String = ''):Void + function printErrors(errors:Array<json2object.Error>, id:String = ''):Void { trace('[${registryId}] Failed to parse entry data: ${id}'); for (error in errors) - printError(error); - } - - function printError(error:Error):Void - { - switch (error) - { - case IncorrectType(vari, expected, pos): - trace(' Expected field "$vari" to be of type "$expected".'); - printPos(pos); - case IncorrectEnumValue(value, expected, pos): - trace(' Invalid enum value (expected "$expected", got "$value")'); - printPos(pos); - case InvalidEnumConstructor(value, expected, pos): - trace(' Invalid enum constructor (epxected "$expected", got "$value")'); - printPos(pos); - case UninitializedVariable(vari, pos): - trace(' Uninitialized variable "$vari"'); - printPos(pos); - case UnknownVariable(vari, pos): - trace(' Unknown variable "$vari"'); - printPos(pos); - case ParserError(message, pos): - trace(' Parsing error: ${message}'); - printPos(pos); - case CustomFunctionException(e, pos): - if (Std.isOfType(e, String)) - { - trace(' ${e}'); - } - else - { - printUnknownError(e); - } - printPos(pos); - default: - printUnknownError(error); - } - } - - function printUnknownError(e:Dynamic):Void - { - switch (Type.typeof(e)) - { - case TClass(c): - trace(' [${Type.getClassName(c)}] ${e.toString()}'); - case TEnum(c): - trace(' [${Type.getEnumName(c)}] ${e.toString()}'); - default: - trace(' [${Type.typeof(e)}] ${e.toString()}'); - } - } - - /** - * TODO: Figure out the nicest way to print this. - * Maybe look up how other JSON parsers format their errors? - * @see https://github.com/elnabo/json2object/blob/master/src/json2object/Position.hx - */ - function printPos(pos:Position):Void - { - if (pos.lines[0].number == pos.lines[pos.lines.length - 1].number) - { - trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}'); - } - else - { - trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}-${pos.lines[pos.lines.length - 1].number}'); - } + DataError.printError(error); } } diff --git a/source/funkin/data/DataError.hx b/source/funkin/data/DataError.hx new file mode 100644 index 000000000..87c99fff5 --- /dev/null +++ b/source/funkin/data/DataError.hx @@ -0,0 +1,75 @@ +package funkin.data; + +import json2object.Position; +import json2object.Position.Line; +import json2object.Error; + +class DataError +{ + public static function printError(error:Error):Void + { + switch (error) + { + case IncorrectType(vari, expected, pos): + trace(' Expected field "$vari" to be of type "$expected".'); + printPos(pos); + case IncorrectEnumValue(value, expected, pos): + trace(' Invalid enum value (expected "$expected", got "$value")'); + printPos(pos); + case InvalidEnumConstructor(value, expected, pos): + trace(' Invalid enum constructor (epxected "$expected", got "$value")'); + printPos(pos); + case UninitializedVariable(vari, pos): + trace(' Uninitialized variable "$vari"'); + printPos(pos); + case UnknownVariable(vari, pos): + trace(' Unknown variable "$vari"'); + printPos(pos); + case ParserError(message, pos): + trace(' Parsing error: ${message}'); + printPos(pos); + case CustomFunctionException(e, pos): + if (Std.isOfType(e, String)) + { + trace(' ${e}'); + } + else + { + printUnknownError(e); + } + printPos(pos); + default: + printUnknownError(error); + } + } + + public static function printUnknownError(e:Dynamic):Void + { + switch (Type.typeof(e)) + { + case TClass(c): + trace(' [${Type.getClassName(c)}] ${e.toString()}'); + case TEnum(c): + trace(' [${Type.getEnumName(c)}] ${e.toString()}'); + default: + trace(' [${Type.typeof(e)}] ${e.toString()}'); + } + } + + /** + * TODO: Figure out the nicest way to print this. + * Maybe look up how other JSON parsers format their errors? + * @see https://github.com/elnabo/json2object/blob/master/src/json2object/Position.hx + */ + static function printPos(pos:Position):Void + { + if (pos.lines[0].number == pos.lines[pos.lines.length - 1].number) + { + trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}'); + } + else + { + trace(' at ${(pos.file == '') ? 'line ' : '${pos.file}:'}${pos.lines[0].number}-${pos.lines[pos.lines.length - 1].number}'); + } + } +} diff --git a/source/funkin/data/DataParse.hx b/source/funkin/data/DataParse.hx index f6b5dd659..4a422b368 100644 --- a/source/funkin/data/DataParse.hx +++ b/source/funkin/data/DataParse.hx @@ -1,7 +1,13 @@ 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.LegacyScrollSpeeds; /** * `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values. @@ -39,36 +45,40 @@ class DataParse */ public static function dynamicValue(json:Json, name:String):Dynamic { - return jsonToDynamic(json); + return Tools.getValue(json); } /** - * Parser which outputs a Dynamic value, which must be an object with properties. - * @param json - * @param name - * @return Dynamic + * Parser which outputs a `Either<Array<LegacyNoteSection>, LegacyNoteData>`. + * Used by the FNF legacy JSON importer. */ - public static function dynamicObject(json:Json, name:String):Dynamic + public static function eitherLegacyNoteData(json:Json, name:String):Either<Array<LegacyNoteSection>, LegacyNoteData> { switch (json.value) { + case JArray(values): + return Either.Left(legacyNoteSectionArray(json, name)); case JObject(fields): - return jsonFieldsToDynamicObject(fields); + return Either.Right(cast Tools.getValue(json)); default: - throw 'Expected property $name to be an object, but it was ${json.value}.'; + throw 'Expected property $name to be note data, but it was ${json.value}.'; } } - static function jsonToDynamic(json:Json):Null<Dynamic> + /** + * Parser which outputs a `Either<Float, LegacyScrollSpeeds>`. + * Used by the FNF legacy JSON importer. + */ + public static function eitherLegacyScrollSpeeds(json:Json, name:String):Either<Float, LegacyScrollSpeeds> { - return switch (json.value) + switch (json.value) { - case JString(s): s; - case JNumber(n): Std.parseInt(n); - case JBool(b): b; - case JNull: null; - case JObject(fields): jsonFieldsToDynamicObject(fields); - case JArray(values): jsonArrayToDynamicArray(values); + case JNumber(f): + return Either.Left(Std.parseFloat(f)); + case JObject(fields): + return Either.Right(cast Tools.getValue(json)); + default: + throw 'Expected property $name to be scroll speeds, but it was ${json.value}.'; } } @@ -82,7 +92,7 @@ class DataParse var result:Dynamic = {}; for (field in fields) { - Reflect.setField(result, field.name, jsonToDynamic(field.value)); + Reflect.setField(result, field.name, Tools.getValue(field.value)); } return result; } @@ -94,6 +104,67 @@ class DataParse */ static function jsonArrayToDynamicArray(jsons:Array<Json>):Array<Null<Dynamic>> { - return [for (json in jsons) jsonToDynamic(json)]; + return [for (json in jsons) Tools.getValue(json)]; + } + + static function legacyNoteSectionArray(json:Json, name:String):Array<LegacyNoteSection> + { + switch (json.value) + { + case JArray(values): + return [for (value in values) legacyNoteSection(value, name)]; + default: + throw 'Expected property to be an array, but it was ${json.value}.'; + } + } + + static function legacyNoteSection(json:Json, name:String):LegacyNoteSection + { + switch (json.value) + { + case JObject(fields): + return cast Tools.getValue(json); + default: + throw 'Expected property $name to be an object, but it was ${json.value}.'; + } + } + + public static function legacyNoteData(json:Json, name:String):LegacyNoteData + { + switch (json.value) + { + case JObject(fields): + return cast Tools.getValue(json); + default: + throw 'Expected property $name to be an object, but it was ${json.value}.'; + } + } + + public static function legacyNotes(json:Json, name:String):Array<LegacyNote> + { + switch (json.value) + { + case JArray(values): + return [for (value in values) legacyNote(value, name)]; + default: + throw 'Expected property $name to be an array of notes, but it was ${json.value}.'; + } + } + + public static function legacyNote(json:Json, name:String):LegacyNote + { + switch (json.value) + { + case JArray(values): + // var time:Null<Float> = values[0] == null ? null : Tools.getValue(values[0]); + // var data:Null<Int> = values[1] == null ? null : Tools.getValue(values[1]); + // var length:Null<Float> = values[2] == null ? null : Tools.getValue(values[2]); + // var alt:Null<Bool> = values[3] == null ? null : Tools.getValue(values[3]); + + // return new LegacyNote(time, data, length, alt); + return null; + default: + throw 'Expected property $name to be a note, but it was ${json.value}.'; + } } } diff --git a/source/funkin/data/DataWrite.hx b/source/funkin/data/DataWrite.hx index 2ff7672da..41993107f 100644 --- a/source/funkin/data/DataWrite.hx +++ b/source/funkin/data/DataWrite.hx @@ -1,8 +1,17 @@ package funkin.data; +import funkin.util.SerializerUtil; + /** * `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON. * * Functions must be of the signature `(T) -> String`, where `T` is the type of the property. */ -class DataWrite {} +class DataWrite +{ + public static function dynamicValue(value:Dynamic):String + { + // Is this cheating? Yes. Do I care? No. + return SerializerUtil.toJSON(value); + } +} diff --git a/source/funkin/data/animation/AnimationData.hx b/source/funkin/data/animation/AnimationData.hx index 2116109db..9765f784c 100644 --- a/source/funkin/data/animation/AnimationData.hx +++ b/source/funkin/data/animation/AnimationData.hx @@ -67,7 +67,6 @@ typedef UnnamedAnimationData = * ONLY for use by MultiSparrow characters. * @default The assetPath of the parent sprite */ - @:default(null) @:optional var assetPath:Null<String>; @@ -85,7 +84,7 @@ typedef UnnamedAnimationData = */ @:default(false) @:optional - var looped:Null<Bool>; + var looped:Bool; /** * Whether the animation's sprites should be flipped horizontally. diff --git a/source/funkin/data/level/LevelRegistry.hx b/source/funkin/data/level/LevelRegistry.hx index d135e1241..75b0b11f6 100644 --- a/source/funkin/data/level/LevelRegistry.hx +++ b/source/funkin/data/level/LevelRegistry.hx @@ -47,6 +47,26 @@ class LevelRegistry extends BaseRegistry<Level, LevelData> return parser.value; } + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String):Null<LevelData> + { + var parser = new json2object.JsonParser<LevelData>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + function createScriptedEntry(clsName:String):Level { return ScriptedLevel.init(clsName, "unknown"); diff --git a/source/funkin/data/notestyle/NoteStyleRegistry.hx b/source/funkin/data/notestyle/NoteStyleRegistry.hx index bb594bca4..da45da5f2 100644 --- a/source/funkin/data/notestyle/NoteStyleRegistry.hx +++ b/source/funkin/data/notestyle/NoteStyleRegistry.hx @@ -54,6 +54,26 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> return parser.value; } + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String):Null<NoteStyleData> + { + var parser = new json2object.JsonParser<NoteStyleData>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + function createScriptedEntry(clsName:String):NoteStyle { return ScriptedNoteStyle.init(clsName, "unknown"); diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 59f8fcaf1..5eb221094 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1,8 +1,6 @@ package funkin.data.song; import flixel.util.typeLimit.OneOfTwo; -import funkin.play.song.SongMigrator; -import funkin.play.song.SongValidator; import funkin.data.song.SongRegistry; import thx.semver.Version; @@ -47,32 +45,33 @@ class SongMetadata * Defaults to `default` or `''`. Populated later. */ @:jignored - public var variation:String = 'default'; + public var variation:String; public function new(songName:String, artist:String, variation:String = 'default') { - this.version = SongMigrator.CHART_VERSION; + this.version = SongRegistry.SONG_METADATA_VERSION; this.songName = songName; this.artist = artist; this.timeFormat = 'ms'; this.divisions = null; this.timeChanges = [new SongTimeChange(0, 100)]; this.looped = false; - this.playData = - { - songVariations: [], - difficulties: ['normal'], - - playableChars: ['bf' => new SongPlayableChar('gf', 'dad')], - - stage: 'mainStage', - noteSkin: 'Normal' - }; + this.playData = new SongPlayData(); + this.playData.songVariations = []; + this.playData.difficulties = []; + this.playData.characters = new SongCharacterData('bf', 'gf', 'dad'); + this.playData.stage = 'mainStage'; + this.playData.noteSkin = 'funkin'; this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; // Variation ID. this.variation = variation; } + /** + * Create a copy of this SongMetadata with the same information. + * @param newVariation Set to a new variation ID to change the new metadata. + * @return The cloned SongMetadata + */ public function clone(?newVariation:String = null):SongMetadata { var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation); @@ -87,6 +86,21 @@ class SongMetadata return result; } + /** + * Serialize this SongMetadata into a JSON string. + * @return The JSON string. + */ + public function serialize(?pretty:Bool = true):String + { + var writer = new json2object.JsonWriter<SongMetadata>(); + var output = this.clone(); + output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer. + return writer.write(output, pretty ? ' ' : null); + } + + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { return 'SongMetadata(${this.songName} by ${this.artist}, variation ${this.variation})'; @@ -121,7 +135,6 @@ class SongTimeChange */ @:optional @:alias("b") - // @:default(funkin.data.song.SongData.SongTimeChange.DEFAULT_BEAT_TIME) public var beatTime:Null<Float>; /** @@ -168,6 +181,9 @@ class SongTimeChange this.beatTuplets = beatTuplets == null ? DEFAULT_BEAT_TUPLETS : beatTuplets; } + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { return 'SongTimeChange(${this.timeStamp}ms,${this.bpm}bpm)'; @@ -199,7 +215,7 @@ class SongMusicData @:optional @:default(false) - public var looped:Bool; + public var looped:Null<Bool>; // @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) public var generatedBy:String; @@ -218,7 +234,7 @@ class SongMusicData public function new(songName:String, artist:String, variation:String = 'default') { - this.version = SongMigrator.CHART_VERSION; + this.version = SongRegistry.SONG_CHART_DATA_VERSION; this.songName = songName; this.artist = artist; this.timeFormat = 'ms'; @@ -243,53 +259,106 @@ class SongMusicData return result; } + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { return 'SongMusicData(${this.songName} by ${this.artist}, variation ${this.variation})'; } } -typedef SongPlayData = +class SongPlayData { + /** + * The variations this song has. The associated metadata files should exist. + */ public var songVariations:Array<String>; + + /** + * The difficulties contained in this song's chart file. + */ public var difficulties:Array<String>; /** - * Keys are the player characters and the values give info on what opponent/GF/inst to use. + * The characters used by this song. */ - public var playableChars:Map<String, SongPlayableChar>; + public var characters:SongCharacterData; + /** + * The stage used by this song. + */ public var stage:String; + + /** + * The note style used by this song. + * TODO: Rename to `noteStyle`? Renaming values is a breaking change to the metadata format. + */ public var noteSkin:String; + + /** + * The difficulty rating for this song as displayed in Freeplay. + * TODO: Adding this is a non-breaking change to the metadata format. + */ + // public var rating:Int; + + /** + * The album ID for the album to display in Freeplay. + * TODO: Adding this is a non-breaking change to the metadata format. + */ + // public var album:String; + + public function new() {} + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongPlayData(${this.songVariations}, ${this.difficulties})'; + } } -class SongPlayableChar +/** + * Information about the characters used in this variation of the song. + * Create a new variation if you want to change the characters. + */ +class SongCharacterData { - @:alias('g') + @:optional + @:default('') + public var player:String = ''; + @:optional @:default('') public var girlfriend:String = ''; - @:alias('o') @:optional @:default('') public var opponent:String = ''; - @:alias('i') @:optional @:default('') - public var inst:String = ''; + public var instrumental:String = ''; - public function new(girlfriend:String = '', opponent:String = '', inst:String = '') + @:optional + @:default([]) + public var altInstrumentals:Array<String> = []; + + public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '') { + this.player = player; this.girlfriend = girlfriend; this.opponent = opponent; - this.inst = inst; + this.instrumental = instrumental; } + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { - return 'SongPlayableChar(${this.girlfriend}, ${this.opponent}, ${this.inst})'; + return 'SongCharacterData(${this.player}, ${this.girlfriend}, ${this.opponent}, ${this.instrumental}, [${this.altInstrumentals.join(', ')}])'; } } @@ -346,14 +415,21 @@ class SongChartData return value; } - public function getEvents():Array<SongEventData> + /** + * Convert this SongChartData into a JSON string. + */ + public function serialize(?pretty:Bool = true):String { - return this.events; + var writer = new json2object.JsonWriter<SongChartData>(); + return writer.write(this, pretty ? ' ' : null); } - public function setEvents(value:Array<SongEventData>):Array<SongEventData> + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String { - return this.events = value; + return 'SongChartData(${this.events.length} events, ${this.notes.size()} difficulties, ${generatedBy})'; } } @@ -387,6 +463,7 @@ class SongEventData @:alias("v") @:optional @:jcustomparse(funkin.data.DataParse.dynamicValue) + @:jcustomwrite(funkin.data.DataWrite.dynamicValue) public var value:Dynamic = null; /** @@ -484,6 +561,9 @@ class SongEventData return this.time <= other.time; } + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { return 'SongEventData(${this.time}ms, ${this.event}: ${this.value})'; @@ -703,6 +783,9 @@ class SongNoteData return this.time <= other.time; } + /** + * Produces a string representation suitable for debugging. + */ public function toString():String { return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index d15a2b19a..4b9318df2 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -8,6 +8,9 @@ import funkin.util.SerializerUtil; using Lambda; +/** + * Utility functions for working with song data, including note data, event data, metadata, etc. + */ class SongDataUtils { /** diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 9bc1278c8..1a7c41d33 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -1,6 +1,7 @@ package funkin.data.song; import funkin.data.song.SongData; +import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongMetadata; import funkin.play.song.ScriptedSong; @@ -8,6 +9,8 @@ import funkin.play.song.Song; import funkin.util.assets.DataAssets; import funkin.util.VersionUtil; +using funkin.data.song.migrator.SongDataMigrator; + class SongRegistry extends BaseRegistry<Song, SongMetadata> { /** @@ -15,14 +18,18 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> * Handle breaking changes by incrementing this value * and adding migration to the `migrateStageData()` function. */ - public static final SONG_METADATA_VERSION:thx.semver.Version = "2.0.0"; + public static final SONG_METADATA_VERSION:thx.semver.Version = "2.1.0"; - public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; + public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.1.x"; public static final SONG_CHART_DATA_VERSION:thx.semver.Version = "2.0.0"; public static final SONG_CHART_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; + public static final SONG_MUSIC_DATA_VERSION:thx.semver.Version = "2.0.0"; + + public static final SONG_MUSIC_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; + public static var DEFAULT_GENERATEDBY(get, null):String; static function get_DEFAULT_GENERATEDBY():String @@ -30,6 +37,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return '${Constants.TITLE} - ${Constants.VERSION}'; } + /** + * TODO: What if there was a Singleton macro which created static functions + * that redirected to the instance? + */ public static final instance:SongRegistry = new SongRegistry(); public function new() @@ -101,13 +112,21 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parseEntryMetadata(id); } + /** + * Parse, and validate the JSON data and produce the corresponding data object. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String = 'raw'):Null<SongMetadata> + { + return parseEntryMetadataRaw(contents); + } + public function parseEntryMetadata(id:String, variation:String = ""):Null<SongMetadata> { // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser<SongMetadata>(); - switch (loadEntryMetadataFile(id)) + switch (loadEntryMetadataFile(id, variation)) { case {fileName: fileName, contents: contents}: parser.fromJson(contents, fileName); @@ -123,6 +142,19 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parser.value; } + public function parseEntryMetadataRaw(contents:String, ?fileName:String = 'raw'):Null<SongMetadata> + { + var parser = new json2object.JsonParser<SongMetadata>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + public function parseEntryMetadataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongMetadata> { // If a version rule is not specified, do not check against it. @@ -130,19 +162,73 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> { return parseEntryMetadata(id); } + else if (VersionUtil.validateVersion(version, "2.0.x")) + { + return parseEntryMetadata_v2_0_0(id); + } else { throw '[${registryId}] Metadata entry ${id}:${variation == '' ? 'default' : variation} does not support migration to version ${SONG_METADATA_VERSION_RULE}.'; } } + public function parseEntryMetadataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongMetadata> + { + // If a version rule is not specified, do not check against it. + if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE)) + { + return parseEntryMetadataRaw(contents, fileName); + } + else if (VersionUtil.validateVersion(version, "2.0.x")) + { + return parseEntryMetadataRaw_v2_0_0(contents, fileName); + } + else + { + throw '[${registryId}] Metadata entry "${fileName}" does not support migration to version ${SONG_METADATA_VERSION_RULE}.'; + } + } + + function parseEntryMetadata_v2_0_0(id:String, variation:String = ""):Null<SongMetadata> + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser = new json2object.JsonParser<SongMetadata_v2_0_0>(); + switch (loadEntryMetadataFile(id)) + { + case {fileName: fileName, contents: contents}: + parser.fromJson(contents, fileName); + default: + return null; + } + if (parser.errors.length > 0) + { + printErrors(parser.errors, id); + return null; + } + return parser.value.migrate(); + } + + function parseEntryMetadataRaw_v2_0_0(contents:String, ?fileName:String = 'raw'):Null<SongMetadata> + { + var parser = new json2object.JsonParser<SongMetadata_v2_0_0>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value.migrate(); + } + public function parseMusicData(id:String, variation:String = ""):Null<SongMusicData> { // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser<SongMusicData>(); - switch (loadMusicDataFile(id)) + switch (loadMusicDataFile(id, variation)) { case {fileName: fileName, contents: contents}: parser.fromJson(contents, fileName); @@ -158,13 +244,52 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parser.value; } + public function parseMusicDataRaw(contents:String, ?fileName:String = 'raw'):Null<SongMusicData> + { + var parser = new json2object.JsonParser<SongMusicData>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + + public function parseMusicDataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongMusicData> + { + // If a version rule is not specified, do not check against it. + if (SONG_MUSIC_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_MUSIC_DATA_VERSION_RULE)) + { + return parseMusicData(id, variation); + } + else + { + throw '[${registryId}] Chart entry ${id}:${variation == '' ? 'default' : variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; + } + } + + public function parseMusicDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongMusicData> + { + // If a version rule is not specified, do not check against it. + if (SONG_MUSIC_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_MUSIC_DATA_VERSION_RULE)) + { + return parseMusicDataRaw(contents, fileName); + } + else + { + throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; + } + } + public function parseEntryChartData(id:String, variation:String = ''):Null<SongChartData> { // JsonParser does not take type parameters, // otherwise this function would be in BaseRegistry. var parser = new json2object.JsonParser<SongChartData>(); - switch (loadEntryChartFile(id)) + switch (loadEntryChartFile(id, variation)) { case {fileName: fileName, contents: contents}: parser.fromJson(contents, fileName); @@ -180,6 +305,19 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parser.value; } + public function parseEntryChartDataRaw(contents:String, ?fileName:String = 'raw'):Null<SongChartData> + { + var parser = new json2object.JsonParser<SongChartData>(); + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + public function parseEntryChartDataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongChartData> { // If a version rule is not specified, do not check against it. @@ -193,6 +331,19 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> } } + public function parseEntryChartDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongChartData> + { + // If a version rule is not specified, do not check against it. + if (SONG_CHART_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_CHART_DATA_VERSION_RULE)) + { + return parseEntryChartDataRaw(contents, fileName); + } + else + { + throw '[${registryId}] Chart entry "${fileName}" does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; + } + } + function createScriptedEntry(clsName:String):Song { return ScriptedSong.init(clsName, "unknown"); diff --git a/source/funkin/data/song/importer/FNFLegacyData.hx b/source/funkin/data/song/importer/FNFLegacyData.hx new file mode 100644 index 000000000..5b75368c9 --- /dev/null +++ b/source/funkin/data/song/importer/FNFLegacyData.hx @@ -0,0 +1,124 @@ +package funkin.data.song.importer; + +import haxe.ds.Either; + +/** + * A data structure representing a song in the old chart format. + * This only works for charts compatible with Week 7, so you'll need a custom program + * to handle importing charts from mods or other engines. + */ +class FNFLegacyData +{ + public var song:LegacySongData; +} + +class LegacySongData +{ + public var player1:String; // Boyfriend + public var player2:String; // Opponent + + @:jcustomparse(funkin.data.DataParse.eitherLegacyScrollSpeeds) + public var speed:Either<Float, LegacyScrollSpeeds>; + public var stageDefault:String; + public var bpm:Float; + + @:jcustomparse(funkin.data.DataParse.eitherLegacyNoteData) + public var notes:Either<Array<LegacyNoteSection>, LegacyNoteData>; + public var song:String; // Song name + + public function new() {} + + public function toString():String + { + var notesStr:String = switch (notes) + { + case Left(sections): 'single difficulty w/ ${sections.length} sections'; + case Right(data): + var difficultyCount:Int = 0; + if (data.easy != null) difficultyCount++; + if (data.normal != null) difficultyCount++; + if (data.hard != null) difficultyCount++; + '${difficultyCount} difficulties'; + }; + return 'LegacySongData($player1, $player2, $notesStr)'; + } +} + +typedef LegacyScrollSpeeds = +{ + public var ?easy:Float; + public var ?normal:Float; + public var ?hard:Float; +}; + +typedef LegacyNoteData = +{ + /** + * The easy difficulty. + */ + public var ?easy:Array<LegacyNoteSection>; + + /** + * The normal difficulty. + */ + public var ?normal:Array<LegacyNoteSection>; + + /** + * The hard difficulty. + */ + public var ?hard:Array<LegacyNoteSection>; +}; + +typedef LegacyNoteSection = +{ + /** + * Whether the section is a must-hit section. + * If true, 0-3 are boyfriends notes, 4-7 are opponents notes. + * If false, 0-3 are opponents notes, 4-7 are boyfriends notes. + */ + public var mustHitSection:Bool; + + /** + * Array of note data: + * - Direction + * - Time (ms) + * - Sustain Duration (ms) + * - Note kind (true = "alt", or string) + */ + public var sectionNotes:Array<LegacyNote>; + + public var ?typeOfSection:Int; + + public var ?lengthInSteps:Int; + + // BPM changes + public var ?changeBPM:Bool; + public var ?bpm:Float; +} + +/** + * Notes in the old format are stored as an Array<Dynamic> + * We use a custom parser to manage this. + */ +@:jcustomparse(funkin.data.DataParse.legacyNote) +class LegacyNote +{ + public var time:Float; + public var data:Int; + public var length:Float; + public var alt:Bool; + + public function new(time:Float, data:Int, ?length:Float, ?alt:Bool) + { + this.time = time; + this.data = data; + + this.length = length ?? 0.0; + this.alt = alt ?? false; + } + + public inline function getKind():String + { + return this.alt ? 'alt' : 'normal'; + } +} diff --git a/source/funkin/data/song/importer/FNFLegacyImporter.hx b/source/funkin/data/song/importer/FNFLegacyImporter.hx new file mode 100644 index 000000000..ee68513dc --- /dev/null +++ b/source/funkin/data/song/importer/FNFLegacyImporter.hx @@ -0,0 +1,202 @@ +package funkin.data.song.importer; // import is a reserved word dumbass + +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongData.SongChartData; +import funkin.data.song.SongData.SongCharacterData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongTimeChange; +import funkin.data.song.importer.FNFLegacyData; +import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection; + +class FNFLegacyImporter +{ + public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):FNFLegacyData + { + var parser = new json2object.JsonParser<FNFLegacyData>(); + parser.fromJson(input, fileName); + + if (parser.errors.length > 0) + { + trace('[FNFLegacyImporter] Error parsing JSON data from ' + fileName + ':'); + for (error in parser.errors) + DataError.printError(error); + return null; + } + return parser.value; + } + + /** + * @param data The raw parsed JSON data to migrate, as a Dynamic. + * @param difficulty + * @return SongMetadata + */ + public static function migrateMetadata(songData:FNFLegacyData, difficulty:String = 'normal'):SongMetadata + { + trace('Migrating song metadata from FNF Legacy.'); + + var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default'); + + var hadError:Bool = false; + + // Set generatedBy string for debugging. + songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)'; + + songMetadata.playData.stage = songData?.song?.stageDefault ?? 'mainStage'; + songMetadata.songName = songData?.song?.song ?? 'Import'; + songMetadata.playData.difficulties = []; + + if (songData?.song?.notes != null) + { + switch (songData.song.notes) + { + case Left(notes): + // One difficulty of notes. + songMetadata.playData.difficulties.push(difficulty); + case Right(difficulties): + if (difficulties.easy != null) songMetadata.playData.difficulties.push('easy'); + if (difficulties.normal != null) songMetadata.playData.difficulties.push('normal'); + if (difficulties.hard != null) songMetadata.playData.difficulties.push('hard'); + } + } + + songMetadata.playData.songVariations = []; + + songMetadata.timeChanges = rebuildTimeChanges(songData); + + songMetadata.playData.characters = new SongCharacterData(songData?.song?.player1 ?? 'bf', 'gf', songData?.song?.player2 ?? 'dad', 'mom'); + + return songMetadata; + } + + public static function migrateChartData(songData:FNFLegacyData, difficulty:String = 'normal'):SongChartData + { + trace('Migrating song chart data from FNF Legacy.'); + + var songChartData:SongChartData = new SongChartData([difficulty => 1.0], [], [difficulty => []]); + + if (songData?.song?.notes != null) + { + switch (songData.song.notes) + { + case Left(notes): + // One difficulty of notes. + songChartData.notes.set(difficulty, migrateNoteSections(notes)); + case Right(difficulties): + var baseDifficulty = null; + if (difficulties.easy != null) songChartData.notes.set('easy', migrateNoteSections(difficulties.easy)); + if (difficulties.normal != null) songChartData.notes.set('normal', migrateNoteSections(difficulties.normal)); + if (difficulties.hard != null) songChartData.notes.set('hard', migrateNoteSections(difficulties.hard)); + } + } + + // Import event data. + songChartData.events = rebuildEventData(songData); + + switch (songData.song.speed) + { + case Left(speed): + // All difficulties will use the one scroll speed. + songChartData.scrollSpeed.set('default', speed); + case Right(speeds): + if (speeds.easy != null) songChartData.scrollSpeed.set('easy', speeds.easy); + if (speeds.normal != null) songChartData.scrollSpeed.set('normal', speeds.normal); + if (speeds.hard != null) songChartData.scrollSpeed.set('hard', speeds.hard); + } + + return songChartData; + } + + /** + * FNF Legacy doesn't have song events, but without them the song won't look right, + * so we insert camera events when the character changes. + */ + static function rebuildEventData(songData:FNFLegacyData):Array<SongEventData> + { + var result:Array<SongEventData> = []; + + var noteSections = []; + switch (songData.song.notes) + { + case Left(notes): + // All difficulties will use the one scroll speed. + noteSections = notes; + case Right(difficulties): + if (difficulties.normal != null) noteSections = difficulties.normal; + if (difficulties.hard != null) noteSections = difficulties.normal; + if (difficulties.easy != null) noteSections = difficulties.normal; + } + + if (noteSections == null || noteSections.length == 0) return result; + + // Add camera events. + var lastSectionWasMustHit:Null<Bool> = null; + for (section in noteSections) + { + // Skip empty sections. + if (section.sectionNotes.length == 0) continue; + + if (section.mustHitSection != lastSectionWasMustHit) + { + lastSectionWasMustHit = section.mustHitSection; + + var firstNote:LegacyNote = section.sectionNotes[0]; + + result.push(new SongEventData(firstNote.time, 'FocusCamera', {char: section.mustHitSection ? 0 : 1})); + } + } + + return result; + } + + /** + * Port over time changes from FNF Legacy. + * If a section contains a BPM change, it will be applied at the timestamp of the first note in that section. + */ + static function rebuildTimeChanges(songData:FNFLegacyData):Array<SongTimeChange> + { + var result:Array<SongTimeChange> = []; + + result.push(new SongTimeChange(0, songData?.song?.bpm ?? Constants.DEFAULT_BPM)); + + var noteSections = []; + switch (songData.song.notes) + { + case Left(notes): + // All difficulties will use the one scroll speed. + noteSections = notes; + case Right(difficulties): + if (difficulties.normal != null) noteSections = difficulties.normal; + if (difficulties.hard != null) noteSections = difficulties.normal; + if (difficulties.easy != null) noteSections = difficulties.normal; + } + + if (noteSections == null || noteSections.length == 0) return result; + + for (noteSection in noteSections) + { + if (noteSection.changeBPM ?? false) + { + var firstNote:LegacyNote = noteSection.sectionNotes[0]; + if (firstNote != null) result.push(new SongTimeChange(firstNote.time, noteSection.bpm)); + } + } + + return result; + } + + static function migrateNoteSections(input:Array<LegacyNoteSection>):Array<SongNoteData> + { + var result:Array<SongNoteData> = []; + + for (section in input) + { + for (note in section.sectionNotes) + { + result.push(new SongNoteData(note.time, note.data, note.length, note.getKind())); + } + } + + return result; + } +} diff --git a/source/funkin/data/song/migrator/SongDataMigrator.hx b/source/funkin/data/song/migrator/SongDataMigrator.hx new file mode 100644 index 000000000..b5e08c832 --- /dev/null +++ b/source/funkin/data/song/migrator/SongDataMigrator.hx @@ -0,0 +1,66 @@ +package funkin.data.song.migrator; + +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongData.SongPlayData; +import funkin.data.song.SongData.SongCharacterData; +import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0; +import funkin.data.song.migrator.SongData_v2_0_0.SongPlayData_v2_0_0; +import funkin.data.song.migrator.SongData_v2_0_0.SongPlayableChar_v2_0_0; + +/** + * This class contains functions to migrate older data formats to the current one. + * + * Utilizes static extensions with overloaded inline functions to make migration as easy as `.migrate()`. + * @see https://try.haxe.org/#e1c1cf22 + */ +class SongDataMigrator +{ + public static overload extern inline function migrate(input:SongData_v2_0_0.SongMetadata_v2_0_0):SongMetadata + { + return migrate_SongMetadata_v2_0_0(input); + } + + public static function migrate_SongMetadata_v2_0_0(input:SongData_v2_0_0.SongMetadata_v2_0_0):SongMetadata + { + var result:SongMetadata = new SongMetadata(input.songName, input.artist, input.variation); + result.version = input.version; + result.timeFormat = input.timeFormat; + result.divisions = input.divisions; + result.timeChanges = input.timeChanges; + result.looped = input.looped; + result.playData = migrate_SongPlayData_v2_0_0(input.playData); + result.generatedBy = input.generatedBy; + + return result; + } + + public static overload extern inline function migrate(input:SongData_v2_0_0.SongPlayData_v2_0_0):SongPlayData + { + return migrate_SongPlayData_v2_0_0(input); + } + + public static function migrate_SongPlayData_v2_0_0(input:SongData_v2_0_0.SongPlayData_v2_0_0):SongPlayData + { + var result:SongPlayData = new SongPlayData(); + result.songVariations = input.songVariations; + result.difficulties = input.difficulties; + result.stage = input.stage; + result.noteSkin = input.noteSkin; + + // Fetch the first playable character and migrate it. + var firstCharKey:Null<String> = input.playableChars.size() == 0 ? null : input.playableChars.keys().array()[0]; + var firstCharData:Null<SongPlayableChar_v2_0_0> = input.playableChars.get(firstCharKey); + + if (firstCharData == null) + { + // Fill in a default playable character. + result.characters = new SongCharacterData('bf', 'gf', 'dad'); + } + else + { + result.characters = new SongCharacterData(firstCharKey, firstCharData.girlfriend, firstCharData.opponent, firstCharData.inst); + } + + return result; + } +} diff --git a/source/funkin/data/song/migrator/SongData_v2_0_0.hx b/source/funkin/data/song/migrator/SongData_v2_0_0.hx new file mode 100644 index 000000000..935e7349c --- /dev/null +++ b/source/funkin/data/song/migrator/SongData_v2_0_0.hx @@ -0,0 +1,122 @@ +package funkin.data.song.migrator; + +import thx.semver.Version; +import funkin.data.song.SongData; + +class SongMetadata_v2_0_0 +{ + // ========== + // MODIFIED VALUES + // =========== + + /** + * In metadata `v2.1.0`, `SongPlayData` was refactored. + */ + public var playData:SongPlayData_v2_0_0; + + /** + * In metadata `v2.1.0`, `variation` was set to `ignore` when writing. + */ + @:optional + @:default('default') + public var variation:String; + + // ========== + // UNMODIFIED VALUES + // ========== + public var version:Version; + + @:default("Unknown") + public var songName:String; + + @:default("Unknown") + public var artist:String; + + @:optional + @:default(96) + public var divisions:Null<Int>; // Optional field + + @:optional + @:default(false) + public var looped:Bool; + + public var generatedBy:String; + + public var timeFormat:SongData.SongTimeFormat; + + public var timeChanges:Array<SongData.SongTimeChange>; + + public function new() {} + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongMetadata[LEGACY:v2.0.0](${this.songName} by ${this.artist}, variation ${this.variation})'; + } +} + +class SongPlayData_v2_0_0 +{ + // ========== + // MODIFIED VALUES + // =========== + + /** + * In metadata version `v2.1.0`, this was refactored to a single `SongCharacterData` object. + */ + public var playableChars:Map<String, SongPlayableChar_v2_0_0>; + + // ========== + // UNMODIFIED VALUES + // ========== + public var songVariations:Array<String>; + public var difficulties:Array<String>; + + public var stage:String; + public var noteSkin:String; + + public function new() {} + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongPlayData[LEGACY:v2.0.0](${this.songVariations}, ${this.difficulties})'; + } +} + +class SongPlayableChar_v2_0_0 +{ + @:alias('g') + @:optional + @:default('') + public var girlfriend:String = ''; + + @:alias('o') + @:optional + @:default('') + public var opponent:String = ''; + + @:alias('i') + @:optional + @:default('') + public var inst:String = ''; + + public function new(girlfriend:String = '', opponent:String = '', inst:String = '') + { + this.girlfriend = girlfriend; + this.opponent = opponent; + this.inst = inst; + } + + /** + * Produces a string representation suitable for debugging. + */ + public function toString():String + { + return 'SongPlayableChar[LEGACY:v2.0.0](${this.girlfriend}, ${this.opponent}, ${this.inst})'; + } +} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 46938215b..4b40112f3 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -46,7 +46,7 @@ import funkin.play.song.Song; import funkin.data.song.SongRegistry; import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongPlayableChar; +import funkin.data.song.SongData.SongCharacterData; import funkin.play.stage.Stage; import funkin.play.stage.StageData.StageDataParser; import funkin.ui.PopUpStuff; @@ -574,8 +574,8 @@ class PlayState extends MusicBeatSubState // Prepare the current song's instrumental and vocals to be played. if (!overrideMusic && currentChart != null) { - currentChart.cacheInst(currentPlayerId); - currentChart.cacheVocals(currentPlayerId); + currentChart.cacheInst(); + currentChart.cacheVocals(); } // Prepare the Conductor. @@ -733,7 +733,7 @@ class PlayState extends MusicBeatSubState // DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM! // :nerd: um ackshually it's not 13 it's 11.97278911564 - if (Paths.SOUND_EXT == 'mp3') Conductor.offset = Constants.MP3_DELAY_MS; + if (Constants.EXT_SOUND == 'mp3') Conductor.offset = Constants.MP3_DELAY_MS; Conductor.update(); @@ -1344,34 +1344,20 @@ class PlayState extends MusicBeatSubState trace('Song difficulty could not be loaded.'); } - // Switch the character we are playing as by manipulating currentPlayerId. - // TODO: How to choose which one to use for story mode? - var playableChars:Array<String> = currentChart.getPlayableChars(); - - if (playableChars.length == 0) - { - trace('WARNING: No playable characters found for this song.'); - } - else if (playableChars.indexOf(currentPlayerId) == -1) - { - currentPlayerId = playableChars[0]; - } - - // - var currentCharData:SongPlayableChar = currentChart.getPlayableChar(currentPlayerId); + var currentCharacterData:SongCharacterData = currentChart.characters; // Switch the character we are playing as by manipulating currentPlayerId. // // GIRLFRIEND // - var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharData.girlfriend); + var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.girlfriend); if (girlfriend != null) { girlfriend.characterType = CharacterType.GF; } - else if (currentCharData.girlfriend != '') + else if (currentCharacterData.girlfriend != '') { - trace('WARNING: Could not load girlfriend character with ID ${currentCharData.girlfriend}, skipping...'); + trace('WARNING: Could not load girlfriend character with ID ${currentCharacterData.girlfriend}, skipping...'); } else { @@ -1381,7 +1367,7 @@ class PlayState extends MusicBeatSubState // // DAD // - var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharData.opponent); + var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.opponent); if (dad != null) { @@ -1400,7 +1386,7 @@ class PlayState extends MusicBeatSubState // // BOYFRIEND // - var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentPlayerId); + var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.player); if (boyfriend != null) { @@ -1549,7 +1535,7 @@ class PlayState extends MusicBeatSubState if (!overrideMusic) { - vocals = currentChart.buildVocals(currentPlayerId); + vocals = currentChart.buildVocals(); if (vocals.members.length == 0) { diff --git a/source/funkin/play/cutscene/dialogue/ConversationData.hx b/source/funkin/play/cutscene/dialogue/ConversationData.hx index 749f1b7a1..8c4aa9684 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationData.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationData.hx @@ -172,9 +172,13 @@ enum abstract BackdropType(String) from String to String class MusicData { public var asset:String; - public var looped:Bool; + public var fadeTime:Float; + @:optional + @:default(false) + public var looped:Bool; + public function new(asset:String, looped:Bool, fadeTime:Float = 0.0) { this.asset = asset; diff --git a/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx b/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx index c25b3e87f..9f80f8f9b 100644 --- a/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx +++ b/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx @@ -6,6 +6,7 @@ import funkin.play.cutscene.dialogue.ScriptedConversation; /** * Contains utilities for loading and parsing conversation data. + * TODO: Refactor to use the json2object + BaseRegistry system that actually validates things for you. */ class ConversationDataParser { diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index e32eb8186..6d645961d 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -11,7 +11,7 @@ import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongRegistry; import funkin.data.song.SongData.SongMetadata; -import funkin.data.song.SongData.SongPlayableChar; +import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongData.SongTimeFormat; import funkin.data.IRegistryEntry; @@ -176,18 +176,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta difficulty.generatedBy = metadata.generatedBy; difficulty.stage = metadata.playData.stage; - // difficulty.noteSkin = metadata.playData.noteSkin; + difficulty.noteStyle = metadata.playData.noteSkin; difficulties.set(diffId, difficulty); - difficulty.chars = new Map<String, SongPlayableChar>(); - if (metadata.playData.playableChars == null) continue; - for (charId in metadata.playData.playableChars.keys()) - { - var char:Null<SongPlayableChar> = metadata.playData.playableChars.get(charId); - if (char == null) continue; - difficulty.chars.set(charId, char); - } + difficulty.characters = metadata.playData.characters; } } } @@ -365,19 +358,20 @@ class SongDifficulty */ public var events:Array<SongEventData>; - public var songName:String = SongValidator.DEFAULT_SONGNAME; - public var songArtist:String = SongValidator.DEFAULT_ARTIST; - public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT; - public var divisions:Null<Int> = SongValidator.DEFAULT_DIVISIONS; - public var looped:Bool = SongValidator.DEFAULT_LOOPED; + public var songName:String = Constants.DEFAULT_SONGNAME; + public var songArtist:String = Constants.DEFAULT_ARTIST; + public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT; + public var divisions:Null<Int> = null; + public var looped:Bool = false; public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY; public var timeChanges:Array<SongTimeChange> = []; - public var stage:String = SongValidator.DEFAULT_STAGE; - public var chars:Map<String, SongPlayableChar> = null; + public var stage:String = Constants.DEFAULT_STAGE; + public var noteStyle:String = Constants.DEFAULT_NOTE_STYLE; + public var characters:SongCharacterData = null; - public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED; + public var scrollSpeed:Float = Constants.DEFAULT_SCROLLSPEED; public function new(song:Song, diffId:String, variation:String) { @@ -401,28 +395,24 @@ class SongDifficulty return timeChanges[0].bpm; } - public function getPlayableChar(id:String):Null<SongPlayableChar> - { - if (id == null || id == '') return null; - return chars.get(id); - } - - public function getPlayableChars():Array<String> - { - return chars.keys().array(); - } - public function getEvents():Array<SongEventData> { return cast events; } - public inline function cacheInst(?currentPlayerId:String = null):Void + public function cacheInst(instrumental = ''):Void { - var currentPlayer:Null<SongPlayableChar> = getPlayableChar(currentPlayerId); - if (currentPlayer != null) + if (characters != null) { - FlxG.sound.cache(Paths.inst(this.song.id, currentPlayer.inst)); + if (instrumental != '' && characters.altInstrumentals.contains(instrumental)) + { + FlxG.sound.cache(Paths.inst(this.song.id, instrumental)); + } + else + { + // Fallback to default instrumental. + FlxG.sound.cache(Paths.inst(this.song.id, characters.instrumental)); + } } else { @@ -440,9 +430,9 @@ class SongDifficulty * Cache the vocals for a given character. * @param id The character we are about to play. */ - public inline function cacheVocals(?id:String = 'bf'):Void + public inline function cacheVocals():Void { - for (voice in buildVoiceList(id)) + for (voice in buildVoiceList()) { FlxG.sound.cache(voice); } @@ -454,22 +444,15 @@ class SongDifficulty * * @param id The character we are about to play. */ - public function buildVoiceList(?id:String = 'bf'):Array<String> + public function buildVoiceList():Array<String> { - var playableCharData:SongPlayableChar = getPlayableChar(id); - if (playableCharData == null) - { - trace('Could not find playable char $id for song ${this.song.id}'); - return []; - } - var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : ''; // Automatically resolve voices by removing suffixes. // For example, if `Voices-bf-car.ogg` does not exist, check for `Voices-bf.ogg`. - var playerId:String = id; - var voicePlayer:String = Paths.voices(this.song.id, '-$id$suffix'); + var playerId:String = characters.player; + var voicePlayer:String = Paths.voices(this.song.id, '-$playerId$suffix'); while (voicePlayer != null && !Assets.exists(voicePlayer)) { // Remove the last suffix. @@ -479,7 +462,7 @@ class SongDifficulty voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix'); } - var opponentId:String = playableCharData.opponent; + var opponentId:String = characters.opponent; var voiceOpponent:String = Paths.voices(this.song.id, '-${opponentId}$suffix'); while (voiceOpponent != null && !Assets.exists(voiceOpponent)) { @@ -505,11 +488,11 @@ class SongDifficulty * @param charId The player ID. * @return The generated vocal group. */ - public function buildVocals(charId:String = 'bf'):VoicesGroup + public function buildVocals():VoicesGroup { var result:VoicesGroup = new VoicesGroup(); - var voiceList:Array<String> = buildVoiceList(charId); + var voiceList:Array<String> = buildVoiceList(); if (voiceList.length == 0) { diff --git a/source/funkin/play/song/SongMigrator.hx b/source/funkin/play/song/SongMigrator.hx deleted file mode 100644 index 43393fa4e..000000000 --- a/source/funkin/play/song/SongMigrator.hx +++ /dev/null @@ -1,256 +0,0 @@ -package funkin.play.song; - -import funkin.play.song.formats.FNFLegacy; -import funkin.data.song.SongData.SongChartData; -import funkin.data.song.SongData.SongEventData; -import funkin.data.song.SongData.SongMetadata; -import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongPlayableChar; -import funkin.util.VersionUtil; - -class SongMigrator -{ - /** - * The current latest version string for the song data format. - * Handle breaking changes by incrementing this value - * and adding migration to the SongMigrator class. - */ - public static final CHART_VERSION:String = '2.0.0'; - - /** - * Version rule for which chart versions are compatible with the current version. - */ - public static final CHART_VERSION_RULE:String = '2.0.x'; - - /** - * Migrate song data from an older chart version to the current version. - * @param jsonData The song metadata to migrate. - * @param songId The ID of the song (only used for error reporting). - * @return The migrated song metadata, or null if the migration failed. - */ - public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata - { - if (jsonData.version != null) - { - if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE)) - { - trace('Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.'); - - var songMetadata:SongMetadata = cast jsonData; - - return songMetadata; - } - else - { - trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.'); - switch (jsonData.version) - { - case '1.0.0': - return migrateSongMetadataFromLegacy(jsonData); - default: - trace('Song (${songId}) has unknown metadata version (${jsonData.version}), assuming FNF Legacy.'); - return migrateSongMetadataFromLegacy(jsonData); - } - } - } - else - { - trace('Song metadata version is missing.'); - } - return null; - } - - /** - * Migrate song chart data from an older chart version to the current version. - * @param jsonData The song chart data to migrate. - * @param songId The ID of the song (only used for error reporting). - * @return The migrated song chart data, or null if the migration failed. - */ - public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData - { - if (jsonData.version) - { - if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE)) - { - trace('Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.'); - - var songChartData:SongChartData = cast jsonData; - - return songChartData; - } - else - { - trace('Song (${songId}) chart version (${jsonData.version}) is outdated.'); - switch (jsonData.version) - { - // TODO: Add migration functions as cases here. - default: - // Unknown version. - trace('Song (${songId}) unknown chart version: ${jsonData.version}'); - } - } - } - else - { - trace('Song chart version is missing.'); - } - return null; - } - - /** - * Migrate song metadata from FNF Legacy chart version to the current version. - * @param jsonData The song metadata to migrate. - * @param songId The ID of the song (only used for error reporting). - * @return The migrated song metadata, or null if the migration failed. - */ - public static function migrateSongMetadataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongMetadata - { - trace('Migrating song metadata from FNF Legacy.'); - - var songData:FNFLegacy = cast jsonData; - - var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default'); - - var hadError:Bool = false; - - // Set generatedBy string for debugging. - songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)'; - - try - { - // Set the song's BPM. - songMetadata.timeChanges[0].bpm = songData.song.bpm; - } - catch (e) - { - trace("Couldn't parse BPM!"); - hadError = true; - } - - try - { - // Set the song's stage. - songMetadata.playData.stage = songData.song.stageDefault; - } - catch (e) - { - trace("Couldn't parse stage!"); - hadError = true; - } - - try - { - // Set's the song's name. - songMetadata.songName = songData.song.song; - } - catch (e) - { - trace("Couldn't parse song name!"); - hadError = true; - } - - songMetadata.playData.difficulties = []; - if (songData.song != null && songData.song.notes != null) - { - if (Std.isOfType(songData.song.notes, Array)) - { - // One difficulty of notes. - songMetadata.playData.difficulties.push(difficulty); - } - else - { - // Multiple difficulties of notes. - var songNoteDataDynamic:haxe.DynamicAccess<Dynamic> = cast songData.song.notes; - for (difficultyKey in songNoteDataDynamic.keys()) - { - songMetadata.playData.difficulties.push(difficultyKey); - } - } - } - else - { - trace("Couldn't parse difficulties!"); - hadError = true; - } - - songMetadata.playData.songVariations = []; - - // Set the song's song variations. - songMetadata.playData.playableChars = []; - try - { - songMetadata.playData.playableChars.set(songData.song.player1, new SongPlayableChar('', songData.song.player2)); - } - catch (e) - { - trace("Couldn't parse characters!"); - hadError = true; - } - - return songMetadata; - } - - /** - * Migrate song chart data from FNF Legacy chart version to the current version. - * @param jsonData The song data to migrate. - * @param songId The ID of the song (only used for error reporting). - * @param difficulty The difficulty to migrate. - * @return The migrated song chart data, or null if the migration failed. - */ - public static function migrateSongChartDataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongChartData - { - trace('Migrating song chart data from FNF Legacy.'); - - var songData:FNFLegacy = cast jsonData; - - var songChartData:SongChartData = new SongChartData(["normal" => 1.0], [], ["normal" => []]); - - var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0; - if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes)); - songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes), difficulty); - songChartData.setScrollSpeed(songData.song.speed, difficulty); - - return songChartData; - } - - static function migrateSongNoteDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongNoteData> - { - var songNotes:Array<SongNoteData> = []; - - for (section in sections) - { - // Skip empty sections. - if (section.sectionNotes.length == 0) continue; - - for (note in section.sectionNotes) - { - songNotes.push(new SongNoteData(note.time, note.getData(section.mustHitSection), note.length, note.kind)); - } - } - - return songNotes; - } - - static function migrateSongEventDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongEventData> - { - var songEvents:Array<SongEventData> = []; - - var lastSectionWasMustHit:Null<Bool> = null; - for (section in sections) - { - // Skip empty sections. - if (section.sectionNotes.length == 0) continue; - - if (section.mustHitSection != lastSectionWasMustHit) - { - lastSectionWasMustHit = section.mustHitSection; - - var firstNote:LegacyNote = section.sectionNotes[0]; - - songEvents.push(new SongEventData(firstNote.time, 'FocusCamera', {char: section.mustHitSection ? 0 : 1})); - } - } - - return songEvents; - } -} diff --git a/source/funkin/play/song/SongSerializer.hx b/source/funkin/play/song/SongSerializer.hx index a0a468c5b..10296e5b4 100644 --- a/source/funkin/play/song/SongSerializer.hx +++ b/source/funkin/play/song/SongSerializer.hx @@ -3,14 +3,14 @@ package funkin.play.song; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongMetadata; import funkin.util.SerializerUtil; +import funkin.util.FileUtil; import lime.utils.Bytes; import openfl.events.Event; import openfl.events.IOErrorEvent; import openfl.net.FileReference; /** - * Utilities for exporting a chart to a JSON file. - * Primarily used for the chart editor. + * TODO: Refactor and remove this. */ class SongSerializer { @@ -20,7 +20,7 @@ class SongSerializer */ public static function importSongChartDataSync(path:String):SongChartData { - var fileData = readFile(path); + var fileData = FileUtil.readStringFromPath(path); if (fileData == null) return null; @@ -35,7 +35,7 @@ class SongSerializer */ public static function importSongMetadataSync(path:String):SongMetadata { - var fileData = readFile(path); + var fileData = FileUtil.readStringFromPath(path); if (fileData == null) return null; @@ -50,7 +50,7 @@ class SongSerializer */ public static function importSongChartDataAsync(callback:SongChartData->Void):Void { - browseFileReference(function(fileReference:FileReference) { + FileUtil.browseFileReference(function(fileReference:FileReference) { var data = fileReference.data.toString(); if (data == null) return; @@ -67,7 +67,7 @@ class SongSerializer */ public static function importSongMetadataAsync(callback:SongMetadata->Void):Void { - browseFileReference(function(fileReference:FileReference) { + FileUtil.browseFileReference(function(fileReference:FileReference) { var data = fileReference.data.toString(); if (data == null) return; @@ -77,126 +77,4 @@ class SongSerializer if (songMetadata != null) callback(songMetadata); }); } - - /** - * Save a SongChartData object as a JSON file to an automatically generated path. - * Works great on HTML5 and desktop. - */ - public static function exportSongChartData(data:SongChartData, songId:String) - { - var path = '${songId}-chart.json'; - exportSongChartDataAs(path, data); - } - - /** - * Save a SongMetadata object as a JSON file to an automatically generated path. - * Works great on HTML5 and desktop. - */ - public static function exportSongMetadata(data:SongMetadata, songId:String) - { - var path = '${songId}-metadata.json'; - exportSongMetadataAs(path, data); - } - - /** - * Save a SongChartData object as a JSON file to a specified path. - * Works great on HTML5 and desktop. - * - * @param path The file path to save to. - */ - public static function exportSongChartDataAs(path:String, data:SongChartData) - { - var dataString = SerializerUtil.toJSON(data); - - writeFileReference(path, dataString); - } - - /** - * Save a SongMetadata object as a JSON file to a specified path. - * Works great on HTML5 and desktop. - * - * @param path The file path to save to. - */ - public static function exportSongMetadataAs(path:String, data:SongMetadata) - { - var dataString = SerializerUtil.toJSON(data); - - writeFileReference(path, dataString); - } - - /** - * Read the string contents of a file. - * Only works on desktop platforms. - * @param path The file path to read from. - */ - static function readFile(path:String):String - { - #if sys - var fileBytes:Bytes = sys.io.File.getBytes(path); - - if (fileBytes == null) return null; - - return fileBytes.toString(); - #end - - trace('ERROR: readFile not implemented for this platform'); - return null; - } - - /** - * Write string contents to a file. - * Only works on desktop platforms. - * @param path The file path to read from. - */ - static function writeFile(path:String, data:String):Void - { - #if sys - sys.io.File.saveContent(path, data); - return; - #end - trace('ERROR: writeFile not implemented for this platform'); - return; - } - - /** - * Browse for a file to read and execute a callback once we have a file reference. - * Works great on HTML5 or desktop. - * - * @param callback The function to call when the file is loaded. - */ - static function browseFileReference(callback:FileReference->Void) - { - var file = new FileReference(); - - file.addEventListener(Event.SELECT, function(e) { - var selectedFileRef:FileReference = e.target; - trace('Selected file: ' + selectedFileRef.name); - selectedFileRef.addEventListener(Event.COMPLETE, function(e) { - var loadedFileRef:FileReference = e.target; - trace('Loaded file: ' + loadedFileRef.name); - callback(loadedFileRef); - }); - selectedFileRef.load(); - }); - - file.browse(); - } - - /** - * Prompts the user to save a file to their computer. - */ - static function writeFileReference(path:String, data:String) - { - var file = new FileReference(); - file.addEventListener(Event.COMPLETE, function(e:Event) { - trace('Successfully wrote file.'); - }); - file.addEventListener(Event.CANCEL, function(e:Event) { - trace('Cancelled writing file.'); - }); - file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent) { - trace('IO error writing file.'); - }); - file.save(data, path); - } } diff --git a/source/funkin/play/song/SongValidator.hx b/source/funkin/play/song/SongValidator.hx deleted file mode 100644 index e33ddd87c..000000000 --- a/source/funkin/play/song/SongValidator.hx +++ /dev/null @@ -1,149 +0,0 @@ -package funkin.play.song; - -import funkin.data.song.SongRegistry; -import funkin.data.song.SongData.SongChartData; -import funkin.data.song.SongData.SongMetadata; -import funkin.data.song.SongData.SongPlayData; -import funkin.data.song.SongData.SongTimeChange; -import funkin.data.song.SongData.SongTimeFormat; - -/** - * For SongMetadata and SongChartData objects, - * ensures mandatory fields are present and populates optional fields with default values. - */ -class SongValidator -{ - public static final DEFAULT_SONGNAME:String = "Unknown"; - public static final DEFAULT_ARTIST:String = "Unknown"; - public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS; - public static final DEFAULT_DIVISIONS:Null<Int> = null; - public static final DEFAULT_LOOPED:Bool = false; - public static final DEFAULT_STAGE:String = "mainStage"; - public static final DEFAULT_SCROLLSPEED:Float = 1.0; - - public static var DEFAULT_GENERATEDBY(get, never):String; - - static function get_DEFAULT_GENERATEDBY():String - { - return '${Constants.TITLE} - ${Constants.VERSION}'; - } - - /** - * Validates the fields of a SongMetadata object (excluding the version field). - * - * @param input The SongMetadata object to validate. - * @param songId The ID of the song being validated. Only used for error messages. - * @return The validated SongMetadata object. - */ - public static function validateSongMetadata(input:SongMetadata, songId:String = 'unknown'):SongMetadata - { - if (input == null) - { - trace('[SONGDATA] Could not parse metadata for song ${songId}'); - return null; - } - - if (input.songName == null) - { - trace('[SONGDATA] Song ${songId} is missing a songName field. '); - input.songName = DEFAULT_SONGNAME; - } - if (input.artist == null) - { - trace('[SONGDATA] Song ${songId} is missing an artist field. '); - input.artist = DEFAULT_ARTIST; - } - if (input.timeFormat == null) - { - trace('[SONGDATA] Song ${songId} is missing a timeFormat field. '); - input.timeFormat = DEFAULT_TIMEFORMAT; - } - if (input.generatedBy == null) - { - input.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; - } - - 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); - - if (input.variation == null) input.variation = ''; - - return input; - } - - /** - * Validates the fields of a SongPlayData object. - * - * @param input The SongPlayData object to validate. - * @param songId The ID of the song being validated. Only used for error messages. - * @return The validated SongPlayData object. - */ - 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; - } - - /** - * Validates the fields of a TimeChange object. - * - * @param input The TimeChange object to validate. - * @param songId The ID of the song being validated. Only used for error messages. - * @return The validated TimeChange object. - */ - 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; - } - - /** - * Validates multiple TimeChange objects in an array. - */ - public static function validateTimeChanges(input:Array<SongTimeChange>, songId:String = 'unknown'):Array<SongTimeChange> - { - if (input == null) - { - trace('[SONGDATA] Could not parse metadata.timeChange for song ${songId}'); - return null; - } - - input = input.map((timeChange) -> validateTimeChange(timeChange, songId)); - - return input; - } - - /** - * Validates the fields of a SongChartData object (excluding the version field). - * - * @param input The SongChartData object to validate. - * @param songId The ID of the song being validated. Only used for error messages. - * @return The validated SongChartData object. - */ - public static function validateSongChartData(input:SongChartData, songId:String = 'unknown'):SongChartData - { - if (input == null) - { - trace('[SONGDATA] Could not parse chart data for song ${songId}'); - return null; - } - - return input; - } -} diff --git a/source/funkin/play/song/formats/FNFLegacy.hx b/source/funkin/play/song/formats/FNFLegacy.hx deleted file mode 100644 index a64e461bd..000000000 --- a/source/funkin/play/song/formats/FNFLegacy.hx +++ /dev/null @@ -1,131 +0,0 @@ -package funkin.play.song.formats; - -typedef FNFLegacy = -{ - var song:LegacySongData; -} - -typedef LegacySongData = -{ - var player1:String; // Boyfriend - var player2:String; // Opponent - - var speed:Float; - var stageDefault:String; - var bpm:Float; - var notes:Array<LegacyNoteSection>; - var song:String; // Song name -}; - -typedef LegacyScrollSpeeds = -{ - var easy:Float; - var normal:Float; - var hard:Float; -}; - -typedef LegacyNoteData = -{ - /** - * The easy difficulty. - */ - var ?easy:Array<LegacyNoteSection>; - - /** - * The normal difficulty. - */ - var ?normal:Array<LegacyNoteSection>; - - /** - * The hard difficulty. - */ - var ?hard:Array<LegacyNoteSection>; -}; - -typedef LegacyNoteSection = -{ - /** - * Whether the section is a must-hit section. - * If true, 0-3 are boyfriends notes, 4-7 are opponents notes. - * If false, 0-3 are opponents notes, 4-7 are boyfriends notes. - */ - var mustHitSection:Bool; - - /** - * Array of note data: - * - Direction - * - Time (ms) - * - Sustain Duration (ms) - * - Note kind (true = "alt", or string) - */ - var sectionNotes:Array<LegacyNote>; - - var typeOfSection:Int; - var lengthInSteps:Int; -} - -/** - * Notes in the old format are stored as an Array<Dynamic> - */ -abstract LegacyNote(Array<Dynamic>) -{ - public var time(get, set):Float; - - function get_time():Float - { - return this[0]; - } - - function set_time(value:Float):Float - { - return this[0] = value; - } - - public var data(get, set):Int; - - function get_data():Int - { - return this[1]; - } - - function set_data(value:Int):Int - { - return this[1] = value; - } - - public function getData(mustHitSection:Bool):Int - { - if (mustHitSection) return this[1]; - - return (this[1] + 4) % 8; - } - - public var length(get, set):Float; - - function get_length():Float - { - if (this.length < 3) return 0.0; - return this[2]; - } - - function set_length(value:Float):Float - { - return this[2] = value; - } - - public var kind(get, set):String; - - function get_kind():String - { - if (this.length < 4) return 'normal'; - - if (Std.isOfType(this[3], Bool)) return this[3] ? 'alt' : 'normal'; - - return this[3]; - } - - function set_kind(value:String):String - { - return this[3] = value; - } -} diff --git a/source/funkin/ui/debug/charting/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/ChartEditorAudioHandler.hx new file mode 100644 index 000000000..e852dff0a --- /dev/null +++ b/source/funkin/ui/debug/charting/ChartEditorAudioHandler.hx @@ -0,0 +1,170 @@ +package funkin.ui.debug.charting; + +import openfl.utils.Assets; +import flixel.system.FlxAssets.FlxSoundAsset; +import flixel.system.FlxSound; +import funkin.play.character.BaseCharacter.CharacterType; +import flixel.system.FlxSound; +import haxe.io.Path; + +/** + * Functions for loading audio for the chart editor. + */ +@:nullSafety +@:allow(funkin.ui.debug.charting.ChartEditorState) +@:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) +@:allow(funkin.ui.debug.charting.ChartEditorImportExportHandler) +class ChartEditorAudioHandler +{ + /** + * Loads a vocal track from an absolute file path. + * @param path The absolute path to the audio file. + * @param charKey The character to load the vocal track for. + * @return Success or failure. + */ + static function loadVocalsFromPath(state:ChartEditorState, path:Path, charKey:String = 'default'):Bool + { + #if sys + var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString()); + return loadVocalsFromBytes(state, fileBytes, charKey); + #else + trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); + return false; + #end + } + + /** + * Load a vocal track for a given song and character and add it to the voices group. + * + * @param path ID of the asset. + * @param charKey Character to load the vocal track for. + * @return Success or failure. + */ + static function loadVocalsFromAsset(state:ChartEditorState, path:String, charType:CharacterType = OTHER):Bool + { + var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false); + if (vocalTrack != null) + { + switch (charType) + { + case CharacterType.BF: + if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); + state.audioVocalTrackData.set(state.currentSongCharacterPlayer, Assets.getBytes(path)); + case CharacterType.DAD: + if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); + state.audioVocalTrackData.set(state.currentSongCharacterOpponent, Assets.getBytes(path)); + default: + if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack); + state.audioVocalTrackData.set('default', Assets.getBytes(path)); + } + + return true; + } + return false; + } + + /** + * Loads a vocal track from audio byte data. + */ + static function loadVocalsFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, charKey:String = ''):Bool + { + var openflSound:openfl.media.Sound = new openfl.media.Sound(); + openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); + var vocalTrack:FlxSound = FlxG.sound.load(openflSound, 1.0, false); + if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack); + state.audioVocalTrackData.set(charKey, bytes); + return true; + } + + /** + * Loads an instrumental from an absolute file path, replacing the current instrumental. + * + * @param path The absolute path to the audio file. + * + * @return Success or failure. + */ + static function loadInstrumentalFromPath(state:ChartEditorState, path:Path):Bool + { + #if sys + // Validate file extension. + if (path.ext != null && !ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext)) + { + return false; + } + + var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString()); + return loadInstrumentalFromBytes(state, fileBytes, '${path.file}.${path.ext}'); + #else + trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); + return false; + #end + } + + /** + * Loads an instrumental from audio byte data, replacing the current instrumental. + * @param bytes The audio byte data. + * @param fileName The name of the file, if available. Used for notifications. + * @return Success or failure. + */ + static function loadInstrumentalFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, fileName:String = null):Bool + { + if (bytes == null) + { + return false; + } + + var openflSound:openfl.media.Sound = new openfl.media.Sound(); + openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); + state.audioInstTrack = FlxG.sound.load(openflSound, 1.0, false); + state.audioInstTrack.autoDestroy = false; + state.audioInstTrack.pause(); + + state.audioInstTrackData = bytes; + + state.postLoadInstrumental(); + + return true; + } + + /** + * Loads an instrumental from an OpenFL asset, replacing the current instrumental. + * @param path The path to the asset. Use `Paths` to build this. + * @return Success or failure. + */ + static function loadInstrumentalFromAsset(state:ChartEditorState, path:String):Bool + { + var instTrack:FlxSound = FlxG.sound.load(path, 1.0, false); + if (instTrack != null) + { + state.audioInstTrack = instTrack; + + state.audioInstTrackData = Assets.getBytes(path); + + state.postLoadInstrumental(); + return true; + } + + return false; + } + + /** + * Play a sound effect. + * Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance. + */ + public static function playSound(path:String):Void + { + var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound(); + + var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path); + if (asset == null) + { + trace('WARN: Failed to play sound $path, asset not found.'); + return; + } + + snd.loadEmbedded(asset); + snd.autoDestroy = true; + FlxG.sound.list.add(snd); + snd.play(); + } +} diff --git a/source/funkin/ui/debug/charting/ChartEditorCommand.hx b/source/funkin/ui/debug/charting/ChartEditorCommand.hx index 79f58a098..c358c1d3d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorCommand.hx +++ b/source/funkin/ui/debug/charting/ChartEditorCommand.hx @@ -64,7 +64,7 @@ class AddNotesCommand implements ChartEditorCommand state.currentEventSelection = []; } - state.playSound(Paths.sound('funnyNoise/funnyNoise-08')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -78,7 +78,7 @@ class AddNotesCommand implements ChartEditorCommand state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); state.currentNoteSelection = []; state.currentEventSelection = []; - state.playSound(Paths.sound('funnyNoise/funnyNoise-01')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -114,7 +114,7 @@ class RemoveNotesCommand implements ChartEditorCommand state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes); state.currentNoteSelection = []; state.currentEventSelection = []; - state.playSound(Paths.sound('funnyNoise/funnyNoise-01')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -131,7 +131,7 @@ class RemoveNotesCommand implements ChartEditorCommand } state.currentNoteSelection = notes; state.currentEventSelection = []; - state.playSound(Paths.sound('funnyNoise/funnyNoise-08')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -252,7 +252,7 @@ class AddEventsCommand implements ChartEditorCommand state.currentEventSelection = events; } - state.playSound(Paths.sound('funnyNoise/funnyNoise-08')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -296,7 +296,7 @@ class RemoveEventsCommand implements ChartEditorCommand { state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events); state.currentEventSelection = []; - state.playSound(Paths.sound('funnyNoise/funnyNoise-01')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -312,7 +312,7 @@ class RemoveEventsCommand implements ChartEditorCommand state.currentSongChartEventData.push(event); } state.currentEventSelection = events; - state.playSound(Paths.sound('funnyNoise/funnyNoise-08')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -352,7 +352,7 @@ class RemoveItemsCommand implements ChartEditorCommand state.currentNoteSelection = []; state.currentEventSelection = []; - state.playSound(Paths.sound('funnyNoise/funnyNoise-01')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01')); state.saveDataDirty = true; state.noteDisplayDirty = true; @@ -376,7 +376,7 @@ class RemoveItemsCommand implements ChartEditorCommand state.currentNoteSelection = notes; state.currentEventSelection = events; - state.playSound(Paths.sound('funnyNoise/funnyNoise-08')); + ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08')); state.saveDataDirty = true; state.noteDisplayDirty = true; diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 6f44f89a2..45ee48113 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -1,40 +1,45 @@ package funkin.ui.debug.charting; -import funkin.play.character.CharacterData; -import funkin.util.Constants; -import funkin.util.SerializerUtil; +import funkin.ui.haxeui.components.FunkinDropDown; +import flixel.util.FlxTimer; +import funkin.data.song.importer.FNFLegacyData; +import funkin.data.song.importer.FNFLegacyImporter; +import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongMetadata; -import flixel.util.FlxTimer; -import funkin.ui.haxeui.components.FunkinLink; -import funkin.util.SortUtil; +import funkin.data.song.SongData.SongTimeChange; +import funkin.data.song.SongRegistry; import funkin.input.Cursor; import funkin.play.character.BaseCharacter; +import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.Song; -import funkin.play.song.SongMigrator; -import funkin.play.song.SongValidator; -import funkin.data.song.SongRegistry; -import funkin.data.song.SongData.SongPlayableChar; -import funkin.data.song.SongData.SongTimeChange; +import funkin.play.stage.StageData; +import funkin.ui.haxeui.components.FunkinLink; +import funkin.util.Constants; import funkin.util.FileUtil; +import funkin.util.SerializerUtil; +import funkin.util.SortUtil; +import funkin.util.VersionUtil; import haxe.io.Path; import haxe.ui.components.Button; import haxe.ui.components.DropDown; import haxe.ui.components.Label; import haxe.ui.components.Link; import haxe.ui.components.NumberStepper; +import haxe.ui.components.Slider; import haxe.ui.components.TextField; import haxe.ui.containers.Box; import haxe.ui.containers.dialogs.Dialog; +import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialogs; -import haxe.ui.containers.properties.PropertyGrid; -import haxe.ui.containers.properties.PropertyGroup; +import haxe.ui.containers.Form; import haxe.ui.containers.VBox; import haxe.ui.core.Component; import haxe.ui.events.UIEvent; import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationType; +import thx.semver.Version; using Lambda; @@ -48,13 +53,14 @@ class ChartEditorDialogHandler static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome'); static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst'); static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata'); - static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); static final CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart'); static final CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-entry'); static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart'); static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide'); + static final CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-variation'); + static final CHART_EDITOR_DIALOG_ADD_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-difficulty'); /** * Builds and opens a dialog giving brief credits for the chart editor. @@ -135,7 +141,7 @@ class ChartEditorDialogHandler dialog.hideDialog(DialogButton.CANCEL); // Load song from template - state.loadSongAsTemplate(targetSongId); + ChartEditorImportExportHandler.loadSongAsTemplate(state, targetSongId); } splashTemplateContainer.addComponent(linkTemplateSong); @@ -298,7 +304,7 @@ class ChartEditorDialogHandler {label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) { if (selectedFile != null && selectedFile.bytes != null) { - if (state.loadInstrumentalFromBytes(selectedFile.bytes)) + if (ChartEditorAudioHandler.loadInstrumentalFromBytes(state, selectedFile.bytes)) { trace('Selected file: ' + selectedFile.fullPath); #if !mac @@ -335,7 +341,7 @@ class ChartEditorDialogHandler onDropFile = function(pathStr:String) { var path:Path = new Path(pathStr); trace('Dropped file (${path})'); - if (state.loadInstrumentalFromPath(path)) + if (ChartEditorAudioHandler.loadInstrumentalFromPath(state, path)) { // Tell the user the load was successful. #if !mac @@ -457,62 +463,96 @@ class ChartEditorDialogHandler dialog.hideDialog(DialogButton.CANCEL); } - var dialogSongName:Null<TextField> = dialog.findComponent('dialogSongName', TextField); - if (dialogSongName == null) throw 'Could not locate dialogSongName TextField in Song Metadata dialog'; - dialogSongName.onChange = function(event:UIEvent) { + var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default'); + + 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) { var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { - dialogSongName.removeClass('invalid-value'); - state.currentSongMetadata.songName = event.target.text; + inputSongName.removeClass('invalid-value'); + newSongMetadata.songName = event.target.text; } else { - state.currentSongMetadata.songName = ""; + newSongMetadata.songName = ""; } }; - state.currentSongMetadata.songName = ""; + inputSongName.text = ""; - var dialogSongArtist:Null<TextField> = dialog.findComponent('dialogSongArtist', TextField); - if (dialogSongArtist == null) throw 'Could not locate dialogSongArtist TextField in Song Metadata dialog'; - dialogSongArtist.onChange = function(event:UIEvent) { + var inputSongArtist:Null<TextField> = dialog.findComponent('inputSongArtist', TextField); + if (inputSongArtist == null) throw 'Could not locate inputSongArtist TextField in Song Metadata dialog'; + inputSongArtist.onChange = function(event:UIEvent) { var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { - dialogSongArtist.removeClass('invalid-value'); - state.currentSongMetadata.artist = event.target.text; + inputSongArtist.removeClass('invalid-value'); + newSongMetadata.artist = event.target.text; } else { - state.currentSongMetadata.artist = ""; + newSongMetadata.artist = ""; } }; - state.currentSongMetadata.artist = ""; + inputSongArtist.text = ""; - var dialogStage:Null<DropDown> = dialog.findComponent('dialogStage', DropDown); - if (dialogStage == null) throw 'Could not locate dialogStage DropDown in Song Metadata dialog'; - dialogStage.onChange = function(event:UIEvent) { + var inputStage:Null<DropDown> = dialog.findComponent('inputStage', DropDown); + if (inputStage == null) throw 'Could not locate inputStage DropDown in Song Metadata dialog'; + inputStage.onChange = function(event:UIEvent) { if (event.data == null && event.data.id == null) return; - state.currentSongMetadata.playData.stage = event.data.id; + newSongMetadata.playData.stage = event.data.id; }; - state.currentSongMetadata.playData.stage = 'mainStage'; + var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(inputStage, newSongMetadata.playData.stage); + inputStage.value = startingValueStage; - var dialogNoteSkin:Null<DropDown> = dialog.findComponent('dialogNoteSkin', DropDown); - if (dialogNoteSkin == null) throw 'Could not locate dialogNoteSkin DropDown in Song Metadata dialog'; - dialogNoteSkin.onChange = function(event:UIEvent) { + var inputNoteStyle:Null<FunkinDropDown> = dialog.findComponent('inputNoteStyle', FunkinDropDown); + if (inputNoteStyle == null) throw 'Could not locate inputNoteStyle DropDown in Song Metadata dialog'; + inputNoteStyle.onChange = function(event:UIEvent) { if (event.data.id == null) return; - state.currentSongNoteSkin = event.data.id; + newSongMetadata.playData.noteSkin = event.data.id; }; - state.currentSongNoteSkin = 'funkin'; + var startingValueNoteStyle = ChartEditorDropdowns.populateDropdownWithNoteStyles(inputNoteStyle, newSongMetadata.playData.noteSkin); + inputNoteStyle.value = startingValueNoteStyle; + + var inputCharacterPlayer:Null<FunkinDropDown> = dialog.findComponent('inputCharacterPlayer', FunkinDropDown); + if (inputCharacterPlayer == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterPlayer component.'; + inputCharacterPlayer.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + newSongMetadata.playData.characters.player = event.data.id; + }; + var startingValuePlayer = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterPlayer, CharacterType.BF, + newSongMetadata.playData.characters.player); + inputCharacterPlayer.value = startingValuePlayer; + + var inputCharacterOpponent:Null<FunkinDropDown> = dialog.findComponent('inputCharacterOpponent', FunkinDropDown); + if (inputCharacterOpponent == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterOpponent component.'; + inputCharacterOpponent.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + newSongMetadata.playData.characters.opponent = event.data.id; + }; + var startingValueOpponent = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterOpponent, CharacterType.DAD, + newSongMetadata.playData.characters.opponent); + inputCharacterOpponent.value = startingValueOpponent; + + var inputCharacterGirlfriend:Null<FunkinDropDown> = dialog.findComponent('inputCharacterGirlfriend', FunkinDropDown); + if (inputCharacterGirlfriend == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterGirlfriend component.'; + inputCharacterGirlfriend.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + newSongMetadata.playData.characters.girlfriend = event.data.id == "none" ? "" : event.data.id; + }; + var startingValueGirlfriend = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterGirlfriend, CharacterType.GF, + newSongMetadata.playData.characters.girlfriend); + inputCharacterGirlfriend.value = startingValueGirlfriend; var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper); if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Song Metadata dialog'; dialogBPM.onChange = function(event:UIEvent) { if (event.value == null || event.value <= 0) return; - var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges; + var timeChanges:Array<SongTimeChange> = newSongMetadata.timeChanges; if (timeChanges == null || timeChanges.length == 0) { timeChanges = [new SongTimeChange(0, event.value)]; @@ -524,24 +564,9 @@ class ChartEditorDialogHandler Conductor.forceBPM(event.value); - state.currentSongMetadata.timeChanges = timeChanges; + newSongMetadata.timeChanges = timeChanges; }; - var dialogCharGrid:Null<PropertyGrid> = dialog.findComponent('dialogCharGrid', PropertyGrid); - if (dialogCharGrid == null) throw 'Could not locate dialogCharGrid PropertyGrid in Song Metadata dialog'; - var dialogCharAdd:Null<Button> = dialog.findComponent('dialogCharAdd', Button); - if (dialogCharAdd == null) throw 'Could not locate dialogCharAdd Button in Song Metadata dialog'; - dialogCharAdd.onClick = function(event:UIEvent) { - var charGroup:PropertyGroup; - charGroup = buildCharGroup(state, null, () -> dialogCharGrid.removeComponent(charGroup)); - dialogCharGrid.addComponent(charGroup); - }; - - // Empty the character list. - state.currentSongMetadata.playData.playableChars = []; - // Add at least one character group with no Remove button. - dialogCharGrid.addComponent(buildCharGroup(state, 'bf')); - var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button); if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog'; dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY); @@ -549,78 +574,6 @@ class ChartEditorDialogHandler return dialog; } - static function buildCharGroup(state:ChartEditorState, key:String = '', removeFunc:Void->Void = null):PropertyGroup - { - var groupKey:String = key; - - var getCharData:Void->Null<SongPlayableChar> = function():Null<SongPlayableChar> { - if (state.currentSongMetadata.playData == null) return null; - if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}'; - - var result = state.currentSongMetadata.playData.playableChars.get(groupKey); - if (result == null) - { - result = new SongPlayableChar('', 'dad'); - state.currentSongMetadata.playData.playableChars.set(groupKey, result); - } - return result; - } - - var moveCharGroup:String->Void = function(target:String):Void { - var charData:Null<SongPlayableChar> = getCharData(); - if (charData == null) return; - - if (state.currentSongMetadata.playData.playableChars == null) return; - state.currentSongMetadata.playData.playableChars.remove(groupKey); - state.currentSongMetadata.playData.playableChars.set(target, charData); - groupKey = target; - } - - var removeGroup:Void->Void = function():Void { - if (state?.currentSongMetadata?.playData?.playableChars == null) return; - state.currentSongMetadata.playData.playableChars.remove(groupKey); - if (removeFunc != null) removeFunc(); - } - - var charData:Null<SongPlayableChar> = getCharData(); - - var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT); - - var charGroupPlayer:Null<DropDown> = charGroup.findComponent('charGroupPlayer', DropDown); - if (charGroupPlayer == null) throw 'Could not locate charGroupPlayer DropDown in Song Metadata dialog'; - charGroupPlayer.onChange = function(event:UIEvent):Void { - if (charData != null) return; - charGroup.text = event.data.text; - moveCharGroup(event.data.id); - }; - - var charGroupOpponent:Null<DropDown> = charGroup.findComponent('charGroupOpponent', DropDown); - if (charGroupOpponent == null) throw 'Could not locate charGroupOpponent DropDown in Song Metadata dialog'; - charGroupOpponent.onChange = function(event:UIEvent):Void { - if (charData == null) return; - charData.opponent = event.data.id; - }; - charGroupOpponent.value = charData.opponent; - - var charGroupGirlfriend:Null<DropDown> = charGroup.findComponent('charGroupGirlfriend', DropDown); - if (charGroupGirlfriend == null) throw 'Could not locate charGroupGirlfriend DropDown in Song Metadata dialog'; - charGroupGirlfriend.onChange = function(event:UIEvent):Void { - if (charData == null) return; - charData.girlfriend = event.data.id; - }; - charGroupGirlfriend.value = charData.girlfriend; - - var charGroupRemove:Null<Button> = charGroup.findComponent('charGroupRemove', Button); - if (charGroupRemove == null) throw 'Could not locate charGroupRemove Button in Song Metadata dialog'; - charGroupRemove.onClick = function(event:UIEvent):Void { - removeGroup(); - }; - - if (removeFunc == null) charGroupRemove.hidden = true; - - return charGroup; - } - /** * Builds and opens a dialog where the user uploads vocals for the current song. * @param state The current chart editor state. @@ -631,13 +584,10 @@ class ChartEditorDialogHandler { var charIdsForVocals:Array<String> = []; - for (charKey in state.currentSongMetadata.playData.playableChars.keys()) - { - var charData:Null<SongPlayableChar> = state.currentSongMetadata.playData.playableChars.get(charKey); - if (charData == null) continue; - charIdsForVocals.push(charKey); - if (charData.opponent != null) charIdsForVocals.push(charData.opponent); - } + var charData:SongCharacterData = state.currentSongMetadata.playData.characters; + + charIdsForVocals.push(charData.player); + charIdsForVocals.push(charData.opponent); var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable); if (dialog == null) throw 'Could not locate Upload Vocals dialog'; @@ -678,7 +628,7 @@ class ChartEditorDialogHandler trace('Selected file: $pathStr'); var path:Path = new Path(pathStr); - if (state.loadVocalsFromPath(path, charKey)) + if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey)) { // Tell the user the load was successful. #if !mac @@ -740,7 +690,7 @@ class ChartEditorDialogHandler #else vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}'; #end - state.loadVocalsFromBytes(selectedFile.bytes, charKey); + ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey); dialogNoVocals.hidden = true; removeDropHandler(onDropFile); } @@ -793,7 +743,7 @@ class ChartEditorDialogHandler var buttonContinue:Null<Button> = dialog.findComponent('dialogContinue', Button); if (buttonContinue == null) throw 'Could not locate dialogContinue button in Open Chart dialog'; buttonContinue.onClick = function(_event) { - state.loadSong(songMetadata, songChartData); + ChartEditorImportExportHandler.loadSong(state, songMetadata, songChartData); dialog.hideDialog(DialogButton.APPLY); } @@ -880,9 +830,26 @@ class ChartEditorDialogHandler var path:Path = new Path(pathStr); trace('Dropped JSON file (${path})'); - var songMetadataJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); - var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import'); - songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import'); + var songMetadataTxt:String = FileUtil.readStringFromPath(path.toString()); + + var songMetadataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songMetadataTxt); + if (songMetadataVersion == null) + { + // Tell the user the load was not successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Could not parse metadata file version (${path.file}.${path.ext})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + return; + } + + var songMetadataVariation:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataRawWithMigration(songMetadataTxt, path.toString(), + songMetadataVersion); if (songMetadataVariation == null) { @@ -928,31 +895,63 @@ class ChartEditorDialogHandler { trace('Selected file: ' + selectedFile.name); - var songMetadataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); - var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import'); - songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import'); - songMetadataVariation.variation = variation; + var songMetadataTxt:String = selectedFile.bytes.toString(); - songMetadata.set(variation, songMetadataVariation); + var songMetadataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songMetadataTxt); + if (songMetadataVersion == null) + { + // Tell the user the load was not successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Could not parse metadata file version (${selectedFile.name})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + return; + } - // Tell the user the load was successful. - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Success', - body: 'Loaded metadata file (${selectedFile.name})', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end + var songMetadataVariation:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataRawWithMigration(songMetadataTxt, selectedFile.name, + songMetadataVersion); - #if FILE_DROP_SUPPORTED - label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; - #else - label.text = 'Metadata file (click to browse)\n${selectedFile.name}'; - #end + if (songMetadataVariation != null) + { + songMetadata.set(variation, songMetadataVariation); - if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations); + // Tell the user the load was successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded metadata file (${selectedFile.name})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + + #if FILE_DROP_SUPPORTED + label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; + #else + label.text = 'Metadata file (click to browse)\n${selectedFile.name}'; + #end + + if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations); + } + else + { + // Tell the user the load was unsuccessful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Failed to load metadata file (${selectedFile.name})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + } } }); } @@ -961,31 +960,64 @@ class ChartEditorDialogHandler var path:Path = new Path(pathStr); trace('Dropped JSON file (${path})'); - var songChartDataJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); - var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import'); - songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import'); + var songChartDataTxt:String = FileUtil.readStringFromPath(path.toString()); - songChartData.set(variation, songChartDataVariation); - state.notePreviewDirty = true; - state.notePreviewViewportBoundsDirty = true; - state.noteDisplayDirty = true; + var songChartDataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songChartDataTxt); + if (songChartDataVersion == null) + { + // Tell the user the load was not successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Could not parse chart data file version (${path.file}.${path.ext})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + return; + } - // Tell the user the load was successful. - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Success', - body: 'Loaded chart data file (${path.file}.${path.ext})', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end + var songChartDataVariation:Null<SongChartData> = SongRegistry.instance.parseEntryChartDataRawWithMigration(songChartDataTxt, path.toString(), + songChartDataVersion); - #if FILE_DROP_SUPPORTED - label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; - #else - label.text = 'Chart data file (click to browse)\n${path.file}.${path.ext}'; - #end + if (songChartDataVariation != null) + { + songChartData.set(variation, songChartDataVariation); + state.notePreviewDirty = true; + state.notePreviewViewportBoundsDirty = true; + state.noteDisplayDirty = true; + + // Tell the user the load was successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart data file (${path.file}.${path.ext})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + + #if FILE_DROP_SUPPORTED + label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; + #else + label.text = 'Chart data file (click to browse)\n${path.file}.${path.ext}'; + #end + } + else + { + // Tell the user the load was unsuccessful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Failed to load chart data file (${path.file}.${path.ext})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + } }; onClickChartDataVariation = function(variation:String, label:Label, _event:UIEvent) { @@ -995,31 +1027,51 @@ class ChartEditorDialogHandler { trace('Selected file: ' + selectedFile.name); - var songChartDataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); - var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import'); - songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import'); + var songChartDataTxt:String = selectedFile.bytes.toString(); - songChartData.set(variation, songChartDataVariation); - state.notePreviewDirty = true; - state.notePreviewViewportBoundsDirty = true; - state.noteDisplayDirty = true; + var songChartDataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songChartDataTxt); + if (songChartDataVersion == null) + { + // Tell the user the load was not successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Could not parse chart data file version (${selectedFile.name})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + return; + } - // Tell the user the load was successful. - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Success', - body: 'Loaded chart data file (${selectedFile.name})', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end + var songChartDataVariation:Null<SongChartData> = SongRegistry.instance.parseEntryChartDataRawWithMigration(songChartDataTxt, selectedFile.name, + songChartDataVersion); - #if FILE_DROP_SUPPORTED - label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; - #else - label.text = 'Chart data file (click to browse)\n${selectedFile.name}'; - #end + if (songChartDataVariation != null) + { + songChartData.set(variation, songChartDataVariation); + state.notePreviewDirty = true; + state.notePreviewViewportBoundsDirty = true; + state.noteDisplayDirty = true; + + // Tell the user the load was successful. + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart data file (${selectedFile.name})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + + #if FILE_DROP_SUPPORTED + label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; + #else + label.text = 'Chart data file (click to browse)\n${selectedFile.name}'; + #end + } } }); } @@ -1102,11 +1154,27 @@ class ChartEditorDialogHandler if (selectedFile != null && selectedFile.bytes != null) { trace('Selected file: ' + selectedFile.fullPath); - var selectedFileJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); - var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson); - var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson); + var selectedFileTxt:String = selectedFile.bytes.toString(); + var fnfLegacyData:Null<FNFLegacyData> = FNFLegacyImporter.parseLegacyDataRaw(selectedFileTxt, selectedFile.fullPath); - state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); + if (fnfLegacyData == null) + { + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Failure', + body: 'Failed to parse FNF chart file (${selectedFile.name})', + type: NotificationType.Error, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + return; + } + + var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(fnfLegacyData); + var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(fnfLegacyData); + + ChartEditorImportExportHandler.loadSong(state, [Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); dialog.hideDialog(DialogButton.APPLY); #if !mac @@ -1124,11 +1192,12 @@ class ChartEditorDialogHandler onDropFile = function(pathStr:String) { var path:Path = new Path(pathStr); - var selectedFileJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); - var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson); - var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson); + var selectedFileText:String = FileUtil.readStringFromPath(path.toString()); + var selectedFileData:FNFLegacyData = FNFLegacyImporter.parseLegacyDataRaw(selectedFileText, path.toString()); + var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(selectedFileData); + var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(selectedFileData); - state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); + ChartEditorImportExportHandler.loadSong(state, [Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); dialog.hideDialog(DialogButton.APPLY); #if !mac @@ -1181,4 +1250,161 @@ class ChartEditorDialogHandler return dialog; } + + /** + * Builds and opens a dialog where the user can add a new variation for a song. + * @param state The current chart editor state. + * @param closable Whether the dialog can be closed by the user. + * @return The dialog that was opened. + */ + public static function openAddVariationDialog(state:ChartEditorState, closable:Bool = true):Dialog + { + var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT, true, false); + if (dialog == null) throw 'Could not locate Add Variation dialog'; + + var variationForm:Null<Form> = dialog.findComponent('variationForm', Form); + if (variationForm == null) throw 'Could not locate variationForm Form in Add Variation dialog'; + + var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button); + if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Variation dialog'; + buttonCancel.onClick = function(_event) { + dialog.hideDialog(DialogButton.CANCEL); + } + + var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button); + if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Variation dialog'; + buttonAdd.onClick = function(_event) { + // This performs validation before the onSubmit callback is called. + variationForm.submit(); + } + + var dialogSongName:Null<TextField> = dialog.findComponent('dialogSongName', TextField); + if (dialogSongName == null) throw 'Could not locate dialogSongName TextField in Add Variation dialog'; + dialogSongName.value = state.currentSongMetadata.songName; + + var dialogSongArtist:Null<TextField> = dialog.findComponent('dialogSongArtist', TextField); + if (dialogSongArtist == null) throw 'Could not locate dialogSongArtist TextField in Add Variation dialog'; + dialogSongArtist.value = state.currentSongMetadata.artist; + + var dialogStage:Null<DropDown> = dialog.findComponent('dialogStage', DropDown); + if (dialogStage == null) throw 'Could not locate dialogStage DropDown in Add Variation dialog'; + var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(dialogStage, state.currentSongMetadata.playData.stage); + dialogStage.value = startingValueStage; + + var dialogNoteStyle:Null<DropDown> = dialog.findComponent('dialogNoteStyle', DropDown); + if (dialogNoteStyle == null) throw 'Could not locate dialogNoteStyle DropDown in Add Variation dialog'; + dialogNoteStyle.value = state.currentSongMetadata.playData.noteSkin; + + var dialogCharacterPlayer:Null<DropDown> = dialog.findComponent('dialogCharacterPlayer', DropDown); + if (dialogCharacterPlayer == null) throw 'Could not locate dialogCharacterPlayer DropDown in Add Variation dialog'; + dialogCharacterPlayer.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterPlayer, CharacterType.BF, + state.currentSongMetadata.playData.characters.player); + + var dialogCharacterOpponent:Null<DropDown> = dialog.findComponent('dialogCharacterOpponent', DropDown); + if (dialogCharacterOpponent == null) throw 'Could not locate dialogCharacterOpponent DropDown in Add Variation dialog'; + dialogCharacterOpponent.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterOpponent, CharacterType.DAD, + state.currentSongMetadata.playData.characters.opponent); + + var dialogCharacterGirlfriend:Null<DropDown> = dialog.findComponent('dialogCharacterGirlfriend', DropDown); + if (dialogCharacterGirlfriend == null) throw 'Could not locate dialogCharacterGirlfriend DropDown in Add Variation dialog'; + dialogCharacterGirlfriend.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterGirlfriend, CharacterType.GF, + state.currentSongMetadata.playData.characters.girlfriend); + + var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper); + if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Add Variation dialog'; + dialogBPM.value = state.currentSongMetadata.timeChanges[0].bpm; + + // If all validators succeeded, this callback is called. + + variationForm.onSubmit = function(_event) { + trace('Add Variation dialog submitted, validation succeeded!'); + + var dialogVariationName:Null<TextField> = dialog.findComponent('dialogVariationName', TextField); + if (dialogVariationName == null) throw 'Could not locate dialogVariationName TextField in Add Variation dialog'; + + var pendingVariation:SongMetadata = new SongMetadata(dialogSongName.text, dialogSongArtist.text, dialogVariationName.text.toLowerCase()); + + pendingVariation.playData.stage = dialogStage.value.id; + pendingVariation.playData.noteSkin = dialogNoteStyle.value; + pendingVariation.timeChanges[0].bpm = dialogBPM.value; + + state.songMetadata.set(pendingVariation.variation, pendingVariation); + state.difficultySelectDirty = true; // Force the Difficulty toolbox to update. + #if !mac + NotificationManager.instance.addNotification( + { + title: "Add Variation", + body: 'Added new variation "${pendingVariation.variation}"', + type: NotificationType.Success + }); + #end + dialog.hideDialog(DialogButton.APPLY); + } + + return dialog; + } + + /** + * Builds and opens a dialog where the user can add a new difficulty for a song. + * @param state The current chart editor state. + * @param closable Whether the dialog can be closed by the user. + * @return The dialog that was opened. + */ + public static function openAddDifficultyDialog(state:ChartEditorState, closable:Bool = true):Dialog + { + var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_ADD_DIFFICULTY_LAYOUT, true, false); + if (dialog == null) throw 'Could not locate Add Difficulty dialog'; + + var difficultyForm:Null<Form> = dialog.findComponent('difficultyForm', Form); + if (difficultyForm == null) throw 'Could not locate difficultyForm Form in Add Difficulty dialog'; + + var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button); + if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Difficulty dialog'; + buttonCancel.onClick = function(_event) { + dialog.hideDialog(DialogButton.CANCEL); + } + + var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button); + if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Difficulty dialog'; + buttonAdd.onClick = function(_event) { + // This performs validation before the onSubmit callback is called. + difficultyForm.submit(); + } + + var dialogVariation:Null<DropDown> = dialog.findComponent('dialogVariation', DropDown); + if (dialogVariation == null) throw 'Could not locate dialogVariation DropDown in Add Variation dialog'; + dialogVariation.value = ChartEditorDropdowns.populateDropdownWithVariations(dialogVariation, state, true); + + var labelScrollSpeed:Null<Label> = dialog.findComponent('labelScrollSpeed', Label); + if (labelScrollSpeed == null) throw 'Could not find labelScrollSpeed component.'; + + var inputScrollSpeed:Null<Slider> = dialog.findComponent('inputScrollSpeed', Slider); + if (inputScrollSpeed == null) throw 'Could not find inputScrollSpeed component.'; + inputScrollSpeed.onChange = function(event:UIEvent) { + labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x'; + }; + inputScrollSpeed.value = state.currentSongChartScrollSpeed; + labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x'; + + difficultyForm.onSubmit = function(_event) { + trace('Add Difficulty dialog submitted, validation succeeded!'); + + var dialogDifficultyName:Null<TextField> = dialog.findComponent('dialogDifficultyName', TextField); + if (dialogDifficultyName == null) throw 'Could not locate dialogDifficultyName TextField in Add Difficulty dialog'; + + state.createDifficulty(dialogVariation.value.id, dialogDifficultyName.text.toLowerCase(), inputScrollSpeed.value ?? 1.0); + + #if !mac + NotificationManager.instance.addNotification( + { + title: "Add Difficulty", + body: 'Added new difficulty "${dialogDifficultyName.text.toLowerCase()}"', + type: NotificationType.Success + }); + #end + dialog.hideDialog(DialogButton.APPLY); + } + + return dialog; + } } diff --git a/source/funkin/ui/debug/charting/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/ChartEditorDropdowns.hx new file mode 100644 index 000000000..ec41de9c0 --- /dev/null +++ b/source/funkin/ui/debug/charting/ChartEditorDropdowns.hx @@ -0,0 +1,129 @@ +package funkin.ui.debug.charting; + +import funkin.data.notestyle.NoteStyleRegistry; +import funkin.play.notes.notestyle.NoteStyle; +import funkin.play.stage.StageData; +import funkin.play.stage.StageData.StageDataParser; +import funkin.play.character.CharacterData; +import haxe.ui.components.DropDown; +import funkin.play.character.BaseCharacter.CharacterType; +import funkin.play.character.CharacterData.CharacterDataParser; + +/** + * This class contains functions for populating dropdowns based on game data. + * These get used by both dialogs and toolboxes so they're in their own class to prevent "reaching over." + */ +@:nullSafety +@:access(ChartEditorState) +class ChartEditorDropdowns +{ + public static function populateDropdownWithCharacters(dropDown:DropDown, charType:CharacterType, startingCharId:String):DropDownEntry + { + dropDown.dataSource.clear(); + + // TODO: Filter based on charType. + var charIds:Array<String> = CharacterDataParser.listCharacterIds(); + + var returnValue:DropDownEntry = switch (charType) + { + case BF: {id: "bf", text: "Boyfriend"}; + case DAD: {id: "dad", text: "Daddy Dearest"}; + default: { + dropDown.dataSource.add({id: "none", text: ""}); + {id: "none", text: "None"}; + } + } + + for (charId in charIds) + { + var character:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charId); + if (character == null) continue; + + var value = {id: charId, text: character.name}; + if (startingCharId == charId) returnValue = value; + + dropDown.dataSource.add(value); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } + + public static function populateDropdownWithStages(dropDown:DropDown, startingStageId:String):DropDownEntry + { + dropDown.dataSource.clear(); + + var stageIds:Array<String> = StageDataParser.listStageIds(); + + var returnValue:DropDownEntry = {id: "mainStage", text: "Main Stage"}; + + for (stageId in stageIds) + { + var stage:Null<StageData> = StageDataParser.parseStageData(stageId); + if (stage == null) continue; + + var value = {id: stageId, text: stage.name}; + if (startingStageId == stageId) returnValue = value; + + dropDown.dataSource.add(value); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } + + public static function populateDropdownWithNoteStyles(dropDown:DropDown, startingStyleId:String):DropDownEntry + { + dropDown.dataSource.clear(); + + var noteStyleIds:Array<String> = NoteStyleRegistry.instance.listEntryIds(); + + var returnValue:DropDownEntry = {id: "funkin", text: "Funkin'"}; + + for (noteStyleId in noteStyleIds) + { + var noteStyle:Null<NoteStyle> = NoteStyleRegistry.instance.fetchEntry(noteStyleId); + if (noteStyle == null) continue; + + var value = {id: noteStyleId, text: noteStyle.getName()}; + if (startingStyleId == noteStyleId) returnValue = value; + + dropDown.dataSource.add(value); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } + + public static function populateDropdownWithVariations(dropDown:DropDown, state:ChartEditorState, includeNone:Bool = true):DropDownEntry + { + dropDown.dataSource.clear(); + + var variationIds:Array<String> = state.availableVariations; + + if (includeNone) + { + dropDown.dataSource.add({id: "none", text: ""}); + } + + var returnValue:DropDownEntry = includeNone ? ({id: "none", text: ""}) : ({id: "default", text: "Default"}); + + for (variationId in variationIds) + { + dropDown.dataSource.add({id: variationId, text: variationId.toTitleCase()}); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } +} + +typedef DropDownEntry = +{ + id:String, + text:String +}; diff --git a/source/funkin/ui/debug/charting/ChartEditorImportExportHandler.hx b/source/funkin/ui/debug/charting/ChartEditorImportExportHandler.hx new file mode 100644 index 000000000..9ac903e38 --- /dev/null +++ b/source/funkin/ui/debug/charting/ChartEditorImportExportHandler.hx @@ -0,0 +1,195 @@ +package funkin.ui.debug.charting; + +import haxe.ui.notifications.NotificationType; +import funkin.util.DateUtil; +import haxe.io.Path; +import funkin.util.SerializerUtil; +import haxe.ui.notifications.NotificationManager; +import funkin.util.FileUtil; +import funkin.util.FileUtil; +import funkin.play.song.Song; +import funkin.data.song.SongData.SongChartData; +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongRegistry; + +/** + * Contains functions for importing, loading, saving, and exporting charts. + */ +@:nullSafety +@:allow(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorImportExportHandler +{ + /** + * Fetch's a song's existing chart and audio and loads it, replacing the current song. + */ + public static function loadSongAsTemplate(state:ChartEditorState, songId:String):Void + { + var song:Null<Song> = SongRegistry.instance.fetchEntry(songId); + + if (song == null) return; + + // Load the song metadata. + var rawSongMetadata:Array<SongMetadata> = song.getRawMetadata(); + var songMetadata:Map<String, SongMetadata> = []; + var songChartData:Map<String, SongChartData> = []; + + for (metadata in rawSongMetadata) + { + if (metadata == null) continue; + var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation; + + // Clone to prevent modifying the original. + var metadataClone:SongMetadata = metadata.clone(variation); + if (metadataClone != null) songMetadata.set(variation, metadataClone); + + var chartData:Null<SongChartData> = SongRegistry.instance.parseEntryChartData(songId, metadata.variation); + if (chartData != null) songChartData.set(variation, chartData); + } + + loadSong(state, songMetadata, songChartData); + + state.sortChartData(); + + state.clearVocals(); + + ChartEditorAudioHandler.loadInstrumentalFromAsset(state, Paths.inst(songId)); + + var diff:Null<SongDifficulty> = song.getDifficulty(state.selectedDifficulty); + var voiceList:Array<String> = diff != null ? diff.buildVoiceList() : []; + if (voiceList.length == 2) + { + ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], BF); + ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], DAD); + } + else + { + for (voicePath in voiceList) + { + ChartEditorAudioHandler.loadVocalsFromAsset(state, voicePath); + } + } + + state.refreshMetadataToolbox(); + + #if !mac + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded song (${rawSongMetadata[0].songName})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + #end + } + + /** + * Loads song metadata and chart data into the editor. + * @param newSongMetadata The song metadata to load. + * @param newSongChartData The song chart data to load. + */ + public static function loadSong(state:ChartEditorState, newSongMetadata:Map<String, SongMetadata>, newSongChartData:Map<String, SongChartData>):Void + { + state.songMetadata = newSongMetadata; + state.songChartData = newSongChartData; + + Conductor.forceBPM(null); // Disable the forced BPM. + Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); + + state.notePreviewDirty = true; + state.notePreviewViewportBoundsDirty = true; + state.difficultySelectDirty = true; + state.opponentPreviewDirty = true; + state.playerPreviewDirty = true; + + // Remove instrumental and vocal tracks, they will be loaded next. + if (state.audioInstTrack != null) + { + state.audioInstTrack.stop(); + state.audioInstTrack = null; + } + if (state.audioVocalTrackGroup != null) + { + state.audioVocalTrackGroup.stop(); + state.audioVocalTrackGroup.clear(); + } + } + + /** + * @param force Whether to force the export without prompting the user for a file location. + * @param tmp If true, save to the temporary directory instead of the local `backup` directory. + */ + public static function exportAllSongData(state:ChartEditorState, force:Bool = false, tmp:Bool = false):Void + { + var zipEntries:Array<haxe.zip.Entry> = []; + + for (variation in state.availableVariations) + { + var variationId:String = variation; + if (variation == '' || variation == 'default' || variation == 'normal') + { + variationId = ''; + } + + if (variationId == '') + { + var variationMetadata:Null<SongMetadata> = state.songMetadata.get(variation); + if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata.json', SerializerUtil.toJSON(variationMetadata))); + var variationChart:Null<SongChartData> = state.songChartData.get(variation); + if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart.json', SerializerUtil.toJSON(variationChart))); + } + else + { + var variationMetadata:Null<SongMetadata> = state.songMetadata.get(variation); + if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata-$variationId.json', + SerializerUtil.toJSON(variationMetadata))); + var variationChart:Null<SongChartData> = state.songChartData.get(variation); + if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart-$variationId.json', + SerializerUtil.toJSON(variationChart))); + } + } + + if (state.audioInstTrackData != null) zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', state.audioInstTrackData)); + for (charId in state.audioVocalTrackData.keys()) + { + var entryData = state.audioVocalTrackData.get(charId); + if (entryData == null) continue; + zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-$charId.ogg', entryData)); + } + + trace('Exporting ${zipEntries.length} files to ZIP...'); + + if (force) + { + var targetPath:String = if (tmp) + { + Path.join([ + FileUtil.getTempDir(), + 'chart-editor-exit-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}' + ]); + } + else + { + Path.join([ + './backups/', + 'chart-editor-exit-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}' + ]); + } + + // We have to force write because the program will die before the save dialog is closed. + trace('Force exporting to $targetPath...'); + FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath); + return; + } + + // Prompt and save. + var onSave:Array<String>->Void = function(paths:Array<String>) { + trace('Successfully exported files.'); + }; + + var onCancel:Void->Void = function() { + trace('Export cancelled.'); + }; + + FileUtil.saveMultipleFiles(zipEntries, onSave, onCancel, '${state.currentSongId}-chart.${Constants.EXT_CHART}'); + } +} diff --git a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx index 4e0972621..77954087b 100644 --- a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx @@ -19,7 +19,7 @@ class ChartEditorNoteSprite extends FlxSprite /** * The list of available note skin to validate against. */ - public static final NOTE_STYLES:Array<String> = ['Normal', 'Pixel']; + public static final NOTE_STYLES:Array<String> = ['funkin', 'pixel']; /** * The ChartEditorState this note belongs to. @@ -54,20 +54,20 @@ class ChartEditorNoteSprite extends FlxSprite // Initialize all the animations, not just the one we're going to use immediately, // so that later we can reuse the sprite without having to initialize more animations during scrolling. - this.animation.addByPrefix('tapLeftNormal', 'purple instance'); - this.animation.addByPrefix('tapDownNormal', 'blue instance'); - this.animation.addByPrefix('tapUpNormal', 'green instance'); - this.animation.addByPrefix('tapRightNormal', 'red instance'); + this.animation.addByPrefix('tapLeftFunkin', 'purple instance'); + this.animation.addByPrefix('tapDownFunkin', 'blue instance'); + this.animation.addByPrefix('tapUpFunkin', 'green instance'); + this.animation.addByPrefix('tapRightFunkin', 'red instance'); - this.animation.addByPrefix('holdLeftNormal', 'LeftHoldPiece'); - this.animation.addByPrefix('holdDownNormal', 'DownHoldPiece'); - this.animation.addByPrefix('holdUpNormal', 'UpHoldPiece'); - this.animation.addByPrefix('holdRightNormal', 'RightHoldPiece'); + this.animation.addByPrefix('holdLeftFunkin', 'LeftHoldPiece'); + this.animation.addByPrefix('holdDownFunkin', 'DownHoldPiece'); + this.animation.addByPrefix('holdUpFunkin', 'UpHoldPiece'); + this.animation.addByPrefix('holdRightFunkin', 'RightHoldPiece'); - this.animation.addByPrefix('holdEndLeftNormal', 'LeftHoldEnd'); - this.animation.addByPrefix('holdEndDownNormal', 'DownHoldEnd'); - this.animation.addByPrefix('holdEndUpNormal', 'UpHoldEnd'); - this.animation.addByPrefix('holdEndRightNormal', 'RightHoldEnd'); + this.animation.addByPrefix('holdEndLeftFunkin', 'LeftHoldEnd'); + this.animation.addByPrefix('holdEndDownFunkin', 'DownHoldEnd'); + this.animation.addByPrefix('holdEndUpFunkin', 'UpHoldEnd'); + this.animation.addByPrefix('holdEndRightFunkin', 'RightHoldEnd'); this.animation.addByPrefix('tapLeftPixel', 'pixel4'); this.animation.addByPrefix('tapDownPixel', 'pixel5'); @@ -187,8 +187,8 @@ class ChartEditorNoteSprite extends FlxSprite function get_noteStyle():String { - // Fall back to 'Normal' if it's not a valid note style. - return if (NOTE_STYLES.contains(this.parentState.currentSongNoteSkin)) this.parentState.currentSongNoteSkin else 'Normal'; + // Fall back to Funkin' if it's not a valid note style. + return if (NOTE_STYLES.contains(this.parentState.currentSongNoteStyle)) this.parentState.currentSongNoteStyle else 'funkin'; } public function playNoteAnimation():Void @@ -199,7 +199,7 @@ class ChartEditorNoteSprite extends FlxSprite var baseAnimationName:String = 'tap'; // Play the appropriate animation for the type, direction, and skin. - var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}'; + var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle.toTitleCase()}'; this.animation.play(animationName); @@ -213,7 +213,7 @@ class ChartEditorNoteSprite extends FlxSprite this.updateHitbox(); // TODO: Make this an attribute of the note skin. - this.antialiasing = (this.parentState.currentSongNoteSkin != 'Pixel'); + this.antialiasing = (this.parentState.currentSongNoteStyle != 'Pixel'); } /** diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index add65c5bf..1abbc6e61 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,5 +1,8 @@ package funkin.ui.debug.charting; +import funkin.play.stage.StageData; +import funkin.play.character.CharacterData.CharacterDataParser; +import funkin.play.character.CharacterData; import flixel.system.FlxAssets.FlxSoundAsset; import flixel.math.FlxMath; import haxe.ui.components.TextField; @@ -41,7 +44,7 @@ import funkin.data.song.SongRegistry; import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongMetadata; import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongPlayableChar; +import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongDataUtils; import funkin.ui.debug.charting.ChartEditorCommand; import funkin.ui.debug.charting.ChartEditorCommand; @@ -88,8 +91,11 @@ using Lambda; // @:nullSafety(Loose) // Enable this while developing, then disable to keep unit tests functional! @:allow(funkin.ui.debug.charting.ChartEditorCommand) +@:allow(funkin.ui.debug.charting.ChartEditorDropdowns) @:allow(funkin.ui.debug.charting.ChartEditorDialogHandler) @:allow(funkin.ui.debug.charting.ChartEditorThemeHandler) +@:allow(funkin.ui.debug.charting.ChartEditorAudioHandler) +@:allow(funkin.ui.debug.charting.ChartEditorImportExportHandler) @:allow(funkin.ui.debug.charting.ChartEditorToolboxHandler) class ChartEditorState extends HaxeUIState { @@ -108,7 +114,6 @@ class ChartEditorState extends HaxeUIState static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); - static final CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:String = Paths.ui('chart-editor/toolbox/characters'); static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); @@ -950,19 +955,19 @@ class ChartEditorState extends HaxeUIState return currentSongChartData.events = value; } - public var currentSongNoteSkin(get, set):String; + public var currentSongNoteStyle(get, set):String; - function get_currentSongNoteSkin():String + function get_currentSongNoteStyle():String { if (currentSongMetadata.playData.noteSkin == null) { // Initialize to the default value if not set. - currentSongMetadata.playData.noteSkin = 'Normal'; + currentSongMetadata.playData.noteSkin = 'funkin'; } return currentSongMetadata.playData.noteSkin; } - function set_currentSongNoteSkin(value:String):String + function set_currentSongNoteStyle(value:String):String { return currentSongMetadata.playData.noteSkin = value; } @@ -1025,57 +1030,28 @@ class ChartEditorState extends HaxeUIState return currentSongMetadata.artist = value; } - var currentSongPlayableCharacters(get, never):Array<String>; - - function get_currentSongPlayableCharacters():Array<String> - { - return currentSongMetadata.playData.playableChars.keys().array(); - } - var currentSongCharacterPlayer(get, set):String; function get_currentSongCharacterPlayer():String { - // Validate selected character before returning it. - if (!currentSongPlayableCharacters.contains(selectedCharacter)) - { - trace('Invalid character selected: ' + selectedCharacter); - selectedCharacter = currentSongPlayableCharacters[0]; - } - - return selectedCharacter; + return currentSongMetadata.playData.characters.player; } function set_currentSongCharacterPlayer(value:String):String { - if (!currentSongPlayableCharacters.contains(value)) - { - trace('Invalid character selected: ' + value); - return value; - } - - return selectedCharacter = value; + return currentSongMetadata.playData.characters.player = value; } var currentSongCharacterOpponent(get, set):String; function get_currentSongCharacterOpponent():String { - // Validate selected character before returning it. - if (!currentSongPlayableCharacters.contains(selectedCharacter)) - { - trace('Invalid character selected: ' + selectedCharacter); - selectedCharacter = currentSongPlayableCharacters[0]; - } - - var playableCharData:SongPlayableChar = currentSongMetadata.playData.playableChars.get(selectedCharacter); - return playableCharData.opponent; + return currentSongMetadata.playData.characters.opponent; } function set_currentSongCharacterOpponent(value:String):String { - var playableCharData:SongPlayableChar = currentSongMetadata.playData.playableChars.get(selectedCharacter); - return playableCharData.opponent = value; + return currentSongMetadata.playData.characters.opponent = value; } /** @@ -1602,7 +1578,7 @@ class ChartEditorState extends HaxeUIState addUIClickListener('menubarItemNewChart', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true)); addUIClickListener('menubarItemOpenChart', _ -> ChartEditorDialogHandler.openBrowseWizard(this, true)); - addUIClickListener('menubarItemSaveChartAs', _ -> exportAllSongData()); + addUIClickListener('menubarItemSaveChartAs', _ -> ChartEditorImportExportHandler.exportAllSongData(this)); addUIClickListener('menubarItemLoadInst', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true)); addUIClickListener('menubarItemImportChart', _ -> ChartEditorDialogHandler.openImportChartDialog(this, 'legacy', true)); @@ -1738,18 +1714,14 @@ class ChartEditorState extends HaxeUIState }); } - addUIChangeListener('menubarItemToggleToolboxTools', - event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT, event.value)); - addUIChangeListener('menubarItemToggleToolboxNotes', - event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value)); - addUIChangeListener('menubarItemToggleToolboxEvents', - event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value)); addUIChangeListener('menubarItemToggleToolboxDifficulty', event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value)); addUIChangeListener('menubarItemToggleToolboxMetadata', event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value)); - addUIChangeListener('menubarItemToggleToolboxCharacters', - event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT, event.value)); + addUIChangeListener('menubarItemToggleToolboxNotes', + event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value)); + addUIChangeListener('menubarItemToggleToolboxEvents', + event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value)); addUIChangeListener('menubarItemToggleToolboxPlayerPreview', event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value)); addUIChangeListener('menubarItemToggleToolboxOpponentPreview', @@ -1795,7 +1767,7 @@ class ChartEditorState extends HaxeUIState // Auto-save to local storage. #else // Auto-save to temp file. - exportAllSongData(true, true); + ChartEditorImportExportHandler.exportAllSongData(this, true, true); #end } @@ -1806,7 +1778,7 @@ class ChartEditorState extends HaxeUIState if (saveDataDirty) { - exportAllSongData(true); + ChartEditorImportExportHandler.exportAllSongData(this, true); } } @@ -2407,13 +2379,20 @@ class ChartEditorState extends HaxeUIState var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs; var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE; - gridGhostHoldNote.visible = true; - gridGhostHoldNote.noteData = gridGhostNote.noteData; - gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); + if (dragLengthSteps > 0) + { + gridGhostHoldNote.visible = true; + gridGhostHoldNote.noteData = gridGhostNote.noteData; + gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection(); - gridGhostHoldNote.setHeightDirectly(dragLengthPixels); + gridGhostHoldNote.setHeightDirectly(dragLengthPixels); - gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); + gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes); + } + else + { + gridGhostHoldNote.visible = false; + } if (FlxG.mouse.justReleased) { @@ -3016,6 +2995,12 @@ class ChartEditorState extends HaxeUIState ChartEditorDialogHandler.openBrowseWizard(this, true); } + // CTRL + SHIFT + S = Save As + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.S) + { + ChartEditorImportExportHandler.exportAllSongData(this, false); + } + // CTRL + Q = Quit to Menu if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q) { @@ -3167,7 +3152,7 @@ class ChartEditorState extends HaxeUIState selectedDifficulty = prevDifficulty; refreshDifficultyTreeSelection(); - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } else { @@ -3176,7 +3161,7 @@ class ChartEditorState extends HaxeUIState selectedDifficulty = prevDifficulty; refreshDifficultyTreeSelection(); - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } } else @@ -3195,7 +3180,7 @@ class ChartEditorState extends HaxeUIState selectedDifficulty = nextDifficulty; refreshDifficultyTreeSelection(); - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } else { @@ -3204,7 +3189,7 @@ class ChartEditorState extends HaxeUIState selectedDifficulty = nextDifficulty; refreshDifficultyTreeSelection(); - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } } @@ -3296,6 +3281,28 @@ class ChartEditorState extends HaxeUIState } } + public function createDifficulty(variation:String, difficulty:String, scrollSpeed:Float = 1.0) + { + var variationMetadata:Null<SongMetadata> = songMetadata.get(variation); + if (variationMetadata == null) return; + + variationMetadata.playData.difficulties.push(difficulty); + + var resultChartData = songChartData.get(variation); + if (resultChartData == null) + { + resultChartData = new SongChartData([difficulty => scrollSpeed], [], [difficulty => []]); + songChartData.set(variation, resultChartData); + } + else + { + resultChartData.scrollSpeed.set(difficulty, scrollSpeed); + resultChartData.notes.set(difficulty, []); + } + + difficultySelectDirty = true; // Force the Difficulty toolbox to update. + } + function refreshDifficultyTreeSelection(?treeView:TreeView):Void { if (treeView == null) @@ -3469,7 +3476,7 @@ class ChartEditorState extends HaxeUIState selectedVariation = variation; selectedDifficulty = difficulty; // refreshDifficultyTreeSelection(treeView); - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } // case 'song': // case 'variation': @@ -3478,14 +3485,14 @@ class ChartEditorState extends HaxeUIState trace('Selected wrong node type, resetting selection.'); var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView); if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode; - refreshSongMetadataToolbox(); + refreshMetadataToolbox(); } } /** * When the difficulty changes, update the song metadata toolbox to reflect the new data. */ - function refreshSongMetadataToolbox():Void + function refreshMetadataToolbox():Void { var toolbox:Null<CollapsibleDialog> = ChartEditorToolboxHandler.getToolbox(this, CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); if (toolbox == null) return; @@ -3499,8 +3506,8 @@ class ChartEditorState extends HaxeUIState var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown); if (inputStage != null) inputStage.value = currentSongMetadata.playData.stage; - var inputNoteSkin:Null<DropDown> = toolbox.findComponent('inputNoteSkin', DropDown); - if (inputNoteSkin != null) inputNoteSkin.value = currentSongMetadata.playData.noteSkin; + var inputNoteStyle:Null<DropDown> = toolbox.findComponent('inputNoteStyle', DropDown); + if (inputNoteStyle != null) inputNoteStyle.value = currentSongMetadata.playData.noteSkin; var inputBPM:Null<NumberStepper> = toolbox.findComponent('inputBPM', NumberStepper); if (inputBPM != null) inputBPM.value = currentSongMetadata.timeChanges[0].bpm; @@ -3515,16 +3522,54 @@ class ChartEditorState extends HaxeUIState if (frameVariation != null) frameVariation.text = 'Variation: ${selectedVariation.toTitleCase()}'; var frameDifficulty:Null<Frame> = toolbox.findComponent('frameDifficulty', Frame); if (frameDifficulty != null) frameDifficulty.text = 'Difficulty: ${selectedDifficulty.toTitleCase()}'; - } - function addDifficulty(variation:String):Void {} + var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown); + var stageId:String = currentSongMetadata.playData.stage; + var stageData:Null<StageData> = StageDataParser.parseStageData(stageId); + if (stageData != null) + { + inputStage.value = {id: stageId, text: stageData.name}; + } + else + { + inputStage.value = {id: "mainStage", text: "Main Stage"}; + } - function addVariation(variationId:String):Void - { - // Create a new variation with the specified ID. - songMetadata.set(variationId, currentSongMetadata.clone(variationId)); - // Switch to the new variation. - selectedVariation = variationId; + var inputCharacterPlayer:Null<DropDown> = toolbox.findComponent('inputCharacterPlayer', DropDown); + var charIdPlayer:String = currentSongMetadata.playData.characters.player; + var charDataPlayer:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdPlayer); + if (charDataPlayer != null) + { + inputCharacterPlayer.value = {id: charIdPlayer, text: charDataPlayer.name}; + } + else + { + inputCharacterPlayer.value = {id: "bf", text: "Boyfriend"}; + } + + var inputCharacterOpponent:Null<DropDown> = toolbox.findComponent('inputCharacterOpponent', DropDown); + var charIdOpponent:String = currentSongMetadata.playData.characters.opponent; + var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdOpponent); + if (charDataOpponent != null) + { + inputCharacterOpponent.value = {id: charIdOpponent, text: charDataOpponent.name}; + } + else + { + inputCharacterOpponent.value = {id: "dad", text: "Dad"}; + } + + var inputCharacterGirlfriend:Null<DropDown> = toolbox.findComponent('inputCharacterGirlfriend', DropDown); + var charIdGirlfriend:String = currentSongMetadata.playData.characters.girlfriend; + var charDataGirlfriend:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdGirlfriend); + if (charDataGirlfriend != null) + { + inputCharacterGirlfriend.value = {id: charIdGirlfriend, text: charDataGirlfriend.name}; + } + else + { + inputCharacterGirlfriend.value = {id: "none", text: "None"}; + } } /** @@ -3710,9 +3755,9 @@ class ChartEditorState extends HaxeUIState switch (noteData.getStrumlineIndex()) { case 0: // Player - if (hitsoundsEnabledPlayer) playSound(Paths.sound('funnyNoise/funnyNoise-09')); + if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-09')); case 1: // Opponent - if (hitsoundsEnabledOpponent) playSound(Paths.sound('funnyNoise/funnyNoise-010')); + if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-010')); } } } @@ -3913,77 +3958,6 @@ class ChartEditorState extends HaxeUIState Conductor.update(targetPos); } - /** - * Loads an instrumental from an absolute file path, replacing the current instrumental. - * - * @param path The absolute path to the audio file. - * - * @return Success or failure. - */ - public function loadInstrumentalFromPath(path:Path):Bool - { - #if sys - // Validate file extension. - if (path.ext != null && !SUPPORTED_MUSIC_FORMATS.contains(path.ext)) - { - return false; - } - - var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString()); - return loadInstrumentalFromBytes(fileBytes, '${path.file}.${path.ext}'); - #else - trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); - return false; - #end - } - - /** - * Loads an instrumental from audio byte data, replacing the current instrumental. - * @param bytes The audio byte data. - * @param fileName The name of the file, if available. Used for notifications. - * @return Success or failure. - */ - public function loadInstrumentalFromBytes(bytes:haxe.io.Bytes, fileName:String = null):Bool - { - if (bytes == null) - { - return false; - } - - var openflSound:openfl.media.Sound = new openfl.media.Sound(); - openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); - audioInstTrack = FlxG.sound.load(openflSound, 1.0, false); - audioInstTrack.autoDestroy = false; - audioInstTrack.pause(); - - audioInstTrackData = bytes; - - postLoadInstrumental(); - - return true; - } - - /** - * Loads an instrumental from an OpenFL asset, replacing the current instrumental. - * @param path The path to the asset. Use `Paths` to build this. - * @return Success or failure. - */ - public function loadInstrumentalFromAsset(path:String):Bool - { - var instTrack:FlxSound = FlxG.sound.load(path, 1.0, false); - if (instTrack != null) - { - audioInstTrack = instTrack; - - audioInstTrackData = Assets.getBytes(path); - - postLoadInstrumental(); - return true; - } - - return false; - } - public function postLoadInstrumental():Void { if (audioInstTrack != null) @@ -4014,23 +3988,6 @@ class ChartEditorState extends HaxeUIState moveSongToScrollPosition(); } - /** - * Loads a vocal track from an absolute file path. - * @param path The absolute path to the audio file. - * @param charKey The character to load the vocal track for. - * @return Success or failure. - */ - public function loadVocalsFromPath(path:Path, charKey:String = 'default'):Bool - { - #if sys - var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString()); - return loadVocalsFromBytes(fileBytes, charKey); - #else - trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); - return false; - #end - } - /** * Clear the voices group. */ @@ -4039,141 +3996,6 @@ class ChartEditorState extends HaxeUIState if (audioVocalTrackGroup != null) audioVocalTrackGroup.clear(); } - /** - * Load a vocal track for a given song and character and add it to the voices group. - * - * @param path ID of the asset. - * @param charKey Character to load the vocal track for. - * @return Success or failure. - */ - public function loadVocalsFromAsset(path:String, charType:CharacterType = OTHER):Bool - { - var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false); - if (vocalTrack != null) - { - switch (charType) - { - case CharacterType.BF: - if (audioVocalTrackGroup != null) audioVocalTrackGroup.addPlayerVoice(vocalTrack); - audioVocalTrackData.set(currentSongCharacterPlayer, Assets.getBytes(path)); - case CharacterType.DAD: - if (audioVocalTrackGroup != null) audioVocalTrackGroup.addOpponentVoice(vocalTrack); - audioVocalTrackData.set(currentSongCharacterOpponent, Assets.getBytes(path)); - default: - if (audioVocalTrackGroup != null) audioVocalTrackGroup.add(vocalTrack); - audioVocalTrackData.set('default', Assets.getBytes(path)); - } - - return true; - } - return false; - } - - /** - * Loads a vocal track from audio byte data. - */ - public function loadVocalsFromBytes(bytes:haxe.io.Bytes, charKey:String = ''):Bool - { - var openflSound:openfl.media.Sound = new openfl.media.Sound(); - openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); - var vocalTrack:FlxSound = FlxG.sound.load(openflSound, 1.0, false); - if (audioVocalTrackGroup != null) audioVocalTrackGroup.add(vocalTrack); - audioVocalTrackData.set(charKey, bytes); - return true; - } - - /** - * Fetch's a song's existing chart and audio and loads it, replacing the current song. - */ - public function loadSongAsTemplate(songId:String):Void - { - var song:Null<Song> = SongRegistry.instance.fetchEntry(songId); - - if (song == null) return; - - // Load the song metadata. - var rawSongMetadata:Array<SongMetadata> = song.getRawMetadata(); - var songMetadata:Map<String, SongMetadata> = []; - var songChartData:Map<String, SongChartData> = []; - - for (metadata in rawSongMetadata) - { - if (metadata == null) continue; - var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation; - - // Clone to prevent modifying the original. - var metadataClone:SongMetadata = metadata.clone(variation); - if (metadataClone != null) songMetadata.set(variation, metadataClone); - - songChartData.set(variation, SongRegistry.instance.parseEntryChartData(songId, metadata.variation)); - } - - loadSong(songMetadata, songChartData); - - sortChartData(); - - clearVocals(); - - loadInstrumentalFromAsset(Paths.inst(songId)); - - var diff:Null<SongDifficulty> = song.getDifficulty(selectedDifficulty); - var voiceList:Array<String> = diff != null ? diff.buildVoiceList(currentSongCharacterPlayer) : []; - if (voiceList.length == 2) - { - loadVocalsFromAsset(voiceList[0], BF); - loadVocalsFromAsset(voiceList[1], DAD); - } - else - { - for (voicePath in voiceList) - { - loadVocalsFromAsset(voicePath); - } - } - - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Success', - body: 'Loaded song (${rawSongMetadata[0].songName})', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end - } - - /** - * Loads song metadata and chart data into the editor. - * @param newSongMetadata The song metadata to load. - * @param newSongChartData The song chart data to load. - */ - public function loadSong(newSongMetadata:Map<String, SongMetadata>, newSongChartData:Map<String, SongChartData>):Void - { - this.songMetadata = newSongMetadata; - this.songChartData = newSongChartData; - - Conductor.forceBPM(null); // Disable the forced BPM. - Conductor.mapTimeChanges(currentSongMetadata.timeChanges); - - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - difficultySelectDirty = true; - opponentPreviewDirty = true; - playerPreviewDirty = true; - - // Remove instrumental and vocal tracks, they will be loaded next. - if (audioInstTrack != null) - { - audioInstTrack.stop(); - audioInstTrack = null; - } - if (audioVocalTrackGroup != null) - { - audioVocalTrackGroup.stop(); - audioVocalTrackGroup.clear(); - } - } - /** * When setting the scroll position, except when automatically scrolling during song playback, * we need to update the conductor's current step time and the timestamp of the audio tracks. @@ -4291,7 +4113,7 @@ class ChartEditorState extends HaxeUIState function playMetronomeTick(high:Bool = false):Void { - playSound(Paths.sound('pianoStuff/piano-${high ? '001' : '008'}')); + ChartEditorAudioHandler.playSound(Paths.sound('pianoStuff/piano-${high ? '001' : '008'}')); } function isNoteSelected(note:Null<SongNoteData>):Bool @@ -4304,27 +4126,6 @@ class ChartEditorState extends HaxeUIState return event != null && currentEventSelection.indexOf(event) != -1; } - /** - * Play a sound effect. - * Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance. - */ - function playSound(path:String):Void - { - var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound(); - - var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path); - if (asset == null) - { - trace('WARN: Failed to play sound $path, asset not found.'); - return; - } - - snd.loadEmbedded(asset); - snd.autoDestroy = true; - FlxG.sound.list.add(snd); - snd.play(); - } - override function destroy():Void { super.destroy(); @@ -4345,78 +4146,6 @@ class ChartEditorState extends HaxeUIState { NotificationManager.instance.clearNotifications(); } - - /** - * @param force Whether to force the export without prompting the user for a file location. - * @param tmp If true, save to the temporary directory instead of the local `backup` directory. - */ - public function exportAllSongData(force:Bool = false, tmp:Bool = false):Void - { - var zipEntries:Array<haxe.zip.Entry> = []; - - for (variation in availableVariations) - { - var variationId:String = variation; - if (variation == '' || variation == 'default' || variation == 'normal') - { - variationId = ''; - } - - if (variationId == '') - { - var variationMetadata:Null<SongMetadata> = songMetadata.get(variation); - if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('$currentSongId-metadata.json', SerializerUtil.toJSON(variationMetadata))); - var variationChart:Null<SongChartData> = songChartData.get(variation); - if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('$currentSongId-chart.json', SerializerUtil.toJSON(variationChart))); - } - else - { - var variationMetadata:Null<SongMetadata> = songMetadata.get(variation); - if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('$currentSongId-metadata-$variationId.json', - SerializerUtil.toJSON(variationMetadata))); - var variationChart:Null<SongChartData> = songChartData.get(variation); - if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('$currentSongId-chart-$variationId.json', SerializerUtil.toJSON(variationChart))); - } - } - - if (audioInstTrackData != null) zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', audioInstTrackData)); - for (charId in audioVocalTrackData.keys()) - { - var entryData = audioVocalTrackData.get(charId); - if (entryData == null) continue; - zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-$charId.ogg', entryData)); - } - - trace('Exporting ${zipEntries.length} files to ZIP...'); - - if (force) - { - var targetPath:String = if (tmp) - { - Path.join([FileUtil.getTempDir(), 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']); - } - else - { - Path.join(['./backups/', 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']); - } - - // We have to force write because the program will die before the save dialog is closed. - trace('Force exporting to $targetPath...'); - FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath); - return; - } - - // Prompt and save. - var onSave:Array<String>->Void = function(paths:Array<String>) { - trace('Successfully exported files.'); - }; - - var onCancel:Void->Void = function() { - trace('Export cancelled.'); - }; - - FileUtil.saveMultipleFiles(zipEntries, onSave, onCancel, '$currentSongId-chart.zip'); - } } enum LiveInputStyle diff --git a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx index db090542d..6f89b6b63 100644 --- a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx @@ -1,5 +1,10 @@ package funkin.ui.debug.charting; +import funkin.ui.haxeui.components.FunkinDropDown; +import funkin.play.stage.StageData.StageDataParser; +import funkin.play.stage.StageData; +import funkin.play.character.CharacterData; +import funkin.play.character.CharacterData.CharacterDataParser; import haxe.ui.components.HorizontalSlider; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; @@ -9,6 +14,7 @@ import funkin.data.event.SongEventData; import funkin.data.song.SongData.SongTimeChange; import funkin.play.song.SongSerializer; import funkin.ui.haxeui.components.CharacterPlayer; +import funkin.util.FileUtil; import haxe.ui.components.Button; import haxe.ui.components.CheckBox; import haxe.ui.components.DropDown; @@ -78,8 +84,6 @@ class ChartEditorToolboxHandler onShowToolboxDifficulty(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: onShowToolboxMetadata(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT: - onShowToolboxCharacters(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: onShowToolboxPlayerPreview(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT: @@ -117,8 +121,6 @@ class ChartEditorToolboxHandler onHideToolboxDifficulty(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: onHideToolboxMetadata(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT: - onHideToolboxCharacters(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: onHideToolboxPlayerPreview(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT: @@ -167,8 +169,6 @@ class ChartEditorToolboxHandler toolbox = buildToolboxDifficultyLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: toolbox = buildToolboxMetadataLayout(state); - case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT: - toolbox = buildToolboxCharactersLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: toolbox = buildToolboxPlayerPreviewLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT: @@ -445,14 +445,20 @@ class ChartEditorToolboxHandler state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false); } + var difficultyToolboxAddVariation:Null<Button> = toolbox.findComponent('difficultyToolboxAddVariation', Button); + if (difficultyToolboxAddVariation == null) + throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxAddVariation component.'; + var difficultyToolboxAddDifficulty:Null<Button> = toolbox.findComponent('difficultyToolboxAddDifficulty', Button); + if (difficultyToolboxAddDifficulty == null) + throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxAddDifficulty component.'; var difficultyToolboxSaveMetadata:Null<Button> = toolbox.findComponent('difficultyToolboxSaveMetadata', Button); if (difficultyToolboxSaveMetadata == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveMetadata component.'; var difficultyToolboxSaveChart:Null<Button> = toolbox.findComponent('difficultyToolboxSaveChart', Button); if (difficultyToolboxSaveChart == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveChart component.'; - var difficultyToolboxSaveAll:Null<Button> = toolbox.findComponent('difficultyToolboxSaveAll', Button); - if (difficultyToolboxSaveAll == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveAll component.'; + // var difficultyToolboxSaveAll:Null<Button> = toolbox.findComponent('difficultyToolboxSaveAll', Button); + // if (difficultyToolboxSaveAll == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxSaveAll component.'; var difficultyToolboxLoadMetadata:Null<Button> = toolbox.findComponent('difficultyToolboxLoadMetadata', Button); if (difficultyToolboxLoadMetadata == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxLoadMetadata component.'; @@ -460,26 +466,32 @@ class ChartEditorToolboxHandler if (difficultyToolboxLoadChart == null) throw 'ChartEditorToolboxHandler.buildToolboxDifficultyLayout() - Could not find difficultyToolboxLoadChart component.'; - difficultyToolboxSaveMetadata.onClick = function(event:UIEvent) { - SongSerializer.exportSongMetadata(state.currentSongMetadata, state.currentSongId); + difficultyToolboxAddVariation.onClick = function(_:UIEvent) { + ChartEditorDialogHandler.openAddVariationDialog(state, true); }; - difficultyToolboxSaveChart.onClick = function(event:UIEvent) { - SongSerializer.exportSongChartData(state.currentSongChartData, state.currentSongId); + difficultyToolboxAddDifficulty.onClick = function(_:UIEvent) { + ChartEditorDialogHandler.openAddDifficultyDialog(state, true); }; - difficultyToolboxSaveAll.onClick = function(event:UIEvent) { - state.exportAllSongData(); + difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) { + var vari:String = state.selectedVariation != Constants.DEFAULT_VARIATION ? '-${state.selectedVariation}' : ''; + FileUtil.writeFileReference('${state.currentSongId}$vari-metadata.json', state.currentSongMetadata.serialize()); }; - difficultyToolboxLoadMetadata.onClick = function(event:UIEvent) { + difficultyToolboxSaveChart.onClick = function(_:UIEvent) { + var vari:String = state.selectedVariation != Constants.DEFAULT_VARIATION ? '-${state.selectedVariation}' : ''; + FileUtil.writeFileReference('${state.currentSongId}$vari-chart.json', state.currentSongChartData.serialize()); + }; + + difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) { // Replace metadata for current variation. SongSerializer.importSongMetadataAsync(function(songMetadata) { state.currentSongMetadata = songMetadata; }); }; - difficultyToolboxLoadChart.onClick = function(event:UIEvent) { + difficultyToolboxLoadChart.onClick = function(_:UIEvent) { // Replace chart data for current variation. SongSerializer.importSongChartDataAsync(function(songChartData) { state.currentSongChartData = songChartData; @@ -554,7 +566,7 @@ class ChartEditorToolboxHandler }; inputSongArtist.value = state.currentSongMetadata.artist; - var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown); + var inputStage:Null<FunkinDropDown> = toolbox.findComponent('inputStage', FunkinDropDown); if (inputStage == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputStage component.'; inputStage.onChange = function(event:UIEvent) { var valid:Bool = event.data != null && event.data.id != null; @@ -564,15 +576,48 @@ class ChartEditorToolboxHandler state.currentSongMetadata.playData.stage = event.data.id; } }; - inputStage.value = state.currentSongMetadata.playData.stage; + var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(inputStage, state.currentSongMetadata.playData.stage); + inputStage.value = startingValueStage; - var inputNoteSkin:Null<DropDown> = toolbox.findComponent('inputNoteSkin', DropDown); - if (inputNoteSkin == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputNoteSkin component.'; - inputNoteSkin.onChange = function(event:UIEvent) { - if ((event?.data?.id ?? null) == null) return; - state.currentSongNoteSkin = event.data.id; + var inputNoteStyle:Null<FunkinDropDown> = toolbox.findComponent('inputNoteStyle', FunkinDropDown); + if (inputNoteStyle == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputNoteStyle component.'; + inputNoteStyle.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + state.currentSongNoteStyle = event.data.id; }; - inputNoteSkin.value = state.currentSongNoteSkin; + inputNoteStyle.value = state.currentSongNoteStyle; + + // By using this flag, we prevent the dropdown value from changing while it is being populated. + + var inputCharacterPlayer:Null<FunkinDropDown> = toolbox.findComponent('inputCharacterPlayer', FunkinDropDown); + if (inputCharacterPlayer == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterPlayer component.'; + inputCharacterPlayer.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + state.currentSongMetadata.playData.characters.player = event.data.id; + }; + var startingValuePlayer = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterPlayer, CharacterType.BF, + state.currentSongMetadata.playData.characters.player); + inputCharacterPlayer.value = startingValuePlayer; + + var inputCharacterOpponent:Null<FunkinDropDown> = toolbox.findComponent('inputCharacterOpponent', FunkinDropDown); + if (inputCharacterOpponent == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterOpponent component.'; + inputCharacterOpponent.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + state.currentSongMetadata.playData.characters.opponent = event.data.id; + }; + var startingValueOpponent = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterOpponent, CharacterType.DAD, + state.currentSongMetadata.playData.characters.opponent); + inputCharacterOpponent.value = startingValueOpponent; + + var inputCharacterGirlfriend:Null<FunkinDropDown> = toolbox.findComponent('inputCharacterGirlfriend', FunkinDropDown); + if (inputCharacterGirlfriend == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterGirlfriend component.'; + inputCharacterGirlfriend.onChange = function(event:UIEvent) { + if (event.data?.id == null) return; + state.currentSongMetadata.playData.characters.girlfriend = event.data.id == "none" ? "" : event.data.id; + }; + var startingValueGirlfriend = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterGirlfriend, CharacterType.GF, + state.currentSongMetadata.playData.characters.girlfriend); + inputCharacterGirlfriend.value = startingValueGirlfriend; var inputBPM:Null<NumberStepper> = toolbox.findComponent('inputBPM', NumberStepper); if (inputBPM == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputBPM component.'; @@ -630,32 +675,11 @@ class ChartEditorToolboxHandler static function onShowToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void { - state.refreshSongMetadataToolbox(); + state.refreshMetadataToolbox(); } static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxCharactersLayout(state:ChartEditorState):Null<CollapsibleDialog> - { - var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT); - - if (toolbox == null) return null; - - // Starting position. - toolbox.x = 175; - toolbox.y = 300; - - toolbox.onDialogClosed = function(event:DialogEvent) { - state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false); - } - - return toolbox; - } - - static function onShowToolboxCharacters(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - - static function onHideToolboxCharacters(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog> { var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT); diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index b454ca429..e422238a9 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -2,6 +2,7 @@ package funkin.util; import flixel.util.FlxColor; import lime.app.Application; +import funkin.data.song.SongData.SongTimeFormat; class Constants { @@ -22,6 +23,16 @@ class Constants */ public static var VERSION(get, never):String; + /** + * The generatedBy string embedded in the chart files made by this application. + */ + public static var GENERATED_BY(get, never):String; + + static function get_GENERATED_BY():String + { + return '${Constants.TITLE} - ${Constants.VERSION}'; + } + /** * A suffix to add to the game version. * Add a suffix to prototype builds and remove it for releases. @@ -140,7 +151,32 @@ class Constants /** * The default BPM for charts, so things don't break if none is specified. */ - public static final DEFAULT_BPM:Int = 100; + public static final DEFAULT_BPM:Float = 100.0; + + /** + * The default name for songs. + */ + public static final DEFAULT_SONGNAME:String = "Unknown"; + + /** + * The default artist for songs. + */ + public static final DEFAULT_ARTIST:String = "Unknown"; + + /** + * The default note style for songs. + */ + public static final DEFAULT_NOTE_STYLE:String = "funkin"; + + /** + * The default timing format for songs. + */ + public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS; + + /** + * The default scroll speed for songs. + */ + public static final DEFAULT_SCROLLSPEED:Float = 1.0; /** * Default numerator for the time signature. @@ -293,6 +329,39 @@ class Constants */ public static final GHOST_TAPPING:Bool = false; + /** + * FILE EXTENSIONS + */ + // ============================== + + /** + * The file extension used when exporting chart files. + * + * - "I made a new file format" + * - "Actually new or just a renamed ZIP?" + */ + public static final EXT_CHART = "fnfc"; + + /** + * The file extension used when loading audio files. + */ + public static final EXT_SOUND = #if web "mp3" #else "ogg" #end; + + /** + * The file extension used when loading video files. + */ + public static final EXT_VIDEO = "mp4"; + + /** + * The file extension used when loading image files. + */ + public static final EXT_IMAGE = "png"; + + /** + * The file extension used when loading data files. + */ + public static final EXT_DATA = "json"; + /** * OTHER */ diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 3a6f4e330..bae3126fb 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -5,10 +5,9 @@ import lime.utils.Bytes; import lime.ui.FileDialog; import openfl.net.FileFilter; import haxe.io.Path; -#if html5 import openfl.net.FileReference; import openfl.events.Event; -#end +import openfl.events.IOErrorEvent; /** * Utilities for reading and writing files on various platforms. @@ -260,8 +259,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 + public static function saveFilesAsZIP(resources:Array<Entry>, ?onSave:Array<String>->Void, ?onCancel:Void->Void, ?defaultPath:String, force:Bool = false):Bool { // Create a ZIP file. var zipBytes:Bytes = createZIPFromEntries(resources); @@ -309,6 +307,7 @@ class FileUtil #if sys return sys.io.File.getContent(path); #else + trace('ERROR: readStringFromPath not implemented for this platform'); return null; #end } @@ -329,6 +328,48 @@ class FileUtil #end } + /** + * Browse for a file to read and execute a callback once we have a file reference. + * Works great on HTML5 or desktop. + * + * @param callback The function to call when the file is loaded. + */ + public static function browseFileReference(callback:FileReference->Void) + { + var file = new FileReference(); + + file.addEventListener(Event.SELECT, function(e) { + var selectedFileRef:FileReference = e.target; + trace('Selected file: ' + selectedFileRef.name); + selectedFileRef.addEventListener(Event.COMPLETE, function(e) { + var loadedFileRef:FileReference = e.target; + trace('Loaded file: ' + loadedFileRef.name); + callback(loadedFileRef); + }); + selectedFileRef.load(); + }); + + file.browse(); + } + + /** + * Prompts the user to save a file to their computer. + */ + public static function writeFileReference(path:String, data:String) + { + var file = new FileReference(); + file.addEventListener(Event.COMPLETE, function(e:Event) { + trace('Successfully wrote file.'); + }); + file.addEventListener(Event.CANCEL, function(e:Event) { + trace('Cancelled writing file.'); + }); + file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent) { + trace('IO error writing file.'); + }); + file.save(data, path); + } + /** * Read JSON file contents directly from a given path. * Only works on desktop. diff --git a/source/funkin/util/SerializerUtil.hx b/source/funkin/util/SerializerUtil.hx index 26563efce..0af0fc9ea 100644 --- a/source/funkin/util/SerializerUtil.hx +++ b/source/funkin/util/SerializerUtil.hx @@ -13,6 +13,7 @@ typedef ScoreInput = /** * A class of functions dedicated to serializing and deserializing data. + * TODO: Rewrite/refactor this to use json2object. */ class SerializerUtil { From be4fd74d4030e89e650f348d5581d9604814c5ce Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 25 Sep 2023 23:24:18 -0400 Subject: [PATCH 03/74] Resolve metadata loading issues. --- source/funkin/data/song/SongData.hx | 22 +++--- source/funkin/data/song/SongRegistry.hx | 93 ++++++++++++++++--------- source/funkin/play/song/Song.hx | 29 ++++---- 3 files changed, 88 insertions(+), 56 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 5eb221094..d557bd39c 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -47,7 +47,7 @@ class SongMetadata @:jignored public var variation:String; - public function new(songName:String, artist:String, variation:String = 'default') + public function new(songName:String, artist:String, ?variation:String) { this.version = SongRegistry.SONG_METADATA_VERSION; this.songName = songName; @@ -64,7 +64,7 @@ class SongMetadata this.playData.noteSkin = 'funkin'; this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; // Variation ID. - this.variation = variation; + this.variation = (variation == null) ? Constants.DEFAULT_VARIATION : variation; } /** @@ -90,12 +90,13 @@ class SongMetadata * Serialize this SongMetadata into a JSON string. * @return The JSON string. */ - public function serialize(?pretty:Bool = true):String + public function serialize(pretty:Bool = true):String { var writer = new json2object.JsonWriter<SongMetadata>(); - var output = this.clone(); - output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer. - return writer.write(output, pretty ? ' ' : null); + // I believe @:jignored should be iggnored by the writer? + // var output = this.clone(); + // output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer. + return writer.write(this, pretty ? ' ' : null); } /** @@ -230,7 +231,7 @@ class SongMusicData * Defaults to `default` or `''`. Populated later. */ @:jignored - public var variation:String = 'default'; + public var variation:String = Constants.DEFAULT_VARIATION; public function new(songName:String, artist:String, variation:String = 'default') { @@ -243,7 +244,7 @@ class SongMusicData this.looped = false; this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY; // Variation ID. - this.variation = variation; + this.variation = variation == null ? Constants.DEFAULT_VARIATION : variation; } public function clone(?newVariation:String = null):SongMusicData @@ -374,6 +375,9 @@ class SongChartData @:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY) public var generatedBy:String; + @:jignored + public var variation:String; + public function new(scrollSpeed:Map<String, Float>, events:Array<SongEventData>, notes:Map<String, Array<SongNoteData>>) { this.version = SongRegistry.SONG_CHART_DATA_VERSION; @@ -418,7 +422,7 @@ class SongChartData /** * Convert this SongChartData into a JSON string. */ - public function serialize(?pretty:Bool = true):String + public function serialize(pretty:Bool = true):String { var writer = new json2object.JsonWriter<SongChartData>(); return writer.write(this, pretty ? ' ' : null); diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index 1a7c41d33..cf2da14f7 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -120,10 +120,9 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parseEntryMetadataRaw(contents); } - public function parseEntryMetadata(id:String, variation:String = ""):Null<SongMetadata> + public function parseEntryMetadata(id:String, ?variation:String):Null<SongMetadata> { - // JsonParser does not take type parameters, - // otherwise this function would be in BaseRegistry. + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser<SongMetadata>(); switch (loadEntryMetadataFile(id, variation)) @@ -139,11 +138,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> printErrors(parser.errors, id); return null; } - return parser.value; + return cleanMetadata(parser.value, variation); } - public function parseEntryMetadataRaw(contents:String, ?fileName:String = 'raw'):Null<SongMetadata> + public function parseEntryMetadataRaw(contents:String, ?fileName:String = 'raw', ?variation:String):Null<SongMetadata> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var parser = new json2object.JsonParser<SongMetadata>(); parser.fromJson(contents, fileName); @@ -152,23 +153,25 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> printErrors(parser.errors, fileName); return null; } - return parser.value; + return cleanMetadata(parser.value, variation); } - public function parseEntryMetadataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongMetadata> + public function parseEntryMetadataWithMigration(id:String, ?variation:String, version:thx.semver.Version):Null<SongMetadata> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + // If a version rule is not specified, do not check against it. if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE)) { - return parseEntryMetadata(id); + return parseEntryMetadata(id, variation); } else if (VersionUtil.validateVersion(version, "2.0.x")) { - return parseEntryMetadata_v2_0_0(id); + return parseEntryMetadata_v2_0_0(id, variation); } else { - throw '[${registryId}] Metadata entry ${id}:${variation == '' ? 'default' : variation} does not support migration to version ${SONG_METADATA_VERSION_RULE}.'; + throw '[${registryId}] Metadata entry ${id}:${variation} does not support migration to version ${SONG_METADATA_VERSION_RULE}.'; } } @@ -191,8 +194,8 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> function parseEntryMetadata_v2_0_0(id:String, variation:String = ""):Null<SongMetadata> { - // JsonParser does not take type parameters, - // otherwise this function would be in BaseRegistry. + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var parser = new json2object.JsonParser<SongMetadata_v2_0_0>(); switch (loadEntryMetadataFile(id)) { @@ -222,10 +225,9 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parser.value.migrate(); } - public function parseMusicData(id:String, variation:String = ""):Null<SongMusicData> + public function parseMusicData(id:String, ?variation:String):Null<SongMusicData> { - // JsonParser does not take type parameters, - // otherwise this function would be in BaseRegistry. + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var parser = new json2object.JsonParser<SongMusicData>(); switch (loadMusicDataFile(id, variation)) @@ -257,8 +259,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return parser.value; } - public function parseMusicDataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongMusicData> + public function parseMusicDataWithMigration(id:String, ?variation:String, version:thx.semver.Version):Null<SongMusicData> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + // If a version rule is not specified, do not check against it. if (SONG_MUSIC_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_MUSIC_DATA_VERSION_RULE)) { @@ -266,7 +270,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> } else { - throw '[${registryId}] Chart entry ${id}:${variation == '' ? 'default' : variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; + throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; } } @@ -283,10 +287,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> } } - public function parseEntryChartData(id:String, variation:String = ''):Null<SongChartData> + public function parseEntryChartData(id:String, ?variation:String):Null<SongChartData> { - // JsonParser does not take type parameters, - // otherwise this function would be in BaseRegistry. + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var parser = new json2object.JsonParser<SongChartData>(); switch (loadEntryChartFile(id, variation)) @@ -302,11 +306,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> printErrors(parser.errors, id); return null; } - return parser.value; + return cleanChartData(parser.value, variation); } - public function parseEntryChartDataRaw(contents:String, ?fileName:String = 'raw'):Null<SongChartData> + public function parseEntryChartDataRaw(contents:String, ?fileName:String = 'raw', ?variation:String):Null<SongChartData> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var parser = new json2object.JsonParser<SongChartData>(); parser.fromJson(contents, fileName); @@ -315,11 +321,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> printErrors(parser.errors, fileName); return null; } - return parser.value; + return cleanChartData(parser.value, variation); } - public function parseEntryChartDataWithMigration(id:String, variation:String = '', version:thx.semver.Version):Null<SongChartData> + public function parseEntryChartDataWithMigration(id:String, ?variation:String, version:thx.semver.Version):Null<SongChartData> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + // If a version rule is not specified, do not check against it. if (SONG_CHART_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_CHART_DATA_VERSION_RULE)) { @@ -327,7 +335,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> } else { - throw '[${registryId}] Chart entry ${id}:${variation == '' ? 'default' : variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; + throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.'; } } @@ -354,9 +362,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return ScriptedSong.listScriptClasses(); } - function loadEntryMetadataFile(id:String, variation:String = ''):Null<BaseRegistry.JsonFile> + function loadEntryMetadataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile> { - var entryFilePath:String = Paths.json('$dataFilePath/$id/$id${variation == '' ? '' : '-$variation'}-metadata'); + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}'); if (!openfl.Assets.exists(entryFilePath)) return null; var rawJson:Null<String> = openfl.Assets.getText(entryFilePath); if (rawJson == null) return null; @@ -364,9 +373,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return {fileName: entryFilePath, contents: rawJson}; } - function loadMusicDataFile(id:String, variation:String = ''):Null<BaseRegistry.JsonFile> + function loadMusicDataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile> { - var entryFilePath:String = Paths.file('music/$id/$id${variation == '' ? '' : '-$variation'}-metadata.json'); + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json'); if (!openfl.Assets.exists(entryFilePath)) return null; var rawJson:String = openfl.Assets.getText(entryFilePath); if (rawJson == null) return null; @@ -374,9 +384,10 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return {fileName: entryFilePath, contents: rawJson}; } - function loadEntryChartFile(id:String, variation:String = ''):Null<BaseRegistry.JsonFile> + function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile> { - var entryFilePath:String = Paths.json('$dataFilePath/$id/$id${variation == '' ? '' : '-$variation'}-chart'); + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}'); if (!openfl.Assets.exists(entryFilePath)) return null; var rawJson:String = openfl.Assets.getText(entryFilePath); if (rawJson == null) return null; @@ -384,20 +395,36 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> return {fileName: entryFilePath, contents: rawJson}; } - public function fetchEntryMetadataVersion(id:String, variation:String = ''):Null<thx.semver.Version> + public function fetchEntryMetadataVersion(id:String, ?variation:String):Null<thx.semver.Version> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var entryStr:Null<String> = loadEntryMetadataFile(id, variation)?.contents; var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr); return entryVersion; } - public function fetchEntryChartVersion(id:String, variation:String = ''):Null<thx.semver.Version> + public function fetchEntryChartVersion(id:String, ?variation:String):Null<thx.semver.Version> { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; var entryStr:Null<String> = loadEntryChartFile(id, variation)?.contents; var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr); return entryVersion; } + function cleanMetadata(metadata:SongMetadata, variation:String):SongMetadata + { + metadata.variation = variation; + + return metadata; + } + + function cleanChartData(chartData:SongChartData, variation:String):SongChartData + { + chartData.variation = variation; + + return chartData; + } + /** * A list of all the story weeks from the base game, in order. * TODO: Should this be hardcoded? diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 6d645961d..d11c7744b 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -92,6 +92,15 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta _metadata = _data == null ? [] : [_data]; + variations.clear(); + variations.push(Constants.DEFAULT_VARIATION); + + if (_data != null && _data.playData != null) + { + for (vari in _data.playData.songVariations) + variations.push(vari); + } + for (meta in fetchVariationMetadata(id)) _metadata.push(meta); @@ -101,15 +110,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta return; } - variations.clear(); - variations.push('default'); - if (_data != null && _data.playData != null) - { - for (vari in _data.playData.songVariations) - variations.push(vari); - - populateFromMetadata(); - } + populateDifficulties(); } @:allow(funkin.play.song.Song) @@ -128,7 +129,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta result.difficultyIds.clear(); - result.populateFromMetadata(); + result.populateDifficulties(); for (variation => chartData in charts) result.applyChartData(chartData, variation); @@ -144,10 +145,10 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta } /** - * Populate the song data from the provided metadata, - * including data from individual difficulties. Does not load chart data. + * Populate the difficulty data from the provided metadata. + * Does not load chart data (that is triggered later when we want to play the song). */ - function populateFromMetadata():Void + function populateDifficulties():Void { if (_metadata == null || _metadata.length == 0) return; @@ -314,7 +315,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta trace('Fetching song metadata for $id'); var version:Null<thx.semver.Version> = SongRegistry.instance.fetchEntryMetadataVersion(id); if (version == null) return null; - return SongRegistry.instance.parseEntryMetadataWithMigration(id, '', version); + return SongRegistry.instance.parseEntryMetadataWithMigration(id, Constants.DEFAULT_VARIATION, version); } function fetchVariationMetadata(id:String):Array<SongMetadata> From 92162bfdadf23bb05023ff4f724fb1dd601ca7e6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Thu, 28 Sep 2023 16:49:01 -0400 Subject: [PATCH 04/74] Fix crash handler without breaking HaxeUI --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 47460facf..fdaf753f7 100644 --- a/hmm.json +++ b/hmm.json @@ -139,7 +139,7 @@ "name": "openfl", "type": "git", "dir": null, - "ref": "d33d489a137ff8fdece4994cf1302f0b6334ed08", + "ref": "de9395d2f367a80f93f082e1b639b9cde2258bf1", "url": "https://github.com/EliteMasterEric/openfl" }, { @@ -160,4 +160,4 @@ "version": "0.11.0" } ] -} \ No newline at end of file +} From b6752531bf9300397a2ed2c4e56be54e062b42b4 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 29 Sep 2023 22:29:32 -0400 Subject: [PATCH 05/74] Add chart editor theme --- assets | 2 +- .../charting/ChartEditorDialogHandler.hx | 5 ++++ .../ui/debug/charting/ChartEditorState.hx | 28 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/assets b/assets index a62e7e50d..e0e56cb3c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a62e7e50d59c14d256c75da651b79dea77e1620e +Subproject commit e0e56cb3cdb1b4394c744d5c1502cdb2629fa9b6 diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 6f44f89a2..3bb945fc8 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -83,6 +83,7 @@ class ChartEditorDialogHandler linkCreateBasic.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); + state.stopWelcomeMusic(); // // Create Song Wizard @@ -95,6 +96,7 @@ class ChartEditorDialogHandler linkImportChartLegacy.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); + state.stopWelcomeMusic(); // Open the "Import Chart" dialog openImportChartWizard(state, 'legacy', false); @@ -105,6 +107,7 @@ class ChartEditorDialogHandler buttonBrowse.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); + state.stopWelcomeMusic(); // Open the "Open Chart" dialog openBrowseWizard(state, false); @@ -133,6 +136,7 @@ class ChartEditorDialogHandler linkTemplateSong.text = songName; linkTemplateSong.onClick = function(_event) { dialog.hideDialog(DialogButton.CANCEL); + state.stopWelcomeMusic(); // Load song from template state.loadSongAsTemplate(targetSongId); @@ -141,6 +145,7 @@ class ChartEditorDialogHandler splashTemplateContainer.addComponent(linkTemplateSong); } + state.fadeInWelcomeMusic(); return dialog; } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index add65c5bf..e8887496e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -751,6 +751,11 @@ class ChartEditorState extends HaxeUIState */ // ============================== + /** + * The chill audio track that plays when you open the Chart Editor. + */ + public var welcomeMusic:FlxSound = new FlxSound(); + /** * The audio track for the instrumental. * `null` until an instrumental track is loaded. @@ -1249,6 +1254,9 @@ class ChartEditorState extends HaxeUIState // Get rid of any music from the previous state. FlxG.sound.music.stop(); + // Play the welcome music. + setupWelcomeMusic(); + buildDefaultSongData(); buildBackground(); @@ -1273,6 +1281,26 @@ class ChartEditorState extends HaxeUIState ChartEditorDialogHandler.openWelcomeDialog(this, false); } + function setupWelcomeMusic() + { + this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop')); + this.welcomeMusic.looped = true; + // this.welcomeMusic.play(); + // fadeInWelcomeMusic(); + } + + public function fadeInWelcomeMusic():Void + { + this.welcomeMusic.play(); + this.welcomeMusic.fadeIn(4, 0, 1.0); + } + + public function stopWelcomeMusic():Void + { + // this.welcomeMusic.fadeOut(4, 0); + this.welcomeMusic.pause(); + } + function buildDefaultSongData():Void { selectedVariation = Constants.DEFAULT_VARIATION; From 50b579ed4fcc356079e7b2431d67817ccf0a6df6 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 14:48:54 +0100 Subject: [PATCH 06/74] attempt using PAT for submodule checkout --- .github/workflows/build-shit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index ed509b44d..0d9f1f2a4 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -26,6 +26,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: 'recursive' + token: ${{ secrets.GH_RO_PAT }} - uses: ./.github/actions/setup-haxeshit - name: Build game run: | @@ -48,6 +49,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: 'recursive' + token: ${{ secrets.GH_RO_PAT }} - uses: ./.github/actions/setup-haxeshit - name: Build game run: | From 572dfb26564918e05430a815a0dfd0b98e79d835 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 14:57:02 +0100 Subject: [PATCH 07/74] apt update --- .github/workflows/build-shit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 0d9f1f2a4..e3dd51661 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -30,6 +30,7 @@ jobs: - uses: ./.github/actions/setup-haxeshit - name: Build game run: | + sudo apt-get update sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev haxelib run lime build html5 -release --times ls From 6d7df09437c02e28c84870be64190538abe91609 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 15:34:01 +0100 Subject: [PATCH 08/74] missed a checkout for unit tests --- .github/workflows/build-shit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index e3dd51661..0e365b281 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -71,6 +71,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: 'recursive' + token: ${{ secrets.GH_RO_PAT }} - uses: ./.github/actions/setup-haxeshit - name: Run unit tests run: | From 66e9bf8716e2741296fedada929ec86ff0571428 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 15:41:37 +0100 Subject: [PATCH 09/74] 64fast --- .github/workflows/build-shit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 0e365b281..ab96ec9a3 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -42,7 +42,7 @@ jobs: create-nightly-win: needs: check_date if: ${{ needs.check_date.outputs.should_run != 'false'}} - runs-on: windows-latest + runs-on: 64fast permissions: contents: write actions: write @@ -63,7 +63,7 @@ jobs: target: win test-unit-win: needs: create-nightly-win - runs-on: windows-latest + runs-on: 64fast permissions: contents: write actions: write From 1e72c3d60065583421cd0a3779096185a9387c95 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 15:54:13 +0100 Subject: [PATCH 10/74] 64fast is not worth it --- .github/actions/upload-itch/action.yml | 4 ++-- .github/workflows/build-shit.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/upload-itch/action.yml b/.github/actions/upload-itch/action.yml index 5abc31b16..af9a87b39 100644 --- a/.github/actions/upload-itch/action.yml +++ b/.github/actions/upload-itch/action.yml @@ -36,9 +36,9 @@ runs: ./butler -V shell: bash - name: Upload game to itch.io - env: + env: BUTLER_API_KEY: ${{inputs.butler-key}} run: | ./butler login ./butler push ${{inputs.build-dir}} ninja-muffin24/funkin-secret:${{inputs.target}}-${GITHUB_REF##*/} - shell: bash \ No newline at end of file + shell: bash diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index ab96ec9a3..0e365b281 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -42,7 +42,7 @@ jobs: create-nightly-win: needs: check_date if: ${{ needs.check_date.outputs.should_run != 'false'}} - runs-on: 64fast + runs-on: windows-latest permissions: contents: write actions: write @@ -63,7 +63,7 @@ jobs: target: win test-unit-win: needs: create-nightly-win - runs-on: 64fast + runs-on: windows-latest permissions: contents: write actions: write From af65b83ddd090c49b5884fa2e740c64511d2f30f Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Thu, 5 Oct 2023 15:55:23 +0100 Subject: [PATCH 11/74] remove --quiet so i can see errors (woah) --- .github/actions/setup-haxeshit/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index 38a504442..e96181ce4 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -16,5 +16,5 @@ runs: haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development haxelib version haxelib --global install hmm - haxelib --global run hmm install --quiet + haxelib --global run hmm install shell: bash From 2e97bb2d7b94c50e9dc15a75b500ffbad7e05527 Mon Sep 17 00:00:00 2001 From: Hazel <hazel@farfrom.earth> Date: Fri, 6 Oct 2023 15:52:28 +0100 Subject: [PATCH 12/74] attempt i. at caching haxe installs --- .github/actions/setup-haxeshit/action.yml | 43 +++++++++++++++-------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index e96181ce4..756530178 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -3,18 +3,31 @@ description: "sets up haxe shit, using HMM!" runs: using: "composite" steps: - - uses: krdlab/setup-haxe@v1.5.1 - with: - haxe-version: 4.3.1 - - name: Config haxelib - run: | - haxelib config - shell: bash - - name: Installing Haxe lol - run: | - haxe -version - haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development - haxelib version - haxelib --global install hmm - haxelib --global run hmm install - shell: bash + - uses: krdlab/setup-haxe@v1.5.1 + with: + haxe-version: 4.3.1 + - name: Config haxelib + run: | + haxelib config + shell: bash + - name: Installing Haxe lol + run: | + haxe -version + haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development + haxelib version + haxelib --global install hmm + shell: bash + - name: dependency install cache + id: cache-hmm + uses: actions/cache@v3 + with: + path: .haxelib + key: ${{ runner.os }}-hmm-${{ hashFiles('**/hmm.json') }} + restore-keys: | + ${{ runner.os }}-hmm- + ${{ runner.os }}- + - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }} + name: hmm install + run: | + haxelib --global run hmm install + shell: bash From 4a4708fe231199f85f16813f4dd009a5fa8d89b6 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 6 Oct 2023 11:42:39 -0400 Subject: [PATCH 13/74] That is the fastest I have ever fixed a bug holy shit. --- source/funkin/play/PlayState.hx | 111 ++------------------------------ source/funkin/util/Constants.hx | 17 ++++- 2 files changed, 20 insertions(+), 108 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 46938215b..8ba5f7a35 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1893,6 +1893,7 @@ class PlayState extends MusicBeatSubState { // Grant the player health. health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; + songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed); } // TODO: Potential penalty for dropping a hold note? @@ -2013,103 +2014,6 @@ class PlayState extends MusicBeatSubState } } - /** - * Handle player inputs. - */ - function keyShit(test:Bool):Void - { - // control arrays, order L D R U - var holdArray:Array<Bool> = [controls.NOTE_LEFT, controls.NOTE_DOWN, controls.NOTE_UP, controls.NOTE_RIGHT]; - var pressArray:Array<Bool> = [ - controls.NOTE_LEFT_P, - controls.NOTE_DOWN_P, - controls.NOTE_UP_P, - controls.NOTE_RIGHT_P - ]; - var releaseArray:Array<Bool> = [ - controls.NOTE_LEFT_R, - controls.NOTE_DOWN_R, - controls.NOTE_UP_R, - controls.NOTE_RIGHT_R - ]; - - // if (pressArray.contains(true)) - // { - // var lol:Array<Int> = cast pressArray; - // inputSpitter.push(Std.int(Conductor.songPosition) + ' ' + lol.join(' ')); - // } - - // HOLDS, check for sustain notes - if (holdArray.contains(true) && generatedMusic) - { - /* - activeNotes.forEachAlive(function(daNote:Note) { - if (daNote.isSustainNote && daNote.canBeHit && daNote.mustPress && holdArray[daNote.data.noteData]) goodNoteHit(daNote); - }); - */ - } - - // PRESSES, check for note hits - if (pressArray.contains(true) && generatedMusic) - { - Haptic.vibrate(100, 100); - - if (currentStage != null && currentStage.getBoyfriend() != null) - { - currentStage.getBoyfriend().holdTimer = 0; - } - - var possibleNotes:Array<NoteSprite> = []; // notes that can be hit - var directionList:Array<Int> = []; // directions that can be hit - var dumbNotes:Array<NoteSprite> = []; // notes to kill later - - for (note in dumbNotes) - { - FlxG.log.add('killing dumb ass note at ' + note.noteData.time); - note.kill(); - // activeNotes.remove(note, true); - note.destroy(); - } - - possibleNotes.sort((a, b) -> Std.int(a.noteData.time - b.noteData.time)); - - if (perfectMode) - { - goodNoteHit(possibleNotes[0], null); - } - else if (possibleNotes.length > 0) - { - for (shit in 0...pressArray.length) - { // if a direction is hit that shouldn't be - if (pressArray[shit] && !directionList.contains(shit)) ghostNoteMiss(shit); - } - for (coolNote in possibleNotes) - { - if (pressArray[coolNote.noteData.getDirection()]) goodNoteHit(coolNote, null); - } - } - else - { - // HNGGG I really want to add an option for ghost tapping - // L + ratio - for (shit in 0...pressArray.length) - if (pressArray[shit]) ghostNoteMiss(shit, false); - } - } - - if (currentStage == null) return; - - for (keyId => isPressed in pressArray) - { - if (playerStrumline == null) continue; - - var dir:NoteDirection = Strumline.DIRECTIONS[keyId]; - - if (isPressed && !playerStrumline.isConfirm(dir)) playerStrumline.playPress(dir); - if (!holdArray[keyId]) playerStrumline.playStatic(dir); - } - } - function goodNoteHit(note:NoteSprite, input:PreciseInputEvent):Void { var event:NoteScriptEvent = new NoteScriptEvent(ScriptEvent.NOTE_HIT, note, Highscore.tallies.combo + 1, true); @@ -2118,19 +2022,16 @@ class PlayState extends MusicBeatSubState // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - if (!note.isHoldNote) - { - Highscore.tallies.combo++; - Highscore.tallies.totalNotesHit++; + Highscore.tallies.combo++; + Highscore.tallies.totalNotesHit++; - if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; + if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; - popUpScore(note, input); - } + popUpScore(note, input); playerStrumline.hitNote(note); - if (note.holdNoteSprite != null) + if (note.isHoldNote && note.holdNoteSprite != null) { playerStrumline.playNoteHoldCover(note.holdNoteSprite); } diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index b454ca429..ff6f1dab6 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -288,16 +288,27 @@ class Constants public static final HEALTH_MINE_PENALTY:Float = 15.0 / 100.0 * HEALTH_MAX; // 15.0% /** - * If true, the player will not receive the ghost miss penalty if there are no notes within the hit window. - * This is the thing people have been begging for forever lolol. + * SCORE VALUES */ - public static final GHOST_TAPPING:Bool = false; + // ============================== + + /** + * The amount of score the player gains for every send they hold a hold note. + * A fraction of this value is granted every frame. + */ + public static final SCORE_HOLD_BONUS_PER_SECOND:Float = 250.0; /** * OTHER */ // ============================== + /** + * If true, the player will not receive the ghost miss penalty if there are no notes within the hit window. + * This is the thing people have been begging for forever lolol. + */ + public static final GHOST_TAPPING:Bool = false; + /** * The separator between an asset library and the asset path. */ From 7a1a2e1ab4413ad48007b64caffe3483262ec7c8 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Sun, 8 Oct 2023 22:09:09 -0400 Subject: [PATCH 14/74] Correct FunkinDropDown capitalization --- .../ui/haxeui/components/{FunkinDropdown.hx => FunkinDropDown.hx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename source/funkin/ui/haxeui/components/{FunkinDropdown.hx => FunkinDropDown.hx} (100%) diff --git a/source/funkin/ui/haxeui/components/FunkinDropdown.hx b/source/funkin/ui/haxeui/components/FunkinDropDown.hx similarity index 100% rename from source/funkin/ui/haxeui/components/FunkinDropdown.hx rename to source/funkin/ui/haxeui/components/FunkinDropDown.hx From 2a3f5bc5dbf86872737ce2ce965261991b3b79f0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 12:02:19 -0400 Subject: [PATCH 15/74] Added Git Hooks --- .github/hooks/README.md | 5 +++++ .github/hooks/post-checkout | 2 ++ .github/hooks/post-merge | 2 ++ .github/hooks/pre-push | 5 +++++ 4 files changed, 14 insertions(+) create mode 100644 .github/hooks/README.md create mode 100644 .github/hooks/post-checkout create mode 100644 .github/hooks/post-merge create mode 100644 .github/hooks/pre-push diff --git a/.github/hooks/README.md b/.github/hooks/README.md new file mode 100644 index 000000000..544fbf365 --- /dev/null +++ b/.github/hooks/README.md @@ -0,0 +1,5 @@ +# Git Hooks +These work even on Windows because of Git Bash. + +## Setup +`git config core.hooksPath .github/hooks` diff --git a/.github/hooks/post-checkout b/.github/hooks/post-checkout new file mode 100644 index 000000000..12358c998 --- /dev/null +++ b/.github/hooks/post-checkout @@ -0,0 +1,2 @@ +#!/bin/sh +git submodule update --init --recursive diff --git a/.github/hooks/post-merge b/.github/hooks/post-merge new file mode 100644 index 000000000..12358c998 --- /dev/null +++ b/.github/hooks/post-merge @@ -0,0 +1,2 @@ +#!/bin/sh +git submodule update --init --recursive diff --git a/.github/hooks/pre-push b/.github/hooks/pre-push new file mode 100644 index 000000000..ec4c820ac --- /dev/null +++ b/.github/hooks/pre-push @@ -0,0 +1,5 @@ +#!/bin/sh +if git diff --cached --submodule | grep -q "^+"; then + echo "WARNING: You have unpushed changes in submodules." + exit 1 +fi From 1462d90a3538eb49792faacc3477176be9cd3a47 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 12:12:31 -0400 Subject: [PATCH 16/74] Disable unit test suite on Actions until we can figure out what's up. --- .github/workflows/build-shit.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 0e365b281..3ce0d538b 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -61,19 +61,19 @@ jobs: butler-key: ${{ secrets.BUTLER_API_KEY}} build-dir: export/release/windows/bin target: win - test-unit-win: - needs: create-nightly-win - runs-on: windows-latest - permissions: - contents: write - actions: write - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - token: ${{ secrets.GH_RO_PAT }} - - uses: ./.github/actions/setup-haxeshit - - name: Run unit tests - run: | - cd ./tests/unit/ - ./start-win-native.bat +# test-unit-win: +# needs: create-nightly-win +# runs-on: windows-latest +# permissions: +# contents: write +# actions: write +# steps: +# - uses: actions/checkout@v3 +# with: +# submodules: 'recursive' +# token: ${{ secrets.GH_RO_PAT }} +# - uses: ./.github/actions/setup-haxeshit +# - name: Run unit tests +# run: | +# cd ./tests/unit/ +# ./start-win-native.bat From 9a762e1129ebc059d9b6a8ced4250cce4f483ccd Mon Sep 17 00:00:00 2001 From: Hazel <git@ravy.org> Date: Mon, 9 Oct 2023 18:38:31 +0100 Subject: [PATCH 17/74] switch from GITHUB_REF to GITHUB_REF_NAME --- .github/actions/upload-itch/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/upload-itch/action.yml b/.github/actions/upload-itch/action.yml index 5abc31b16..7a4b45427 100644 --- a/.github/actions/upload-itch/action.yml +++ b/.github/actions/upload-itch/action.yml @@ -36,9 +36,9 @@ runs: ./butler -V shell: bash - name: Upload game to itch.io - env: + env: BUTLER_API_KEY: ${{inputs.butler-key}} run: | ./butler login - ./butler push ${{inputs.build-dir}} ninja-muffin24/funkin-secret:${{inputs.target}}-${GITHUB_REF##*/} - shell: bash \ No newline at end of file + ./butler push ${{inputs.build-dir}} ninja-muffin24/funkin-secret:${{inputs.target}}-${GITHUB_REF_NAME} + shell: bash From 74b925d2c6cafe29c216e9b8b092c1c038f52cfa Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 14:13:14 -0400 Subject: [PATCH 18/74] Assert the difficulty exists (and throw an error if it doesn't) before performing reset. --- source/funkin/play/PlayState.hx | 79 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ce72fa56c..3cfe9e8f2 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -511,41 +511,7 @@ class PlayState extends MusicBeatSubState NoteSplash.buildSplashFrames(); - // Returns null if the song failed to load or doesn't have the selected difficulty. - if (currentSong == null || currentChart == null) - { - // We have encountered a critical error. Prevent Flixel from trying to run any gameplay logic. - criticalFailure = true; - - // Choose an error message. - var message:String = 'There was a critical error. Click OK to return to the main menu.'; - if (currentSong == null) - { - message = 'The was a critical error loading this song\'s chart. Click OK to return to the main menu.'; - } - else if (currentDifficulty == null) - { - message = 'The was a critical error selecting a difficulty for this song. Click OK to return to the main menu.'; - } - else if (currentSong.getDifficulty(currentDifficulty) == null) - { - message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty. Click OK to return to the main menu.'; - } - - // Display a popup. This blocks the application until the user clicks OK. - lime.app.Application.current.window.alert(message, 'Error loading PlayState'); - - // Force the user back to the main menu. - if (isSubState) - { - this.close(); - } - else - { - FlxG.switchState(new MainMenuState()); - } - return; - } + if (!assertChartExists()) return; if (false) { @@ -660,6 +626,47 @@ class PlayState extends MusicBeatSubState initialized = true; } + function assertChartExists():Bool + { + // Returns null if the song failed to load or doesn't have the selected difficulty. + if (currentSong == null || currentChart == null) + { + // We have encountered a critical error. Prevent Flixel from trying to run any gameplay logic. + criticalFailure = true; + + // Choose an error message. + var message:String = 'There was a critical error. Click OK to return to the main menu.'; + if (currentSong == null) + { + message = 'The was a critical error loading this song\'s chart. Click OK to return to the main menu.'; + } + else if (currentDifficulty == null) + { + message = 'The was a critical error selecting a difficulty for this song. Click OK to return to the main menu.'; + } + else if (currentSong.getDifficulty(currentDifficulty) == null) + { + message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty. Click OK to return to the main menu.'; + } + + // Display a popup. This blocks the application until the user clicks OK. + lime.app.Application.current.window.alert(message, 'Error loading PlayState'); + + // Force the user back to the main menu. + if (isSubState) + { + this.close(); + } + else + { + FlxG.switchState(new MainMenuState()); + } + return false; + } + + return true; + } + public override function update(elapsed:Float):Void { if (criticalFailure) return; @@ -672,6 +679,8 @@ class PlayState extends MusicBeatSubState // Handle restarting the song when needed (player death or pressing Retry) if (needsReset) { + if (!assertChartExists()) return; + dispatchEvent(new ScriptEvent(ScriptEvent.SONG_RETRY)); resetCamera(); From e967b1e7f33e4f1fd807373b3fea79bb4c941a8d Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 14:19:52 -0400 Subject: [PATCH 19/74] Fix vocals not changing when switching difficulties. --- source/funkin/play/PlayState.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3cfe9e8f2..d7c2a2a4c 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -695,8 +695,10 @@ class PlayState extends MusicBeatSubState // Reset music properly. FlxG.sound.music.pause(); - vocals.pause(); FlxG.sound.music.time = (startTimestamp); + + vocals = currentChart.buildVocals(); + vocals.pause(); vocals.time = 0; FlxG.sound.music.volume = 1; From 3e1e5d330cadefb125d0ea53eeafe7f147b662de Mon Sep 17 00:00:00 2001 From: Hazel <git@ravy.org> Date: Mon, 9 Oct 2023 19:39:11 +0100 Subject: [PATCH 20/74] ci: hxcpp + export caching --- .github/actions/setup-haxeshit/action.yml | 1 - .github/workflows/build-shit.yml | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index 756530178..0cc544cf7 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -25,7 +25,6 @@ runs: key: ${{ runner.os }}-hmm-${{ hashFiles('**/hmm.json') }} restore-keys: | ${{ runner.os }}-hmm- - ${{ runner.os }}- - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }} name: hmm install run: | diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 3ce0d538b..92d915a8e 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -52,13 +52,30 @@ jobs: submodules: 'recursive' token: ${{ secrets.GH_RO_PAT }} - uses: ./.github/actions/setup-haxeshit + - name: Make HXCPP cache dir + shell: bash + run: | + mkdir -p ${{ runner.temp }}/hxcpp_cache + - name: Restore build cache + id: cache-build-win + uses: actions/cache@v3 + with: + path: | + .haxelib + export + ${{ runner.temp }}/hxcpp_cache + key: ${{ runner.os }}-build-win-${{ github.ref_name }} + restore-keys: | + ${{ runner.os }}-build-win- - name: Build game run: | haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER dir + env: + HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache" - uses: ./.github/actions/upload-itch with: - butler-key: ${{ secrets.BUTLER_API_KEY}} + butler-key: ${{ secrets.BUTLER_API_KEY }} build-dir: export/release/windows/bin target: win # test-unit-win: From 5ae2bc814be0cc0956061c9e62576d6fd86032a0 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 15:35:50 -0400 Subject: [PATCH 21/74] Fix issue causing crash on F5 --- source/funkin/play/stage/Stage.hx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 1ac9b0b67..d9875e456 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -649,16 +649,20 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass } boppers = []; - for (sprite in this.group) + if (group != null) { - if (sprite != null) + for (sprite in this.group) { - sprite.kill(); - sprite.destroy(); - remove(sprite); + if (sprite != null) + { + sprite.kill(); + sprite.destroy(); + remove(sprite); + } } + group.clear(); } - group.clear(); + if (debugIconGroup != null && debugIconGroup.group != null) { debugIconGroup.kill(); From 1df86037636a6af7a51bc7a747b9d3fd9f93bd7d Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 15:36:00 -0400 Subject: [PATCH 22/74] Update scripts to fix alt animation issues. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 7bc9407e0..108cdc364 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7bc9407e0e8141a643605ff4514ba63169cc41e2 +Subproject commit 108cdc364a3bf062a112694d9368c3a98c84919f From db563a4967ffdf618836496966d90d7a08b0964e Mon Sep 17 00:00:00 2001 From: Hazel <git@ravy.org> Date: Mon, 9 Oct 2023 21:03:49 +0100 Subject: [PATCH 24/74] hotfix: windows style paths --- .github/workflows/build-shit.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 92d915a8e..809a8b94b 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -55,7 +55,7 @@ jobs: - name: Make HXCPP cache dir shell: bash run: | - mkdir -p ${{ runner.temp }}/hxcpp_cache + mkdir -p ${{ runner.temp }}\\hxcpp_cache - name: Restore build cache id: cache-build-win uses: actions/cache@v3 @@ -63,7 +63,7 @@ jobs: path: | .haxelib export - ${{ runner.temp }}/hxcpp_cache + ${{ runner.temp }}\\hxcpp_cache key: ${{ runner.os }}-build-win-${{ github.ref_name }} restore-keys: | ${{ runner.os }}-build-win- @@ -72,7 +72,7 @@ jobs: haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER dir env: - HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache" + HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache" - uses: ./.github/actions/upload-itch with: butler-key: ${{ secrets.BUTLER_API_KEY }} From 7ac2e91c8d765c6ae486b3fbfc963aae96c71c26 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 4 Aug 2023 15:59:17 -0400 Subject: [PATCH 25/74] selected in middle-ish --- source/funkin/FreeplayState.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 6cd353233..c7ae49893 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -970,6 +970,8 @@ class FreeplayState extends MusicBeatSubState for (index => capsule in grpCapsules.members) { + index += 1; + capsule.selected = false; capsule.targetPos.y = ((index - curSelected) * 150) + 160; From 9571eb70e0edec7700e00f32ebf4e7c9c06ffa06 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 4 Aug 2023 16:19:48 -0400 Subject: [PATCH 26/74] scaling of capsules --- source/funkin/FreeplayState.hx | 2 +- source/funkin/freeplayStuff/SongMenuItem.hx | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index c7ae49893..012b6290a 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -974,7 +974,7 @@ class FreeplayState extends MusicBeatSubState capsule.selected = false; - capsule.targetPos.y = ((index - curSelected) * 150) + 160; + capsule.targetPos.y = ((index - curSelected) * ((capsule.height * capsule.realScaled) + 10)) + 160; capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected))); // capsule.targetPos.x = 320 + (40 * (index - curSelected)); diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 37198f6d7..cccd84c98 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -38,7 +38,7 @@ class SongMenuItem extends FlxSpriteGroup // capsule.animation add(capsule); - songText = new FlxText(120, 40, 0, songTitle, 40); + songText = new FlxText(capsule.width * 0.1, 40, 0, songTitle, Std.int(40 * realScaled)); songText.font = "5by7"; songText.color = 0xFF43C1EA; add(songText); @@ -63,6 +63,8 @@ class SongMenuItem extends FlxSpriteGroup var xPosLerpLol:Array<Float> = [0.9, 0.4, 0.16, 0.16, 0.22, 0.22, 0.245]; // NUMBERS ARE JANK CUZ THE SCALING OR WHATEVER var xPosOutLerpLol:Array<Float> = [0.245, 0.75, 0.98, 0.98, 1.2]; // NUMBERS ARE JANK CUZ THE SCALING OR WHATEVER + public final realScaled:Float = 0.8; + override function update(elapsed:Float) { if (doJumpIn) @@ -77,6 +79,9 @@ class SongMenuItem extends FlxSpriteGroup scale.y = 1 / xFrames[frameInTypeBeat]; x = FlxG.width * xPosLerpLol[Std.int(Math.min(frameInTypeBeat, xPosLerpLol.length - 1))]; + scale.x *= realScaled; + scale.y *= realScaled; + frameInTypeBeat += 1; } } @@ -93,6 +98,9 @@ class SongMenuItem extends FlxSpriteGroup scale.y = 1 / xFrames[frameOutTypeBeat]; x = FlxG.width * xPosOutLerpLol[Std.int(Math.min(frameOutTypeBeat, xPosOutLerpLol.length - 1))]; + scale.x *= realScaled; + scale.y *= realScaled; + frameOutTypeBeat += 1; } } From 84f908fbde8d8f6afb3286d1abc25290edc70c99 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 4 Aug 2023 17:10:27 -0400 Subject: [PATCH 27/74] tween in fixed --- source/funkin/FreeplayState.hx | 5 +++-- source/funkin/freeplayStuff/SongMenuItem.hx | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 012b6290a..42d29bfc9 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -494,7 +494,8 @@ class FreeplayState extends MusicBeatSubState for (i in 0...tempSongs.length) { - var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, (i * 150) + 160, tempSongs[i].songName); + var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, 0, tempSongs[i].songName); + funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; funnyMenu.targetPos.x = funnyMenu.x; funnyMenu.ID = i; funnyMenu.alpha = 0.5; @@ -974,7 +975,7 @@ class FreeplayState extends MusicBeatSubState capsule.selected = false; - capsule.targetPos.y = ((index - curSelected) * ((capsule.height * capsule.realScaled) + 10)) + 160; + capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected))); // capsule.targetPos.x = 320 + (40 * (index - curSelected)); diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index cccd84c98..6476a1258 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -38,7 +38,7 @@ class SongMenuItem extends FlxSpriteGroup // capsule.animation add(capsule); - songText = new FlxText(capsule.width * 0.1, 40, 0, songTitle, Std.int(40 * realScaled)); + songText = new FlxText(capsule.width * 0.23, 40, 0, songTitle, Std.int(40 * realScaled)); songText.font = "5by7"; songText.color = 0xFF43C1EA; add(songText); @@ -114,6 +114,11 @@ class SongMenuItem extends FlxSpriteGroup super.update(elapsed); } + public function intendedY(index:Int):Float + { + return (index * ((height * realScaled) + 10)) + 160; + } + function set_selected(value:Bool):Bool { // trace(value); From 65422393cfaddf85c7fe4144dde5959205a764c7 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 4 Aug 2023 18:09:40 -0400 Subject: [PATCH 28/74] letter sort --- source/funkin/FreeplayState.hx | 2 +- source/funkin/freeplayStuff/LetterSort.hx | 18 +++++++++++++----- source/funkin/freeplayStuff/SongMenuItem.hx | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 42d29bfc9..0c51dae59 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -376,7 +376,7 @@ class FreeplayState extends MusicBeatSubState speed: 0.26 }); - var letterSort:LetterSort = new LetterSort(300, 100); + var letterSort:LetterSort = new LetterSort(400, 75); add(letterSort); exitMovers.set([letterSort], diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index c3b22f973..f0425dfc9 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -17,24 +17,32 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> { super(x, y); - var leftArrow:FreeplayLetter = new FreeplayLetter(-20, 0); + var leftArrow:FreeplayLetter = new FreeplayLetter(-20, 20); leftArrow.animation.play("arrow"); + leftArrow.flipX = true; add(leftArrow); - for (i in 0...6) + for (i in 0...5) { var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i); add(letter); letters.push(letter); - if (i == 3) letter.alpha = 0.6; + if (i == 2) letter.alpha = 0.6; - var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 50, 0); + // don't put the last seperator + if (i == 4) continue; + + var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 60, 20); sep.animation.play("seperator"); add(sep); } + var rightArrow:FreeplayLetter = new FreeplayLetter(380, 20); + rightArrow.animation.play("arrow"); + add(rightArrow); + // changeSelection(-3); } @@ -51,7 +59,7 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> for (letter in letters) letter.changeLetter(diff); - if (changeSelectionCallback != null) changeSelectionCallback(letters[3].arr[letters[3].curLetter]); // bullshit and long lol! + if (changeSelectionCallback != null) changeSelectionCallback(letters[2].arr[letters[2].curLetter]); // bullshit and long lol! } } diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 6476a1258..5649d1187 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -43,7 +43,7 @@ class SongMenuItem extends FlxSpriteGroup songText.color = 0xFF43C1EA; add(songText); - favIcon = new FlxSprite(440, 40); + favIcon = new FlxSprite(400, 40); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.animation.addByPrefix('fav', "favorite heart", 24, false); favIcon.animation.play('fav'); @@ -116,7 +116,7 @@ class SongMenuItem extends FlxSpriteGroup public function intendedY(index:Int):Float { - return (index * ((height * realScaled) + 10)) + 160; + return (index * ((height * realScaled) + 10)) + 120; } function set_selected(value:Bool):Bool From eb87d40d5871b060f77f1211ba6f74a0bda0c233 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 6 Aug 2023 16:24:34 -0400 Subject: [PATCH 29/74] random capsule in progres --- source/funkin/FreeplayState.hx | 172 ++++++++++---------- source/funkin/freeplayStuff/LetterSort.hx | 18 +- source/funkin/freeplayStuff/SongMenuItem.hx | 27 +++ 3 files changed, 128 insertions(+), 89 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 0c51dae59..5ddd47432 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -466,7 +466,7 @@ class FreeplayState extends MusicBeatSubState public function generateSongList(?filterStuff:SongFilter, force:Bool = false) { - curSelected = 0; + curSelected = 1; grpCapsules.clear(); @@ -492,9 +492,38 @@ class FreeplayState extends MusicBeatSubState } } + // if (regexp != null) + // tempSongs = songs.filter(item -> regexp.match(item.songName)); + + // tempSongs.sort(function(a, b):Int + // { + // var tempA = a.songName.toUpperCase(); + // var tempB = b.songName.toUpperCase(); + + // if (tempA < tempB) + // return -1; + // else if (tempA > tempB) + // return 1; + // else + // return 0; + // }); + + var randomCapsule:SongMenuItem = new SongMenuItem(FlxG.width, 0, "Random"); + randomCapsule.onConfirm = function() { + trace("RANDOM SELECTED"); + }; + randomCapsule.y = randomCapsule.intendedY(0) + 10; + randomCapsule.targetPos.x = randomCapsule.x; + randomCapsule.alpha = 0.5; + randomCapsule.songText.visible = false; + randomCapsule.favIcon.visible = false; + randomCapsule.initJumpIn(0, force); + grpCapsules.add(randomCapsule); + for (i in 0...tempSongs.length) { var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, 0, tempSongs[i].songName); + funnyMenu.onConfirm = capsuleOnConfirmDefault; funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; funnyMenu.targetPos.x = funnyMenu.x; funnyMenu.ID = i; @@ -503,29 +532,7 @@ class FreeplayState extends MusicBeatSubState funnyMenu.favIcon.visible = tempSongs[i].isFav; // fp.updateScore(0); - - var maxTimer:Float = Math.min(i, 4); - - new FlxTimer().start((1 / 24) * maxTimer, function(doShit) { - funnyMenu.doJumpIn = true; - }); - - new FlxTimer().start((0.09 * maxTimer) + 0.85, function(lerpTmr) { - funnyMenu.doLerp = true; - }); - - if (!force) - { - new FlxTimer().start(((0.20 * maxTimer) / (1 + maxTimer)) + 0.75, function(swagShi) { - funnyMenu.songText.visible = true; - funnyMenu.alpha = 1; - }); - } - else - { - funnyMenu.songText.visible = true; - funnyMenu.alpha = 1; - } + funnyMenu.initJumpIn(Math.min(i, 4), force); grpCapsules.add(funnyMenu); @@ -831,66 +838,7 @@ class FreeplayState extends MusicBeatSubState { // if (Assets.exists()) - var poop:String = songs[curSelected].songName.toLowerCase(); - - // does not work properly, always just accidentally sets it to normal anyways! - /* if (!Assets.exists(Paths.json(songs[curSelected].songName + '/' + poop))) - { - // defaults to normal if HARD / EASY doesn't exist - // does not account if NORMAL doesn't exist! - FlxG.log.warn("CURRENT DIFFICULTY IS NOT CHARTED, DEFAULTING TO NORMAL!"); - poop = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), 1); - curDifficulty = 1; - }*/ - - PlayStatePlaylist.isStoryMode = false; - var songId:String = songs[curSelected].songName.toLowerCase(); - var targetSong:Song = SongRegistry.instance.fetchEntry(songId); - var targetDifficulty:String = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: 'normal'; - }; - - // TODO: Implement additional difficulties into the interface properly. - if (FlxG.keys.pressed.E) - { - targetDifficulty = 'erect'; - } - - // TODO: Implement Pico into the interface properly. - var targetCharacter:String = 'bf'; - if (FlxG.keys.pressed.P) - { - targetCharacter = 'pico'; - } - - PlayStatePlaylist.campaignId = songs[curSelected].levelId; - - // Visual and audio effects. - FlxG.sound.play(Paths.sound('confirmMenu')); - dj.confirm(); - - if (targetSong != null) - { - // Load and cache the song's charts. - // TODO: Do this in the loading state. - targetSong.cacheCharts(true); - } - - new FlxTimer().start(1, function(tmr:FlxTimer) { - LoadingState.loadAndSwitchState(new PlayState( - { - targetSong: targetSong, - targetDifficulty: targetDifficulty, - targetCharacter: targetCharacter, - }), true); - }); + grpCapsules.members[curSelected].onConfirm(); } } @@ -942,6 +890,62 @@ class FreeplayState extends MusicBeatSubState } } + function capsuleOnConfirmDefault():Void + { + var poop:String = songs[curSelected].songName.toLowerCase(); + + // does not work properly, always just accidentally sets it to normal anyways! + /* if (!Assets.exists(Paths.json(songs[curSelected].songName + '/' + poop))) + { + // defaults to normal if HARD / EASY doesn't exist + // does not account if NORMAL doesn't exist! + FlxG.log.warn("CURRENT DIFFICULTY IS NOT CHARTED, DEFAULTING TO NORMAL!"); + poop = Highscore.formatSong(songs[curSelected].songName.toLowerCase(), 1); + curDifficulty = 1; + }*/ + + PlayStatePlaylist.isStoryMode = false; + var targetSong:Song = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase()); + var targetDifficulty:String = switch (curDifficulty) + { + case 0: + 'easy'; + case 1: + 'normal'; + case 2: + 'hard'; + default: 'normal'; + }; + + // TODO: Implement additional difficulties into the interface properly. + if (FlxG.keys.pressed.E) + { + targetDifficulty = 'erect'; + } + + // TODO: Implement Pico into the interface properly. + var targetCharacter:String = 'bf'; + if (FlxG.keys.pressed.P) + { + targetCharacter = 'pico'; + } + + PlayStatePlaylist.campaignId = songs[curSelected].levelId; + + // Visual and audio effects. + FlxG.sound.play(Paths.sound('confirmMenu')); + dj.confirm(); + + new FlxTimer().start(1, function(tmr:FlxTimer) { + LoadingState.loadAndSwitchState(new PlayState( + { + targetSong: targetSong, + targetDifficulty: targetDifficulty, + targetCharacter: targetCharacter, + }), true); + }); + } + function changeSelection(change:Int = 0) { // fp.updateScore(12345); diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index f0425dfc9..bb467e67b 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -4,6 +4,8 @@ import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; +import flixel.tweens.FlxTween; +import flixel.tweens.FlxEase; class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> { @@ -25,6 +27,7 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> for (i in 0...5) { var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i); + letter.ogY = y; add(letter); letters.push(letter); @@ -43,7 +46,7 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> rightArrow.animation.play("arrow"); add(rightArrow); - // changeSelection(-3); + changeSelection(0); } override function update(elapsed:Float) @@ -56,8 +59,12 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> public function changeSelection(diff:Int = 0) { + curSelection += diff; + if (curSelection < 0) curSelection = letters.length - 1; + if (curSelection >= letters.length) curSelection = 0; + for (letter in letters) - letter.changeLetter(diff); + letter.changeLetter(diff, curSelection); if (changeSelectionCallback != null) changeSelectionCallback(letters[2].arr[letters[2].curLetter]); // bullshit and long lol! } @@ -69,16 +76,17 @@ class FreeplayLetter extends FlxSprite public var curLetter:Int = 0; + public var ogY:Float = 0; + public function new(x:Float, y:Float, ?letterInd:Int) { super(x, y); - frames = Paths.getSparrowAtlas("freeplay/letterStuff"); var alphabet:String = "abcdefghijklmnopqrstuvwxyz"; arr = alphabet.split(""); - arr.insert(0, "#"); arr.insert(0, "ALL"); + arr.insert(0, "#"); arr.insert(0, "fav"); for (str in arr) @@ -96,7 +104,7 @@ class FreeplayLetter extends FlxSprite } } - public function changeLetter(diff:Int = 0) + public function changeLetter(diff:Int = 0, ?curSelection:Int) { curLetter += diff; diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 5649d1187..91a7c188c 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -7,6 +7,7 @@ import flixel.group.FlxSpriteGroup; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.text.FlxText; +import flixel.util.FlxTimer; class SongMenuItem extends FlxSpriteGroup { @@ -25,6 +26,8 @@ class SongMenuItem extends FlxSpriteGroup public var doJumpOut:Bool = false; + public var onConfirm:Void->Void; + public function new(x:Float, y:Float, song:String) { super(x, y); @@ -65,6 +68,30 @@ class SongMenuItem extends FlxSpriteGroup public final realScaled:Float = 0.8; + public function initJumpIn(maxTimer:Float, ?force:Bool):Void + { + new FlxTimer().start((1 / 24) * maxTimer, function(doShit) { + doJumpIn = true; + }); + + new FlxTimer().start((0.09 * maxTimer) + 0.85, function(lerpTmr) { + doLerp = true; + }); + + if (!force) + { + new FlxTimer().start(((0.20 * maxTimer) / (1 + maxTimer)) + 0.75, function(swagShi) { + songText.visible = true; + alpha = 1; + }); + } + else + { + songText.visible = true; + alpha = 1; + } + } + override function update(elapsed:Float) { if (doJumpIn) From dc452bbe9ae0b3f11879d10918a5fc2c73d61c63 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 6 Aug 2023 17:02:37 -0400 Subject: [PATCH 30/74] different colors / sizes --- source/funkin/freeplayStuff/LetterSort.hx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index bb467e67b..bfc8c4a70 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -6,6 +6,7 @@ import flixel.group.FlxGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; +import flixel.util.FlxColor; class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> { @@ -32,13 +33,18 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> letters.push(letter); - if (i == 2) letter.alpha = 0.6; + if (i == 2) letter.scale.x = letter.scale.y = 1.2; + + var darkness:Float = Math.abs(i - 2) / 6; + + letter.color = letter.color.getDarkened(darkness); // don't put the last seperator if (i == 4) continue; - var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 60, 20); + var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 55, 20); sep.animation.play("seperator"); + sep.color = letter.color.getDarkened(darkness); add(sep); } From f460af1ba034042df9fe81f841213ec27d766856 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 6 Aug 2023 17:06:07 -0400 Subject: [PATCH 31/74] positioning stuff --- source/funkin/freeplayStuff/LetterSort.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index bfc8c4a70..44261bee1 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -33,7 +33,9 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> letters.push(letter); - if (i == 2) letter.scale.x = letter.scale.y = 1.2; + if (i != 2) letter.scale.x = letter.scale.y = 0.8; + else + letter.scale.x = letter.scale.y = 1.1; var darkness:Float = Math.abs(i - 2) / 6; From 11ffc6223893ef676fd17f0401c6c812a83ab77e Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 6 Aug 2023 22:20:18 -0400 Subject: [PATCH 32/74] new letters --- assets | 2 +- source/funkin/FreeplayState.hx | 3 ++ source/funkin/Paths.hx | 4 +- source/funkin/freeplayStuff/LetterSort.hx | 56 ++++++++++++++++------- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/assets b/assets index 7bc9407e0..f6d6e839a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7bc9407e0e8141a643605ff4514ba63169cc41e2 +Subproject commit f6d6e839a86208279bf92e0b07b8c02b25913947 diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 5ddd47432..277db9873 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -1026,7 +1026,10 @@ class DifficultySelector extends FlxSprite whiteShader.colorSet = true; + scale.x = scale.y = 0.5; + new FlxTimer().start(2 / 24, function(tmr) { + scale.x = scale.y = 1; whiteShader.colorSet = false; updateHitbox(); }); diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index c8c9c79b7..07a15dae1 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -49,9 +49,9 @@ class Paths return getPath(file, type, library); } - public static inline function animateAtlas(path:String, library:String) + public static inline function animateAtlas(path:String, ?library:String) { - return getLibraryPathForce('images/$path', library); + return getLibraryPath('images/$path', library); } inline static public function txt(key:String, ?library:String) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index 44261bee1..b02eb0a92 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -7,6 +7,8 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; import flixel.util.FlxColor; +import flixel.util.FlxTimer; +import funkin.graphics.adobeanimate.FlxAtlasSprite; class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> { @@ -16,11 +18,14 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> public var changeSelectionCallback:String->Void; + var leftArrow:FreeplayLetter; + var rightArrow:FreeplayLetter; + public function new(x, y) { super(x, y); - var leftArrow:FreeplayLetter = new FreeplayLetter(-20, 20); + leftArrow = new FreeplayLetter(-20, 15); leftArrow.animation.play("arrow"); leftArrow.flipX = true; add(leftArrow); @@ -50,7 +55,7 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> add(sep); } - var rightArrow:FreeplayLetter = new FreeplayLetter(380, 20); + rightArrow = new FreeplayLetter(380, leftArrow.y); rightArrow.animation.play("arrow"); add(rightArrow); @@ -67,6 +72,21 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> public function changeSelection(diff:Int = 0) { + if (diff < 0) + { + leftArrow.offset.x = 3; + new FlxTimer().start(2 / 24, function(_) { + leftArrow.offset.x = 0; + }); + } + else if (diff > 0) + { + rightArrow.offset.x = -3; + new FlxTimer().start(2 / 24, function(_) { + rightArrow.offset.x = 0; + }); + } + curSelection += diff; if (curSelection < 0) curSelection = letters.length - 1; if (curSelection >= letters.length) curSelection = 0; @@ -78,7 +98,7 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> } } -class FreeplayLetter extends FlxSprite +class FreeplayLetter extends FlxAtlasSprite { public var arr:Array<String> = []; @@ -88,26 +108,30 @@ class FreeplayLetter extends FlxSprite public function new(x:Float, y:Float, ?letterInd:Int) { - super(x, y); - frames = Paths.getSparrowAtlas("freeplay/letterStuff"); + super(x, y, Paths.animateAtlas("freeplay/sortedLetters")); + // frames = Paths.getSparrowAtlas("freeplay/letterStuff"); + // this.anim.play("AB"); + // trace(this.anim.symbolDictionary); - var alphabet:String = "abcdefghijklmnopqrstuvwxyz"; - arr = alphabet.split(""); + var alphabet:String = "AB-CD-EH-I L-MN-OR-s-t-UZ"; + arr = alphabet.split("-"); arr.insert(0, "ALL"); - arr.insert(0, "#"); arr.insert(0, "fav"); + arr.insert(0, "#"); - for (str in arr) - { - animation.addByPrefix(str, str + " "); // string followed by a space! intentional! - } + // trace(arr); - animation.addByPrefix("arrow", "mini arrow"); - animation.addByPrefix("seperator", "seperator"); + // for (str in arr) + // { + // animation.addByPrefix(str, str + " "); // string followed by a space! intentional! + // } + + // animation.addByPrefix("arrow", "mini arrow"); + // animation.addByPrefix("seperator", "seperator"); if (letterInd != null) { - animation.play(arr[letterInd]); + this.anim.play(arr[letterInd]); curLetter = letterInd; } } @@ -119,6 +143,6 @@ class FreeplayLetter extends FlxSprite if (curLetter < 0) curLetter = arr.length - 1; if (curLetter >= arr.length) curLetter = 0; - animation.play(arr[curLetter]); + this.anim.play(arr[curLetter]); } } From c2f46309ab05f9fb025f688583018565730133fe Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 6 Aug 2023 22:54:55 -0400 Subject: [PATCH 33/74] arrow and seperator back in --- assets | 2 +- source/funkin/freeplayStuff/LetterSort.hx | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/assets b/assets index f6d6e839a..b3fec7399 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit f6d6e839a86208279bf92e0b07b8c02b25913947 +Subproject commit b3fec73999ce576d009f70b345f19da6b5f0f82a diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index b02eb0a92..278500da8 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -10,7 +10,7 @@ import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.graphics.adobeanimate.FlxAtlasSprite; -class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> +class LetterSort extends FlxTypedSpriteGroup<FlxSprite> { public var letters:Array<FreeplayLetter> = []; @@ -18,15 +18,15 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> public var changeSelectionCallback:String->Void; - var leftArrow:FreeplayLetter; - var rightArrow:FreeplayLetter; + var leftArrow:FlxSprite; + var rightArrow:FlxSprite; public function new(x, y) { super(x, y); - leftArrow = new FreeplayLetter(-20, 15); - leftArrow.animation.play("arrow"); + leftArrow = new FlxSprite(-20, 15).loadGraphic(Paths.image("freeplay/miniArrow")); + // leftArrow.animation.play("arrow"); leftArrow.flipX = true; add(leftArrow); @@ -49,14 +49,15 @@ class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter> // don't put the last seperator if (i == 4) continue; - var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 55, 20); - sep.animation.play("seperator"); + var sep:FlxSprite = new FlxSprite((i * 80) + 55, 20).loadGraphic(Paths.image("freeplay/seperator")); + // sep.animation.play("seperator"); sep.color = letter.color.getDarkened(darkness); add(sep); } - rightArrow = new FreeplayLetter(380, leftArrow.y); - rightArrow.animation.play("arrow"); + rightArrow = new FlxSprite(380, 15).loadGraphic(Paths.image("freeplay/miniArrow")); + + // rightArrow.animation.play("arrow"); add(rightArrow); changeSelection(0); From e618fbc64f99b9d306a9fb2a07886892180a07ab Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 12:33:12 -0400 Subject: [PATCH 34/74] bouncing thingie --- source/funkin/freeplayStuff/LetterSort.hx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index 278500da8..68eeeab33 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -33,6 +33,8 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> for (i in 0...5) { var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i); + letter.x += 50; + letter.y += 50; letter.ogY = y; add(letter); @@ -40,7 +42,10 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> if (i != 2) letter.scale.x = letter.scale.y = 0.8; else - letter.scale.x = letter.scale.y = 1.1; + { + // letter.x += 10; + // letter.scale.x = letter.scale.y = 1.1; + } var darkness:Float = Math.abs(i - 2) / 6; @@ -89,11 +94,11 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> } curSelection += diff; - if (curSelection < 0) curSelection = letters.length - 1; - if (curSelection >= letters.length) curSelection = 0; + if (curSelection < 0) curSelection = letters[0].arr.length - 1; + if (curSelection >= letters[0].arr.length) curSelection = 0; for (letter in letters) - letter.changeLetter(diff, curSelection); + letter.changeLetter(diff, curSelection + 2); if (changeSelectionCallback != null) changeSelectionCallback(letters[2].arr[letters[2].curLetter]); // bullshit and long lol! } @@ -132,7 +137,8 @@ class FreeplayLetter extends FlxAtlasSprite if (letterInd != null) { - this.anim.play(arr[letterInd]); + this.anim.play(arr[letterInd] + " move"); + this.anim.pause(); curLetter = letterInd; } } @@ -144,6 +150,8 @@ class FreeplayLetter extends FlxAtlasSprite if (curLetter < 0) curLetter = arr.length - 1; if (curLetter >= arr.length) curLetter = 0; - this.anim.play(arr[curLetter]); + this.anim.play(arr[curLetter] + " move"); + if (curSelection != curLetter) this.anim.pause(); + updateHitbox(); } } From ce2e6a399315ac21790c8acb9e5d7252fc931340 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 15:42:38 -0400 Subject: [PATCH 35/74] proper letter stuff --- source/funkin/freeplayStuff/LetterSort.hx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index 68eeeab33..ed3ca7312 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -150,7 +150,18 @@ class FreeplayLetter extends FlxAtlasSprite if (curLetter < 0) curLetter = arr.length - 1; if (curLetter >= arr.length) curLetter = 0; - this.anim.play(arr[curLetter] + " move"); + var animName:String = arr[curLetter] + " move"; + switch (arr[curLetter]) + { + case "I L": + animName = "IL move"; + case "s": + animName = "S move"; + case "t": + animName = "T move"; + } + + this.anim.play(animName); if (curSelection != curLetter) this.anim.pause(); updateHitbox(); } From c469d4b7c07e224f685202ddf3308a2aff1093d1 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 16:01:22 -0400 Subject: [PATCH 36/74] regexp filter --- source/funkin/FreeplayState.hx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 277db9873..bc3d9bbce 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -393,7 +393,7 @@ class FreeplayState extends MusicBeatSubState case "ALL": generateSongList(null, true); default: - generateSongList({filterType: STARTSWITH, filterData: str}, true); + generateSongList({filterType: REGEXP, filterData: str}, true); } }; @@ -477,6 +477,13 @@ class FreeplayState extends MusicBeatSubState { switch (filterStuff.filterType) { + case REGEXP: + // filterStuff.filterData has a string with the first letter of the sorting range, and the second one + // this creates a filter to return all the songs that start with a letter between those two + var filterRegexp = new EReg("^[" + filterStuff.filterData + "].*", "i"); + tempSongs = tempSongs.filter(str -> { + return filterRegexp.match(str.songName); + }); case STARTSWITH: tempSongs = tempSongs.filter(str -> { return str.songName.toLowerCase().startsWith(filterStuff.filterData); @@ -1045,6 +1052,7 @@ typedef SongFilter = enum abstract FilterType(String) { var STARTSWITH; + var REGEXP; var FAVORITE; var ALL; } From 9c641f9e90fb1b4ba09eaf6d012bbe7b02487913 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 17:26:51 -0400 Subject: [PATCH 37/74] tabbing movement --- source/funkin/freeplayStuff/LetterSort.hx | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index ed3ca7312..eb12ecbbe 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -78,8 +78,41 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> public function changeSelection(diff:Int = 0) { + var ezTimer:Int->FreeplayLetter->Float->Void = function(frameNum:Int, daLetter:FreeplayLetter, offsetNum:Float) { + new FlxTimer().start(frameNum / 24, function(_) { + daLetter.offset.x = offsetNum; + }); + }; + + var positions:Array<Float> = [-10, -22, -67, 2, 0]; + if (diff < 0) { + for (index => letter in letters) + { + letter.offset.x = positions[0]; + + new FlxTimer().start(1 / 24, function(_) { + letter.offset.x = positions[1]; + if (index == 0) letter.visible = false; + }); + + new FlxTimer().start(2 / 24, function(_) { + letter.offset.x = positions[2]; + if (index == 0) letter.visible = true; + }); + + if (index == 2) + { + ezTimer(3, letter, 0); + // letter.offset.x = 0; + continue; + } + + ezTimer(3, letter, positions[3]); + ezTimer(4, letter, positions[4]); + } + leftArrow.offset.x = 3; new FlxTimer().start(2 / 24, function(_) { leftArrow.offset.x = 0; @@ -87,6 +120,32 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> } else if (diff > 0) { + // same timing and functions and shit as the left one... except to the right!! + for (index => letter in letters) + { + letter.offset.x = -positions[0]; + + new FlxTimer().start(1 / 24, function(_) { + letter.offset.x = -positions[1]; + if (index == 4) letter.visible = false; + }); + + new FlxTimer().start(2 / 24, function(_) { + letter.offset.x = -positions[2]; + if (index == 4) letter.visible = true; + }); + + if (index == 2) + { + ezTimer(3, letter, 0); + // letter.offset.x = 0; + continue; + } + + ezTimer(3, letter, -positions[3]); + ezTimer(4, letter, -positions[4]); + } + rightArrow.offset.x = -3; new FlxTimer().start(2 / 24, function(_) { rightArrow.offset.x = 0; From ee7108757b609164379408632e7bd2fdf9cad320 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 17:58:06 -0400 Subject: [PATCH 38/74] proper fav and # symbol move --- source/funkin/freeplayStuff/LetterSort.hx | 48 +++++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index eb12ecbbe..fc4bf9344 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -14,12 +14,13 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> { public var letters:Array<FreeplayLetter> = []; - var curSelection:Int = 0; + var curSelection:Int = 2; public var changeSelectionCallback:String->Void; var leftArrow:FlxSprite; var rightArrow:FlxSprite; + var grpSeperators:Array<FlxSprite> = []; public function new(x, y) { @@ -58,6 +59,8 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> // sep.animation.play("seperator"); sep.color = letter.color.getDarkened(darkness); add(sep); + + grpSeperators.push(sep); } rightArrow = new FlxSprite(380, 15).loadGraphic(Paths.image("freeplay/miniArrow")); @@ -78,16 +81,24 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> public function changeSelection(diff:Int = 0) { - var ezTimer:Int->FreeplayLetter->Float->Void = function(frameNum:Int, daLetter:FreeplayLetter, offsetNum:Float) { + var ezTimer:Int->FlxSprite->Float->Void = function(frameNum:Int, spr:FlxSprite, offsetNum:Float) { new FlxTimer().start(frameNum / 24, function(_) { - daLetter.offset.x = offsetNum; + spr.offset.x = offsetNum; }); }; - var positions:Array<Float> = [-10, -22, -67, 2, 0]; + var positions:Array<Float> = [-10, -22, 2, 0]; if (diff < 0) { + for (sep in grpSeperators) + { + ezTimer(0, sep, positions[0]); + ezTimer(1, sep, positions[1]); + ezTimer(2, sep, positions[2]); + ezTimer(3, sep, positions[3]); + } + for (index => letter in letters) { letter.offset.x = positions[0]; @@ -99,7 +110,7 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> new FlxTimer().start(2 / 24, function(_) { letter.offset.x = positions[2]; - if (index == 0) letter.visible = true; + if (index == 0.) letter.visible = true; }); if (index == 2) @@ -110,7 +121,6 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> } ezTimer(3, letter, positions[3]); - ezTimer(4, letter, positions[4]); } leftArrow.offset.x = 3; @@ -120,19 +130,27 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> } else if (diff > 0) { + for (sep in grpSeperators) + { + ezTimer(0, sep, -positions[0]); + ezTimer(1, sep, -positions[1]); + ezTimer(2, sep, -positions[2]); + ezTimer(3, sep, -positions[3]); + } // same timing and functions and shit as the left one... except to the right!! + for (index => letter in letters) { letter.offset.x = -positions[0]; new FlxTimer().start(1 / 24, function(_) { letter.offset.x = -positions[1]; - if (index == 4) letter.visible = false; + if (index == 0) letter.visible = false; }); new FlxTimer().start(2 / 24, function(_) { letter.offset.x = -positions[2]; - if (index == 4) letter.visible = true; + if (index == 0) letter.visible = true; }); if (index == 2) @@ -143,7 +161,6 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> } ezTimer(3, letter, -positions[3]); - ezTimer(4, letter, -positions[4]); } rightArrow.offset.x = -3; @@ -157,7 +174,7 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> if (curSelection >= letters[0].arr.length) curSelection = 0; for (letter in letters) - letter.changeLetter(diff, curSelection + 2); + letter.changeLetter(diff, curSelection); if (changeSelectionCallback != null) changeSelectionCallback(letters[2].arr[letters[2].curLetter]); // bullshit and long lol! } @@ -210,6 +227,7 @@ class FreeplayLetter extends FlxAtlasSprite if (curLetter >= arr.length) curLetter = 0; var animName:String = arr[curLetter] + " move"; + switch (arr[curLetter]) { case "I L": @@ -221,7 +239,13 @@ class FreeplayLetter extends FlxAtlasSprite } this.anim.play(animName); - if (curSelection != curLetter) this.anim.pause(); - updateHitbox(); + if (curSelection != curLetter) + { + trace(animName); + trace(curLetter); + trace(curSelection); + this.anim.pause(); + } + // updateHitbox(); } } From 8052773f922e28e7443581f4a57829fd898374cf Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 7 Aug 2023 17:58:55 -0400 Subject: [PATCH 39/74] comment --- source/funkin/freeplayStuff/LetterSort.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index fc4bf9344..3fbbc12c4 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -14,6 +14,7 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> { public var letters:Array<FreeplayLetter> = []; + // starts at 2, cuz that's the middle letter on start (accounting for fav and #, it should begin at ALL filter) var curSelection:Int = 2; public var changeSelectionCallback:String->Void; From 92f45d93aa6aa23a61d3ed20b26b0b389e9775cb Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 8 Aug 2023 14:35:07 -0400 Subject: [PATCH 40/74] pixel icons --- assets | 2 +- source/funkin/FreeplayState.hx | 1 + source/funkin/freeplayStuff/SongMenuItem.hx | 51 +++++++++++++++++---- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/assets b/assets index b3fec7399..b0bd61a2f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b3fec73999ce576d009f70b345f19da6b5f0f82a +Subproject commit b0bd61a2f0fa9be900a236f20662601bd4d81a1d diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index bc3d9bbce..82629dba5 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -530,6 +530,7 @@ class FreeplayState extends MusicBeatSubState for (i in 0...tempSongs.length) { var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, 0, tempSongs[i].songName); + if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter); funnyMenu.onConfirm = capsuleOnConfirmDefault; funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; funnyMenu.targetPos.x = funnyMenu.x; diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 91a7c188c..a11538791 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -12,6 +12,7 @@ import flixel.util.FlxTimer; class SongMenuItem extends FlxSpriteGroup { var capsule:FlxSprite; + var pixelIcon:FlxSprite; public var selected(default, set):Bool = false; @@ -28,7 +29,7 @@ class SongMenuItem extends FlxSpriteGroup public var onConfirm:Void->Void; - public function new(x:Float, y:Float, song:String) + public function new(x:Float, y:Float, song:String, ?character:String) { super(x, y); @@ -46,6 +47,13 @@ class SongMenuItem extends FlxSpriteGroup songText.color = 0xFF43C1EA; add(songText); + pixelIcon = new FlxSprite(80, 35); + pixelIcon.makeGraphic(32, 32, 0x00000000); + pixelIcon.antialiasing = false; + add(pixelIcon); + + if (character != null) setCharacter(character); + favIcon = new FlxSprite(400, 40); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.animation.addByPrefix('fav', "favorite heart", 24, false); @@ -56,6 +64,31 @@ class SongMenuItem extends FlxSpriteGroup selected = selected; // just to kickstart the set_selected } + /** + * [Description] + * @param char Should be songCharacter, and will get translated to the correct path via switch + */ + public function setCharacter(char:String) + { + var charPath:String = "freeplay/icons/"; + + switch (char) + { + case "monster-christmas": + charPath += "monsterpixel"; + case "mom": + charPath += "mommypixel"; + case "dad": + charPath += "daddypixel"; + default: + charPath += char + "pixel"; + } + + pixelIcon.loadGraphic(Paths.image(charPath)); + pixelIcon.setGraphicSize(Std.int(pixelIcon.width * 2)); + // pixelIcon.updateHitbox(); + } + var frameInTicker:Float = 0; var frameInTypeBeat:Int = 0; @@ -102,12 +135,12 @@ class SongMenuItem extends FlxSpriteGroup { frameInTicker = 0; - scale.x = xFrames[frameInTypeBeat]; - scale.y = 1 / xFrames[frameInTypeBeat]; + capsule.scale.x = xFrames[frameInTypeBeat]; + capsule.scale.y = 1 / xFrames[frameInTypeBeat]; x = FlxG.width * xPosLerpLol[Std.int(Math.min(frameInTypeBeat, xPosLerpLol.length - 1))]; - scale.x *= realScaled; - scale.y *= realScaled; + capsule.scale.x *= realScaled; + capsule.scale.y *= realScaled; frameInTypeBeat += 1; } @@ -121,12 +154,12 @@ class SongMenuItem extends FlxSpriteGroup { frameOutTicker = 0; - scale.x = xFrames[frameOutTypeBeat]; - scale.y = 1 / xFrames[frameOutTypeBeat]; + capsule.scale.x = xFrames[frameOutTypeBeat]; + capsule.scale.y = 1 / xFrames[frameOutTypeBeat]; x = FlxG.width * xPosOutLerpLol[Std.int(Math.min(frameOutTypeBeat, xPosOutLerpLol.length - 1))]; - scale.x *= realScaled; - scale.y *= realScaled; + capsule.scale.x *= realScaled; + capsule.scale.y *= realScaled; frameOutTypeBeat += 1; } From c3d30f6711c849c1221fd443e73920e4e74928d0 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 9 Aug 2023 02:47:22 -0400 Subject: [PATCH 41/74] polish / optimize in progress --- source/funkin/FreeplayState.hx | 49 ++++++-------------- source/funkin/freeplayStuff/LetterSort.hx | 9 +--- source/funkin/freeplayStuff/SongMenuItem.hx | 51 ++++++++++++++++++--- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 82629dba5..16c4d5233 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -420,7 +420,7 @@ class FreeplayState extends MusicBeatSubState grpTxtScrolls.visible = true; }); - generateSongList(); + generateSongList(null, false); // FlxG.sound.playMusic(Paths.music('title'), 0); // FlxG.sound.music.fadeIn(2, 0, 0.8); @@ -468,7 +468,10 @@ class FreeplayState extends MusicBeatSubState { curSelected = 1; - grpCapsules.clear(); + for (cap in grpCapsules.members) + cap.kill(); + + // grpCapsules.clear(); // var regexp:EReg = regexp; var tempSongs:Array<FreeplaySongData> = songs; @@ -499,23 +502,8 @@ class FreeplayState extends MusicBeatSubState } } - // if (regexp != null) - // tempSongs = songs.filter(item -> regexp.match(item.songName)); - - // tempSongs.sort(function(a, b):Int - // { - // var tempA = a.songName.toUpperCase(); - // var tempB = b.songName.toUpperCase(); - - // if (tempA < tempB) - // return -1; - // else if (tempA > tempB) - // return 1; - // else - // return 0; - // }); - - var randomCapsule:SongMenuItem = new SongMenuItem(FlxG.width, 0, "Random"); + var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem); + randomCapsule.init(FlxG.width, 0, "Random"); randomCapsule.onConfirm = function() { trace("RANDOM SELECTED"); }; @@ -529,7 +517,8 @@ class FreeplayState extends MusicBeatSubState for (i in 0...tempSongs.length) { - var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, 0, tempSongs[i].songName); + var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem); + funnyMenu.init(FlxG.width, 0, tempSongs[i].songName); if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter); funnyMenu.onConfirm = capsuleOnConfirmDefault; funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; @@ -540,20 +529,12 @@ class FreeplayState extends MusicBeatSubState funnyMenu.favIcon.visible = tempSongs[i].isFav; // fp.updateScore(0); - funnyMenu.initJumpIn(Math.min(i, 4), force); + + if (i < 8) funnyMenu.initJumpIn(Math.min(i, 4), force); + else + funnyMenu.forcePosition(); grpCapsules.add(funnyMenu); - - var songText:Alphabet = new Alphabet(0, (70 * i) + 30, tempSongs[i].songName, true, false); - songText.x += 100; - songText.isMenuItem = true; - songText.targetY = i; - - // grpSongs.add(songText); - - // songText.x += 40; - // DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !! - // songText.screenCenter(X); } changeSelection(); @@ -958,8 +939,6 @@ class FreeplayState extends MusicBeatSubState { // fp.updateScore(12345); - NGio.logEvent('Fresh'); - // NGio.logEvent('Fresh'); FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); @@ -979,8 +958,6 @@ class FreeplayState extends MusicBeatSubState // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName), 0); #end - var bullShit:Int = 0; - for (index => capsule in grpCapsules.members) { index += 1; diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx index 3fbbc12c4..e6d923c90 100644 --- a/source/funkin/freeplayStuff/LetterSort.hx +++ b/source/funkin/freeplayStuff/LetterSort.hx @@ -38,16 +38,12 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite> letter.x += 50; letter.y += 50; letter.ogY = y; + // letter.visible = false; add(letter); letters.push(letter); if (i != 2) letter.scale.x = letter.scale.y = 0.8; - else - { - // letter.x += 10; - // letter.scale.x = letter.scale.y = 1.1; - } var darkness:Float = Math.abs(i - 2) / 6; @@ -242,9 +238,6 @@ class FreeplayLetter extends FlxAtlasSprite this.anim.play(animName); if (curSelection != curLetter) { - trace(animName); - trace(curLetter); - trace(curSelection); this.anim.pause(); } // updateHitbox(); diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index a11538791..3a1dfacbf 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -50,6 +50,7 @@ class SongMenuItem extends FlxSpriteGroup pixelIcon = new FlxSprite(80, 35); pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; + pixelIcon.active = false; add(pixelIcon); if (character != null) setCharacter(character); @@ -64,6 +65,17 @@ class SongMenuItem extends FlxSpriteGroup selected = selected; // just to kickstart the set_selected } + public function init(x:Float, y:Float, song:String, ?character:String) + { + this.x = x; + this.y = y; + this.songTitle = song; + songText.text = this.songTitle; + if (character != null) setCharacter(character); + + selected = selected; + } + /** * [Description] * @param char Should be songCharacter, and will get translated to the correct path via switch @@ -103,6 +115,8 @@ class SongMenuItem extends FlxSpriteGroup public function initJumpIn(maxTimer:Float, ?force:Bool):Void { + frameInTypeBeat = 0; + new FlxTimer().start((1 / 24) * maxTimer, function(doShit) { doJumpIn = true; }); @@ -111,18 +125,41 @@ class SongMenuItem extends FlxSpriteGroup doLerp = true; }); - if (!force) + if (force) { - new FlxTimer().start(((0.20 * maxTimer) / (1 + maxTimer)) + 0.75, function(swagShi) { + alpha = 1; + songText.visible = true; + } + else + { + new FlxTimer().start((xFrames.length / 24) * 2.5, function(_) { songText.visible = true; alpha = 1; }); } - else - { - songText.visible = true; - alpha = 1; - } + } + + public function forcePosition() + { + alpha = 1; + doLerp = true; + doJumpIn = false; + doJumpOut = false; + + frameInTypeBeat = xFrames.length; + frameOutTypeBeat = 0; + + capsule.scale.x = xFrames[frameInTypeBeat - 1]; + capsule.scale.y = 1 / xFrames[frameInTypeBeat - 1]; + // x = FlxG.width * xPosLerpLol[Std.int(Math.min(frameInTypeBeat - 1, xPosLerpLol.length - 1))]; + + x = targetPos.x; + y = targetPos.y; + + capsule.scale.x *= realScaled; + capsule.scale.y *= realScaled; + + songText.visible = true; } override function update(elapsed:Float) From 97267d18b9ccd56eeeea1d646d03d43ee43e45b9 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 9 Aug 2023 03:03:58 -0400 Subject: [PATCH 42/74] freeplay wrapping fix --- source/funkin/FreeplayState.hx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 16c4d5233..9d0b346b3 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -944,14 +944,24 @@ class FreeplayState extends MusicBeatSubState curSelected += change; - if (curSelected < 0) curSelected = grpCapsules.members.length - 1; - if (curSelected >= grpCapsules.members.length) curSelected = 0; + if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; + if (curSelected >= grpCapsules.countLiving()) curSelected = 0; // selector.y = (70 * curSelected) + 30; // intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); - intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); - intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty); + + if (songs[curSelected] != null) + { + intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty); + intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty); + } + else + { + intendedScore = 0; + intendedCompletion = 0; + } + // lerpScore = 0; #if PRELOAD_ALL @@ -971,7 +981,7 @@ class FreeplayState extends MusicBeatSubState if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure } - if (grpCapsules.members.length > 0) grpCapsules.members[curSelected].selected = true; + if (grpCapsules.countLiving() > 0) grpCapsules.members[curSelected].selected = true; } } From 097b5dab46c2f679ee38e2c9a9dce012f3648e45 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 9 Aug 2023 16:15:34 -0400 Subject: [PATCH 43/74] freeplay optimize fix --- art | 2 +- source/funkin/FreeplayState.hx | 46 ++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/art b/art index 1656bea53..bfb99631f 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 1656bea5370c65879aaeb323e329f403c78071c5 +Subproject commit bfb99631f899493b599e1dee49a029b8ec18f16a diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 9d0b346b3..7aedb5ae2 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -355,6 +355,28 @@ class FreeplayState extends MusicBeatSubState txtCompletion.visible = false; add(txtCompletion); + var letterSort:LetterSort = new LetterSort(400, 75); + add(letterSort); + letterSort.visible = false; + + exitMovers.set([letterSort], + { + y: -100, + speed: 0.3 + }); + + letterSort.changeSelectionCallback = (str) -> { + switch (str) + { + case "fav": + generateSongList({filterType: FAVORITE}, true); + case "ALL": + // generateSongList(null, true); + default: + generateSongList({filterType: REGEXP, filterData: str}, true); + } + }; + exitMovers.set([fp, txtCompletion, fnfHighscoreSpr], { x: FlxG.width, @@ -370,33 +392,14 @@ class FreeplayState extends MusicBeatSubState add(diffSelLeft); add(diffSelRight); + letterSort.visible = true; + exitMovers.set([diffSelLeft, diffSelRight], { x: -diffSelLeft.width * 2, speed: 0.26 }); - var letterSort:LetterSort = new LetterSort(400, 75); - add(letterSort); - - exitMovers.set([letterSort], - { - y: -100, - speed: 0.3 - }); - - letterSort.changeSelectionCallback = (str) -> { - switch (str) - { - case "fav": - generateSongList({filterType: FAVORITE}, true); - case "ALL": - generateSongList(null, true); - default: - generateSongList({filterType: REGEXP, filterData: str}, true); - } - }; - new FlxTimer().start(1 / 24, function(handShit) { fnfHighscoreSpr.visible = true; fnfFreeplay.visible = true; @@ -413,7 +416,6 @@ class FreeplayState extends MusicBeatSubState }); pinkBack.color = 0xFFffd863; - // fnfFreeplay.visible = true; bgDad.visible = true; orangeBackShit.visible = true; alsoOrangeLOL.visible = true; From b96c0e625697d6880b8fbac1f28913495696451f Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 9 Aug 2023 19:34:19 -0400 Subject: [PATCH 44/74] album artist stuff --- art | 2 +- assets | 2 +- source/funkin/FreeplayState.hx | 53 +++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/art b/art index bfb99631f..f4acf8d6e 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit bfb99631f899493b599e1dee49a029b8ec18f16a +Subproject commit f4acf8d6e1981ea4d1a28e7e512e335b2a4f261f diff --git a/assets b/assets index b0bd61a2f..37fa62d8e 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b0bd61a2f0fa9be900a236f20662601bd4d81a1d +Subproject commit 37fa62d8e88b7a7023e689ef52d8a5c527cd4618 diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 7aedb5ae2..5e3e896e2 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -38,6 +38,7 @@ import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; import lime.app.Future; import lime.utils.Assets; +import funkin.graphics.adobeanimate.FlxAtlasSprite; class FreeplayState extends MusicBeatSubState { @@ -312,6 +313,40 @@ class FreeplayState extends MusicBeatSubState grpDifficulties.group.members[curDifficulty].visible = true; + var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll")); + albumArt.visible = false; + add(albumArt); + + exitMovers.set([albumArt], + { + x: FlxG.width, + speed: 0.4, + wait: 0 + }); + + var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1')); + var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); + + albumTitle.visible = false; + albumArtist.visible = false; + + exitMovers.set([albumTitle], + { + x: FlxG.width, + speed: 0.2, + wait: 0.1 + }); + + exitMovers.set([albumArtist], + { + x: FlxG.width * 1.1, + speed: 0.2, + wait: 0.2 + }); + + add(albumTitle); + add(albumArtist); + var overhangStuff:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 64, FlxColor.BLACK); overhangStuff.y -= overhangStuff.height; add(overhangStuff); @@ -371,7 +406,7 @@ class FreeplayState extends MusicBeatSubState case "fav": generateSongList({filterType: FAVORITE}, true); case "ALL": - // generateSongList(null, true); + generateSongList(null, true); default: generateSongList({filterType: REGEXP, filterData: str}, true); } @@ -384,6 +419,22 @@ class FreeplayState extends MusicBeatSubState }); dj.onIntroDone.add(function() { + // when boyfriend hits dat shiii + // + albumArt.visible = true; + albumArt.anim.play(""); + albumArt.anim.onComplete = function() { + albumArt.anim.pause(); + }; + + new FlxTimer().start(1, function(_) { + albumTitle.visible = true; + }); + + new FlxTimer().start(35 / 24, function(_) { + albumArtist.visible = true; + }); + FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut}); var diffSelLeft = new DifficultySelector(20, grpDifficulties.y - 10, false, controls); From 4468e2a5a587e7b0f5b867aa3f2e165d276a224f Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Wed, 2 Aug 2023 18:08:49 -0400 Subject: [PATCH 45/74] Fixed several bugs with Play State (mostly restarting the song) --- source/funkin/play/PlayState.hx | 10 ++++++++++ source/funkin/play/character/BaseCharacter.hx | 1 + 2 files changed, 11 insertions(+) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ce72fa56c..3722d3797 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -820,6 +820,16 @@ class PlayState extends MusicBeatSubState FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } + if (currentStage.getBoyfriend() != null) + { + FlxG.watch.addQuick('bfCameraFocus', currentStage.getBoyfriend().cameraFocusPoint); + } + + if (currentStage.getDad() != null) + { + FlxG.watch.addQuick('dadCameraFocus', currentStage.getDad().cameraFocusPoint); + } + // TODO: Add a song event for Handle GF dance speed. // Handle player death. diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 30b549fd3..a9be44e7f 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -235,6 +235,7 @@ class BaseCharacter extends Bopper // Then reapply animOffsets... // applyAnimationOffsets(getCurrentAnimation()); + // Make sure we are playing the idle animation this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song. // Make sure we are playing the idle animation // ...then update the hitbox so that this.width and this.height are correct. From 2f56c74060e4f906e482329d2bbc27f834f9358d Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Thu, 3 Aug 2023 12:41:54 -0400 Subject: [PATCH 46/74] Fix HTML typing issue --- source/funkin/input/PreciseInputManager.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/input/PreciseInputManager.hx b/source/funkin/input/PreciseInputManager.hx index 6217b2fe7..4cce0964d 100644 --- a/source/funkin/input/PreciseInputManager.hx +++ b/source/funkin/input/PreciseInputManager.hx @@ -181,7 +181,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList> updateKeyStates(key, true); - if (getInputByKey(key) ?.justPressed ?? false) + if (getInputByKey(key)?.justPressed ?? false) { onInputPressed.dispatch( { @@ -203,7 +203,7 @@ class PreciseInputManager extends FlxKeyManager<FlxKey, PreciseInputList> updateKeyStates(key, false); - if (getInputByKey(key) ?.justReleased ?? false) + if (getInputByKey(key)?.justReleased ?? false) { onInputReleased.dispatch( { @@ -264,7 +264,7 @@ class PreciseInputList extends FlxKeyList { for (key in getKeysForDir(noteDir)) { - if (check(_preciseInputManager.getInputByKey(key) ?.ID)) return true; + if (check(_preciseInputManager.getInputByKey(key)?.ID)) return true; } return false; } From 4f0e9614e0a881025a48bb13f40941b9cd2b6d59 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Thu, 3 Aug 2023 22:22:29 -0400 Subject: [PATCH 47/74] Fix build issues caused by int64 handling --- source/funkin/play/PlayState.hx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3722d3797..2c085513d 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2229,7 +2229,8 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; + var currentTimestampNs:Int64 = PreciseInputManager.getCurrentTimestamp(); + var inputLatencyMs:Float = haxe.Int64.toInt(currentTimestampNs - input.timestamp) / Constants.NS_PER_MS; trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. From 5b4a00e893f59949469f1d7e1aed1cb981cab15f Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 4 Aug 2023 11:13:41 -0400 Subject: [PATCH 48/74] Make sure timestamps are consistent with use of Int64. --- source/funkin/play/PlayState.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 2c085513d..3722d3797 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2229,8 +2229,7 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var currentTimestampNs:Int64 = PreciseInputManager.getCurrentTimestamp(); - var inputLatencyMs:Float = haxe.Int64.toInt(currentTimestampNs - input.timestamp) / Constants.NS_PER_MS; + var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. From 11b1ca510075dc92c82d2a369d96a0c777f6237f Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 4 Aug 2023 11:18:00 -0400 Subject: [PATCH 49/74] Fix bug where pressing ENTER in UI would cause song to try to preview. Fix bug where trying to preview a newly created song would crash. --- source/funkin/play/song/Song.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index d11c7744b..f3fc89a86 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -117,7 +117,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>, validScore:Bool = false):Song { - var result:Song = new Song(songId); + var result:Song = new Song(songId, true); result._metadata.clear(); for (meta in metadata) From 5fa9c5246fae5e33801e3ab648f248668ebe3f2e Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 4 Aug 2023 12:35:01 -0400 Subject: [PATCH 50/74] Fixed a bug where beat hit events were called multiple times during the conductor --- source/funkin/play/PlayState.hx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3722d3797..ce72fa56c 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -820,16 +820,6 @@ class PlayState extends MusicBeatSubState FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } - if (currentStage.getBoyfriend() != null) - { - FlxG.watch.addQuick('bfCameraFocus', currentStage.getBoyfriend().cameraFocusPoint); - } - - if (currentStage.getDad() != null) - { - FlxG.watch.addQuick('dadCameraFocus', currentStage.getDad().cameraFocusPoint); - } - // TODO: Add a song event for Handle GF dance speed. // Handle player death. From f7bac02ee32a7029395b91a64aed6006805d87fd Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Fri, 4 Aug 2023 17:25:13 -0400 Subject: [PATCH 51/74] Fix crashing, broken countdown, broken miss muting in song preview --- source/funkin/play/character/BaseCharacter.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index a9be44e7f..30b549fd3 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -235,7 +235,6 @@ class BaseCharacter extends Bopper // Then reapply animOffsets... // applyAnimationOffsets(getCurrentAnimation()); - // Make sure we are playing the idle animation this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song. // Make sure we are playing the idle animation // ...then update the hitbox so that this.width and this.height are correct. From a8e39d90e8df855d90b9b384b51ab4d746b71e5f Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 11 Aug 2023 05:20:25 -0400 Subject: [PATCH 52/74] temp rank stuff in progress --- assets | 2 +- hmm.json | 8 ++++---- source/funkin/freeplayStuff/SongMenuItem.hx | 21 +++++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/assets b/assets index 37fa62d8e..abbbc89a8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 37fa62d8e88b7a7023e689ef52d8a5c527cd4618 +Subproject commit abbbc89a83f48317568b5b25eaf831ae16f88a8c diff --git a/hmm.json b/hmm.json index aa032fb75..85c83ea6d 100644 --- a/hmm.json +++ b/hmm.json @@ -4,14 +4,14 @@ "name": "discord_rpc", "type": "git", "dir": null, - "ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5", + "ref": "2d83fa8", "url": "https://github.com/Aidan63/linc_discord-rpc" }, { "name": "flixel", "type": "git", "dir": null, - "ref": "32cee07a0e5f21e590a4b21234603b2cd5898b10", + "ref": "32cee07a", "url": "https://github.com/EliteMasterEric/flixel" }, { @@ -32,7 +32,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "a9136359271cae6ea3016b7fd9023c5c42562933", + "ref": "a913635", "url": "https://github.com/ninjamuffin99/flxanimate" }, { @@ -75,7 +75,7 @@ "name": "hxCodec", "type": "git", "dir": null, - "ref": "c8c47e706ad82a423783006ed901b6d93c89a421", + "ref": "c8c47e7", "url": "https://github.com/polybiusproxy/hxCodec" }, { diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 3a1dfacbf..184846805 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -20,6 +20,9 @@ class SongMenuItem extends FlxSpriteGroup public var songText:FlxText; public var favIcon:FlxSprite; + public var ranking:FlxSprite; + + var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect"]; public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; @@ -42,6 +45,20 @@ class SongMenuItem extends FlxSpriteGroup // capsule.animation add(capsule); + var rank:String = FlxG.random.getObject(ranks); + + ranking = new FlxSprite(capsule.width * 0.78, 30); + ranking.loadGraphic(Paths.image("freeplay/ranks/" + rank)); + ranking.scale.x = ranking.scale.y = realScaled; + ranking.alpha = 0.75; + add(ranking); + + switch (rank) + { + case "perfect": + ranking.x -= 10; + } + songText = new FlxText(capsule.width * 0.23, 40, 0, songTitle, Std.int(40 * realScaled)); songText.font = "5by7"; songText.color = 0xFF43C1EA; @@ -218,12 +235,12 @@ class SongMenuItem extends FlxSpriteGroup function set_selected(value:Bool):Bool { - // trace(value); - // cute one liners, lol! songText.alpha = value ? 1 : 0.6; capsule.offset.x = value ? 0 : -5; capsule.animation.play(value ? "selected" : "unselected"); + ranking.alpha = value ? 1 : 0.7; + ranking.color = value ? 0xFFFFFFFF : 0xFFAAAAAA; return value; } } From eacd76a0fa1437f118413054a9e8c5c25956423a Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Fri, 11 Aug 2023 21:45:34 -0400 Subject: [PATCH 53/74] difficulty rankings and grayscale mockup --- art | 2 +- assets | 2 +- source/funkin/freeplayStuff/SongMenuItem.hx | 14 +++++++++++++ source/funkin/shaderslmfao/Grayscale.hx | 22 +++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 source/funkin/shaderslmfao/Grayscale.hx diff --git a/art b/art index f4acf8d6e..fbe23f8b5 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit f4acf8d6e1981ea4d1a28e7e512e335b2a4f261f +Subproject commit fbe23f8b59831ca0123727a35e02cdfb49f3ce60 diff --git a/assets b/assets index abbbc89a8..e5aad64d7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit abbbc89a83f48317568b5b25eaf831ae16f88a8c +Subproject commit e5aad64d7b5201d23cd28cf740cff48fae6ebf74 diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 184846805..780a7b2ad 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -8,6 +8,7 @@ import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.text.FlxText; import flixel.util.FlxTimer; +import funkin.shaderslmfao.Grayscale; class SongMenuItem extends FlxSpriteGroup { @@ -24,6 +25,11 @@ class SongMenuItem extends FlxSpriteGroup var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect"]; + // lol... + var diffRanks:Array<String> = [ + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "14", "15" + ]; + public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; public var doJumpIn:Bool = false; @@ -31,6 +37,7 @@ class SongMenuItem extends FlxSpriteGroup public var doJumpOut:Bool = false; public var onConfirm:Void->Void; + public var diffGrayscale:Grayscale; public function new(x:Float, y:Float, song:String, ?character:String) { @@ -53,6 +60,12 @@ class SongMenuItem extends FlxSpriteGroup ranking.alpha = 0.75; add(ranking); + diffGrayscale = new Grayscale(1); + + var diffRank = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRankings/diff" + FlxG.random.getObject(diffRanks))); + diffRank.shader = diffGrayscale; + add(diffRank); + switch (rank) { case "perfect": @@ -236,6 +249,7 @@ class SongMenuItem extends FlxSpriteGroup function set_selected(value:Bool):Bool { // cute one liners, lol! + diffGrayscale.setAmount(value ? 0 : 0.8); songText.alpha = value ? 1 : 0.6; capsule.offset.x = value ? 0 : -5; capsule.animation.play(value ? "selected" : "unselected"); diff --git a/source/funkin/shaderslmfao/Grayscale.hx b/source/funkin/shaderslmfao/Grayscale.hx new file mode 100644 index 000000000..016d64b46 --- /dev/null +++ b/source/funkin/shaderslmfao/Grayscale.hx @@ -0,0 +1,22 @@ +package funkin.shaderslmfao; + +import flixel.addons.display.FlxRuntimeShader; +import funkin.Paths; +import openfl.utils.Assets; + +class Grayscale extends FlxRuntimeShader +{ + public var amount:Float = 1; + + public function new(amount:Float = 1) + { + super(Assets.getText(Paths.frag("grayscale"))); + setAmount(amount); + } + + public function setAmount(value:Float):Void + { + amount = value; + this.setFloat("amount", amount); + } +} From f474c3aac5da74adbe27f426ad9efe93b9e7ee4f Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sat, 12 Aug 2023 00:31:43 -0400 Subject: [PATCH 54/74] better grouping stuf --- source/funkin/freeplayStuff/SongMenuItem.hx | 30 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 780a7b2ad..a28ea1d9f 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -1,5 +1,6 @@ package funkin.freeplayStuff; +import flixel.group.FlxGroup; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; @@ -52,6 +53,9 @@ class SongMenuItem extends FlxSpriteGroup // capsule.animation add(capsule); + // doesn't get added, simply is here to help with visibility of things for the pop in! + grpHide = new FlxGroup(); + var rank:String = FlxG.random.getObject(ranks); ranking = new FlxSprite(capsule.width * 0.78, 30); @@ -59,12 +63,15 @@ class SongMenuItem extends FlxSpriteGroup ranking.scale.x = ranking.scale.y = realScaled; ranking.alpha = 0.75; add(ranking); + grpHide.add(ranking); diffGrayscale = new Grayscale(1); var diffRank = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRankings/diff" + FlxG.random.getObject(diffRanks))); diffRank.shader = diffGrayscale; + diffRank.visible = false; add(diffRank); + grpHide.add(diffRank); switch (rank) { @@ -72,16 +79,18 @@ class SongMenuItem extends FlxSpriteGroup ranking.x -= 10; } - songText = new FlxText(capsule.width * 0.23, 40, 0, songTitle, Std.int(40 * realScaled)); + songText = new FlxText(capsule.width * 0.26, 45, 0, songTitle, Std.int(40 * realScaled)); songText.font = "5by7"; songText.color = 0xFF43C1EA; add(songText); + grpHide.add(songText); pixelIcon = new FlxSprite(80, 35); pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; add(pixelIcon); + grpHide.add(pixelIcon); if (character != null) setCharacter(character); @@ -91,10 +100,21 @@ class SongMenuItem extends FlxSpriteGroup favIcon.animation.play('fav'); favIcon.setGraphicSize(60, 60); add(favIcon); + grpHide.add(favIcon); + + setVisibleGrp(false); selected = selected; // just to kickstart the set_selected } + function setVisibleGrp(value:Bool) + { + for (spr in grpHide.members) + { + spr.visible = value; + } + } + public function init(x:Float, y:Float, song:String, ?character:String) { this.x = x; @@ -158,17 +178,19 @@ class SongMenuItem extends FlxSpriteGroup if (force) { alpha = 1; - songText.visible = true; + setVisibleGrp(true); } else { new FlxTimer().start((xFrames.length / 24) * 2.5, function(_) { - songText.visible = true; + setVisibleGrp(true); alpha = 1; }); } } + var grpHide:FlxGroup; + public function forcePosition() { alpha = 1; @@ -189,7 +211,7 @@ class SongMenuItem extends FlxSpriteGroup capsule.scale.x *= realScaled; capsule.scale.y *= realScaled; - songText.visible = true; + setVisibleGrp(true); } override function update(elapsed:Float) From 8525cfaf21109adb111eb6668a290137fd2ded88 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sat, 12 Aug 2023 00:42:17 -0400 Subject: [PATCH 55/74] cute text popin --- source/funkin/freeplayStuff/SongMenuItem.hx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index a28ea1d9f..be663af74 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -107,12 +107,29 @@ class SongMenuItem extends FlxSpriteGroup selected = selected; // just to kickstart the set_selected } + function textAppear() + { + songText.scale.x = 1.7; + songText.scale.y = 0.2; + + new FlxTimer().start(1 / 24, function(_) { + songText.scale.x = 0.4; + songText.scale.y = 1.4; + }); + + new FlxTimer().start(2 / 24, function(_) { + songText.scale.x = songText.scale.y = 1; + }); + } + function setVisibleGrp(value:Bool) { for (spr in grpHide.members) { spr.visible = value; } + + if (value) textAppear(); } public function init(x:Float, y:Float, song:String, ?character:String) From 0dbcc50bccb9b84ed03b2fe6a3be9846816f9585 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 13 Aug 2023 22:12:08 -0400 Subject: [PATCH 56/74] random song music in progress --- assets | 2 +- source/funkin/FreeplayState.hx | 11 ++++++++++- source/funkin/freeplayStuff/SongMenuItem.hx | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/assets b/assets index e5aad64d7..fd8251b13 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit e5aad64d7b5201d23cd28cf740cff48fae6ebf74 +Subproject commit fd8251b139d482146ed79495865a48676a4d1d68 diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 5e3e896e2..99f12bcb0 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -994,6 +994,7 @@ class FreeplayState extends MusicBeatSubState // NGio.logEvent('Fresh'); FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName)); curSelected += change; @@ -1034,7 +1035,15 @@ class FreeplayState extends MusicBeatSubState if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure } - if (grpCapsules.countLiving() > 0) grpCapsules.members[curSelected].selected = true; + if (grpCapsules.countLiving() > 0) + { + if (curSelected == 0) + { + FlxG.sound.playMusic(Paths.music('freeplay/freeplayRandom'), 0); + FlxG.sound.music.fadeIn(2, 0, 0.8); + } + grpCapsules.members[curSelected].selected = true; + } } } diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index be663af74..c781b8b8d 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -178,7 +178,7 @@ class SongMenuItem extends FlxSpriteGroup var xPosLerpLol:Array<Float> = [0.9, 0.4, 0.16, 0.16, 0.22, 0.22, 0.245]; // NUMBERS ARE JANK CUZ THE SCALING OR WHATEVER var xPosOutLerpLol:Array<Float> = [0.245, 0.75, 0.98, 0.98, 1.2]; // NUMBERS ARE JANK CUZ THE SCALING OR WHATEVER - public final realScaled:Float = 0.8; + public var realScaled:Float = 0.8; public function initJumpIn(maxTimer:Float, ?force:Bool):Void { From 5d6264e67ead40bdbf25efd90ec3bdfbe24fd2c1 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Sun, 13 Aug 2023 23:22:24 -0400 Subject: [PATCH 57/74] sprites follow rotation of capsule --- source/funkin/freeplayStuff/SongMenuItem.hx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index c781b8b8d..33a94cbe0 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -58,10 +58,11 @@ class SongMenuItem extends FlxSpriteGroup var rank:String = FlxG.random.getObject(ranks); - ranking = new FlxSprite(capsule.width * 0.78, 30); + ranking = new FlxSprite(capsule.width * 0.84, 30); ranking.loadGraphic(Paths.image("freeplay/ranks/" + rank)); ranking.scale.x = ranking.scale.y = realScaled; ranking.alpha = 0.75; + ranking.origin.set(capsule.origin.x - ranking.x, capsule.origin.y - ranking.y); add(ranking); grpHide.add(ranking); @@ -71,6 +72,7 @@ class SongMenuItem extends FlxSpriteGroup diffRank.shader = diffGrayscale; diffRank.visible = false; add(diffRank); + diffRank.origin.set(capsule.origin.x - diffRank.x, capsule.origin.y - diffRank.y); grpHide.add(diffRank); switch (rank) @@ -85,10 +87,11 @@ class SongMenuItem extends FlxSpriteGroup add(songText); grpHide.add(songText); - pixelIcon = new FlxSprite(80, 35); + pixelIcon = new FlxSprite(155, 15); pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; + add(pixelIcon); grpHide.add(pixelIcon); @@ -98,9 +101,10 @@ class SongMenuItem extends FlxSpriteGroup favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.animation.addByPrefix('fav', "favorite heart", 24, false); favIcon.animation.play('fav'); - favIcon.setGraphicSize(60, 60); + favIcon.setGraphicSize(50, 50); + favIcon.visible = false; add(favIcon); - grpHide.add(favIcon); + // grpHide.add(favIcon); setVisibleGrp(false); @@ -165,7 +169,10 @@ class SongMenuItem extends FlxSpriteGroup pixelIcon.loadGraphic(Paths.image(charPath)); pixelIcon.setGraphicSize(Std.int(pixelIcon.width * 2)); - // pixelIcon.updateHitbox(); + pixelIcon.updateHitbox(); + pixelIcon.origin.x += 100; + // pixelIcon.origin.x = capsule.origin.x; + // pixelIcon.offset.x -= pixelIcon.origin.x; } var frameInTicker:Float = 0; From 20d13f56accf9f6d0c83f6aa183e23585d1452b2 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 14 Aug 2023 02:49:18 -0400 Subject: [PATCH 58/74] capsule text effect --- assets | 2 +- source/funkin/freeplayStuff/CapsuleText.hx | 49 +++++++++++++++++++ source/funkin/freeplayStuff/SongMenuItem.hx | 9 ++-- .../funkin/shaderslmfao/GaussianBlurShader.hx | 25 ++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 source/funkin/freeplayStuff/CapsuleText.hx create mode 100644 source/funkin/shaderslmfao/GaussianBlurShader.hx diff --git a/assets b/assets index fd8251b13..bc7e486ba 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit fd8251b139d482146ed79495865a48676a4d1d68 +Subproject commit bc7e486ba4cf52e55893daa951e58b37059b0adb diff --git a/source/funkin/freeplayStuff/CapsuleText.hx b/source/funkin/freeplayStuff/CapsuleText.hx new file mode 100644 index 000000000..dda687f5e --- /dev/null +++ b/source/funkin/freeplayStuff/CapsuleText.hx @@ -0,0 +1,49 @@ +package funkin.freeplayStuff; + +import openfl.filters.BitmapFilterQuality; +import flixel.text.FlxText; +import flixel.group.FlxSpriteGroup; +import funkin.shaderslmfao.GaussianBlurShader; + +class CapsuleText extends FlxSpriteGroup +{ + public var blurredText:FlxText; + + var whiteText:FlxText; + + public var text(default, set):String; + + public function new(x:Float, y:Float, songTitle:String, size:Float) + { + super(x, y); + + blurredText = initText(songTitle, size); + blurredText.shader = new GaussianBlurShader(1); + whiteText = initText(songTitle, size); + // whiteText.shader = new GaussianBlurShader(0.3); + text = songTitle; + + blurredText.color = 0xFF00ccff; + whiteText.color = 0xFFFFFFFF; + add(blurredText); + add(whiteText); + } + + function initText(songTitle, size:Float):FlxText + { + var text:FlxText = new FlxText(0, 0, 0, songTitle, Std.int(size)); + text.font = "5by7"; + return text; + } + + function set_text(value:String):String + { + blurredText.text = value; + whiteText.text = value; + whiteText.textField.filters = [ + new openfl.filters.GlowFilter(0x00ccff, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM), + // new openfl.filters.BlurFilter(5, 5, BitmapFilterQuality.LOW) + ]; + return value; + } +} diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 33a94cbe0..596dfbed4 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -1,5 +1,6 @@ package funkin.freeplayStuff; +import funkin.shaderslmfao.GaussianBlurShader; import flixel.group.FlxGroup; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; @@ -20,7 +21,7 @@ class SongMenuItem extends FlxSpriteGroup public var songTitle:String = "Test"; - public var songText:FlxText; + public var songText:CapsuleText; public var favIcon:FlxSprite; public var ranking:FlxSprite; @@ -81,9 +82,7 @@ class SongMenuItem extends FlxSpriteGroup ranking.x -= 10; } - songText = new FlxText(capsule.width * 0.26, 45, 0, songTitle, Std.int(40 * realScaled)); - songText.font = "5by7"; - songText.color = 0xFF43C1EA; + songText = new CapsuleText(capsule.width * 0.26, 45, songTitle, Std.int(40 * realScaled)); add(songText); grpHide.add(songText); @@ -91,7 +90,6 @@ class SongMenuItem extends FlxSpriteGroup pixelIcon.makeGraphic(32, 32, 0x00000000); pixelIcon.antialiasing = false; pixelIcon.active = false; - add(pixelIcon); grpHide.add(pixelIcon); @@ -297,6 +295,7 @@ class SongMenuItem extends FlxSpriteGroup // cute one liners, lol! diffGrayscale.setAmount(value ? 0 : 0.8); songText.alpha = value ? 1 : 0.6; + songText.blurredText.visible = value ? true : false; capsule.offset.x = value ? 0 : -5; capsule.animation.play(value ? "selected" : "unselected"); ranking.alpha = value ? 1 : 0.7; diff --git a/source/funkin/shaderslmfao/GaussianBlurShader.hx b/source/funkin/shaderslmfao/GaussianBlurShader.hx new file mode 100644 index 000000000..ad472ac31 --- /dev/null +++ b/source/funkin/shaderslmfao/GaussianBlurShader.hx @@ -0,0 +1,25 @@ +package funkin.shaderslmfao; + +import flixel.addons.display.FlxRuntimeShader; +import funkin.Paths; +import openfl.utils.Assets; + +/** + * Note... not actually gaussian! + */ +class GaussianBlurShader extends FlxRuntimeShader +{ + public var amount:Float; + + public function new(amount:Float = 1.0) + { + super(Assets.getText(Paths.frag("gaussianBlur"))); + setAmount(amount); + } + + public function setAmount(value:Float):Void + { + this.amount = value; + this.setFloat("amount", amount); + } +} From b20cb27f828a57e7b6676782245ce94fe707220e Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 23 Aug 2023 05:33:52 -0400 Subject: [PATCH 59/74] hue shifting in progress --- assets | 2 +- hmm.json | 2 +- .../funkin/freeplayStuff/DifficultyStars.hx | 112 ++++++++++++++++ source/funkin/freeplayStuff/FreeplayFlames.hx | 120 ++++++++++++++++++ source/funkin/shaderslmfao/HSVShader.hx | 44 +++++++ 5 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 source/funkin/freeplayStuff/DifficultyStars.hx create mode 100644 source/funkin/freeplayStuff/FreeplayFlames.hx create mode 100644 source/funkin/shaderslmfao/HSVShader.hx diff --git a/assets b/assets index bc7e486ba..cb481d798 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit bc7e486ba4cf52e55893daa951e58b37059b0adb +Subproject commit cb481d79891707029a7f10975ce0d392c56f244a diff --git a/hmm.json b/hmm.json index 85c83ea6d..bb2832ba8 100644 --- a/hmm.json +++ b/hmm.json @@ -32,7 +32,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "a913635", + "ref": "37fd4ed45011063a38fdfe9bb6091671b3ac6c17", "url": "https://github.com/ninjamuffin99/flxanimate" }, { diff --git a/source/funkin/freeplayStuff/DifficultyStars.hx b/source/funkin/freeplayStuff/DifficultyStars.hx new file mode 100644 index 000000000..3078f0e9a --- /dev/null +++ b/source/funkin/freeplayStuff/DifficultyStars.hx @@ -0,0 +1,112 @@ +package funkin.freeplayStuff; + +import flixel.group.FlxSpriteGroup; +import funkin.graphics.adobeanimate.FlxAtlasSprite; +import funkin.shaderslmfao.HSVShader; + +class DifficultyStars extends FlxSpriteGroup +{ + /** + * Internal handler var for difficulty... ranges from 0... to 15 + * 0 is 1 star... 15 is 0 stars! + */ + var curDifficulty(default, set):Int = 0; + + /** + * Range between 0 and 15 + */ + public var difficulty(default, set):Int = 1; + + var stars:FlxAtlasSprite; + + var flames:FreeplayFlames; + + var hsvShader:HSVShader; + + public function new(x:Float, y:Float) + { + super(x, y); + + hsvShader = new HSVShader(); + + flames = new FreeplayFlames(0, 0); + add(flames); + + stars = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/freeplayStars")); + stars.anim.play("diff stars"); + add(stars); + + stars.shader = hsvShader; + + for (memb in flames.members) + memb.shader = hsvShader; + + FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ["hue", "saturation", "value"])); + FlxG.debugger.track(hsvShader); + + FlxG.debugger.addTrackerProfile(new TrackerProfile(DifficultyStars, ["difficulty"])); + FlxG.debugger.track(this); + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + + // "loops" the current animation + // for clarity, the animation file looks like + // frame : stars + // 0-99: 1 star + // 100-199: 2 stars + // ...... + // 1300-1499: 15 stars + // 1500 : 0 stars + if (curDifficulty < 15 && stars.anim.curFrame >= (curDifficulty + 1) * 100) + { + stars.anim.play("diff stars", true, false, curDifficulty * 100); + } + } + + function set_difficulty(value:Int):Int + { + difficulty = value; + + if (difficulty <= 0) + { + difficulty = 0; + curDifficulty = 15; + } + else if (difficulty <= 15) + { + difficulty = value; + curDifficulty = difficulty - 1; + } + else + { + difficulty = 15; + curDifficulty = difficulty - 1; + } + + if (difficulty > 10) flames.flameCount = difficulty - 10; + else + flames.flameCount = 0; + + return difficulty; + } + + function set_curDifficulty(value:Int):Int + { + curDifficulty = value; + if (curDifficulty == 15) + { + stars.anim.play("diff stars", true, false, 1500); + stars.anim.pause(); + } + else + { + stars.anim.curFrame = Std.int(curDifficulty * 100); + stars.anim.play("diff stars", true, false, curDifficulty * 100); + } + + return curDifficulty; + } +} diff --git a/source/funkin/freeplayStuff/FreeplayFlames.hx b/source/funkin/freeplayStuff/FreeplayFlames.hx new file mode 100644 index 000000000..cf606bdb8 --- /dev/null +++ b/source/funkin/freeplayStuff/FreeplayFlames.hx @@ -0,0 +1,120 @@ +package funkin.freeplayStuff; + +import flixel.group.FlxSpriteGroup; +import flixel.FlxSprite; +import flixel.util.FlxTimer; + +class FreeplayFlames extends FlxSpriteGroup +{ + var flameX(default, set):Float = 917; + var flameY(default, set):Float = 103; + var flameSpreadX(default, set):Float = 29; + var flameSpreadY(default, set):Float = 6; + + public var flameCount(default, set):Int = 0; + + var flameTimer:Float = 0.25; + + public function new(x:Float, y:Float) + { + super(x, y); + + for (i in 0...5) + { + var flame:FlxSprite = new FlxSprite(flameX + (flameSpreadX * i), flameY + (flameSpreadY * i)); + flame.frames = Paths.getSparrowAtlas("freeplay/freeplayFlame"); + flame.animation.addByPrefix("flame", "fire loop", FlxG.random.int(23, 25), false); + flame.animation.play("flame"); + flame.visible = false; + flameCount = 0; + + // sets the loop... maybe better way to do this lol! + flame.animation.finishCallback = function(_) { + flame.animation.play("flame", true, false, 2); + }; + add(flame); + } + + FlxG.debugger.addTrackerProfile(new TrackerProfile(FreeplayFlames, ["flameTimer"])); + FlxG.debugger.track(this); + } + + var properPositions:Bool = false; + + override public function update(elapsed:Float):Void + { + super.update(elapsed); + // doesn't work in create()/new() for some reason + // so putting it here bwah! + if (!properPositions) + { + setFlamePositions(); + properPositions = true; + } + } + + function set_flameCount(value:Int):Int + { + this.flameCount = value; + var visibleCount:Int = 0; + for (i in 0...5) + { + if (members[i] == null) continue; + var flame:FlxSprite = members[i]; + if (i < flameCount) + { + if (!flame.visible) + { + new FlxTimer().start(flameTimer * visibleCount, function(_) { + flame.animation.play("flame", true); + flame.visible = true; + }); + visibleCount++; + } + } + else + { + flame.visible = false; + } + } + return this.flameCount; + } + + function setFlamePositions() + { + for (i in 0...5) + { + var flame:FlxSprite = members[i]; + flame.x = flameX + (flameSpreadX * i); + flame.y = flameY + (flameSpreadY * i); + } + } + + function set_flameX(value:Float):Float + { + this.flameX = value; + setFlamePositions(); + return this.flameX; + } + + function set_flameY(value:Float):Float + { + this.flameY = value; + setFlamePositions(); + return this.flameY; + } + + function set_flameSpreadX(value:Float):Float + { + this.flameSpreadX = value; + setFlamePositions(); + return this.flameSpreadX; + } + + function set_flameSpreadY(value:Float):Float + { + this.flameSpreadY = value; + setFlamePositions(); + return this.flameSpreadY; + } +} diff --git a/source/funkin/shaderslmfao/HSVShader.hx b/source/funkin/shaderslmfao/HSVShader.hx new file mode 100644 index 000000000..066a49c96 --- /dev/null +++ b/source/funkin/shaderslmfao/HSVShader.hx @@ -0,0 +1,44 @@ +package funkin.shaderslmfao; + +import flixel.addons.display.FlxRuntimeShader; +import funkin.Paths; +import openfl.utils.Assets; + +class HSVShader extends FlxRuntimeShader +{ + public var hue(default, set):Float; + public var saturation(default, set):Float; + public var value(default, set):Float; + + public function new() + { + super(Assets.getText(Paths.frag('hsv'))); + hue = 1; + saturation = 1; + value = 1; + } + + function set_hue(value:Float):Float + { + this.setFloat('hue', value); + this.hue = value; + + return this.hue; + } + + function set_saturation(value:Float):Float + { + this.setFloat('sat', value); + this.saturation = value; + + return this.saturation; + } + + function set_value(value:Float):Float + { + this.setFloat('val', value); + this.value = value; + + return this.value; + } +} From f000d21689d7619f175c4bd2f589030a00e54c84 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 28 Aug 2023 14:52:03 -0400 Subject: [PATCH 60/74] shaders and blendmode overlay stuff in progres --- art | 2 +- assets | 2 +- source/funkin/FreeplayState.hx | 44 +++++++---- .../funkin/freeplayStuff/BGScrollingText.hx | 32 ++++++-- source/funkin/freeplayStuff/SongMenuItem.hx | 12 +++ .../funkin/shaderslmfao/BlendModesShader.hx | 23 ++++++ source/funkin/ui/title/FlxSpriteOverlay.hx | 74 +++++++++++++++++++ source/funkin/ui/title/TitleState.hx | 11 ++- 8 files changed, 175 insertions(+), 25 deletions(-) create mode 100644 source/funkin/shaderslmfao/BlendModesShader.hx create mode 100644 source/funkin/ui/title/FlxSpriteOverlay.hx diff --git a/art b/art index fbe23f8b5..e8d96feb8 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit fbe23f8b59831ca0123727a35e02cdfb49f3ce60 +Subproject commit e8d96feb8af616f360e68538b7347fae84d8308f diff --git a/assets b/assets index cb481d798..20553cc46 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit cb481d79891707029a7f10975ce0d392c56f244a +Subproject commit 20553cc4687ec975427b37eda80e74e381d9a2ef diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 99f12bcb0..1c263cf5f 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.shaderslmfao.HSVShader; import funkin.ui.StickerSubState; import flash.text.TextField; import flixel.FlxCamera; @@ -170,7 +171,7 @@ class FreeplayState extends MusicBeatSubState FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); add(pinkBack); - var orangeBackShit:FlxSprite = new FlxSprite(84, FlxG.height * 0.68).makeGraphic(Std.int(pinkBack.width), 50, 0xFFffd400); + var orangeBackShit:FlxSprite = new FlxSprite(84, 440).makeGraphic(Std.int(pinkBack.width), 75, 0xFFfeda00); add(orangeBackShit); var alsoOrangeLOL:FlxSprite = new FlxSprite(0, orangeBackShit.y).makeGraphic(100, Std.int(orangeBackShit.height), 0xFFffd400); @@ -192,21 +193,26 @@ class FreeplayState extends MusicBeatSubState add(grpTxtScrolls); grpTxtScrolls.visible = false; - var moreWays:BGScrollingText = new BGScrollingText(0, 200, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width); + FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ["x", "y", "speed", "size"])); + + var moreWays:BGScrollingText = new BGScrollingText(0, 160, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width, true, 43); moreWays.funnyColor = 0xFFfff383; - moreWays.speed = 4; + moreWays.speed = 6.8; grpTxtScrolls.add(moreWays); + FlxG.debugger.track(moreWays, "HotBlooded 1"); + exitMovers.set([moreWays], { x: FlxG.width * 2, speed: 0.4, }); - var funnyScroll:BGScrollingText = new BGScrollingText(0, 250, "BOYFRIEND", FlxG.width / 2); + var funnyScroll:BGScrollingText = new BGScrollingText(0, 220, "BOYFRIEND", FlxG.width / 2, false, 60); funnyScroll.funnyColor = 0xFFff9963; - funnyScroll.speed = -1; + funnyScroll.speed = -3.8; grpTxtScrolls.add(funnyScroll); + FlxG.debugger.track(funnyScroll, "Boyfriend 1"); exitMovers.set([funnyScroll], { @@ -216,18 +222,21 @@ class FreeplayState extends MusicBeatSubState wait: 0 }); - var txtNuts:BGScrollingText = new BGScrollingText(0, 300, "PROTECT YO NUTS", FlxG.width / 2); + var txtNuts:BGScrollingText = new BGScrollingText(0, 285, "PROTECT YO NUTS", FlxG.width / 2, true, 43); + txtNuts.speed = 3.5; grpTxtScrolls.add(txtNuts); exitMovers.set([txtNuts], { x: FlxG.width * 2, speed: 0.4, }); + FlxG.debugger.track(txtNuts, "Protect yo nuts 1"); - var funnyScroll2:BGScrollingText = new BGScrollingText(0, 340, "BOYFRIEND", FlxG.width / 2); + var funnyScroll2:BGScrollingText = new BGScrollingText(0, 335, "BOYFRIEND", FlxG.width / 2, false, 60); funnyScroll2.funnyColor = 0xFFff9963; - funnyScroll2.speed = -1.2; + funnyScroll2.speed = -3.8; grpTxtScrolls.add(funnyScroll2); + FlxG.debugger.track(funnyScroll2, "Boyfriend 2"); exitMovers.set([funnyScroll2], { @@ -235,10 +244,11 @@ class FreeplayState extends MusicBeatSubState speed: 0.5, }); - var moreWays2:BGScrollingText = new BGScrollingText(0, 400, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width); + var moreWays2:BGScrollingText = new BGScrollingText(0, 397, "HOT BLOODED IN MORE WAYS THAN ONE", FlxG.width, true, 43); moreWays2.funnyColor = 0xFFfff383; - moreWays2.speed = 4.4; + moreWays2.speed = 6.8; grpTxtScrolls.add(moreWays2); + FlxG.debugger.track(moreWays2, "HotBlooded 2"); exitMovers.set([moreWays2], { @@ -246,10 +256,11 @@ class FreeplayState extends MusicBeatSubState speed: 0.4 }); - var funnyScroll3:BGScrollingText = new BGScrollingText(0, orangeBackShit.y, "BOYFRIEND", FlxG.width / 2); - funnyScroll3.funnyColor = 0xFFff9963; - funnyScroll3.speed = -0.8; + var funnyScroll3:BGScrollingText = new BGScrollingText(0, orangeBackShit.y + 10, "BOYFRIEND", FlxG.width / 2, 60); + funnyScroll3.funnyColor = 0xFFfea400; + funnyScroll3.speed = -3.8; grpTxtScrolls.add(funnyScroll3); + FlxG.debugger.track(funnyScroll3, "Boyfriend 3"); exitMovers.set([funnyScroll3], { @@ -555,6 +566,11 @@ class FreeplayState extends MusicBeatSubState } } + var hsvShader:HSVShader = new HSVShader(); + + FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ["hue", "saturation", "value"])); + FlxG.debugger.track(hsvShader, "capsule shader"); + var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem); randomCapsule.init(FlxG.width, 0, "Random"); randomCapsule.onConfirm = function() { @@ -566,6 +582,7 @@ class FreeplayState extends MusicBeatSubState randomCapsule.songText.visible = false; randomCapsule.favIcon.visible = false; randomCapsule.initJumpIn(0, force); + randomCapsule.hsvShader = hsvShader; grpCapsules.add(randomCapsule); for (i in 0...tempSongs.length) @@ -580,6 +597,7 @@ class FreeplayState extends MusicBeatSubState funnyMenu.alpha = 0.5; funnyMenu.songText.visible = false; funnyMenu.favIcon.visible = tempSongs[i].isFav; + funnyMenu.hsvShader = hsvShader; // fp.updateScore(0); diff --git a/source/funkin/freeplayStuff/BGScrollingText.hx b/source/funkin/freeplayStuff/BGScrollingText.hx index 9fa6dd49b..586f83822 100644 --- a/source/funkin/freeplayStuff/BGScrollingText.hx +++ b/source/funkin/freeplayStuff/BGScrollingText.hx @@ -7,6 +7,7 @@ import flixel.math.FlxMath; import flixel.text.FlxText; import flixel.util.FlxColor; import flixel.util.FlxSort; +import flixel.util.FlxTimer; // its kinda like marqeee html lol! class BGScrollingText extends FlxSpriteGroup @@ -16,36 +17,53 @@ class BGScrollingText extends FlxSpriteGroup public var widthShit:Float = FlxG.width; public var placementOffset:Float = 20; public var speed:Float = 1; + public var size(default, set):Int = 48; public var funnyColor(default, set):Int = 0xFFFFFFFF; - public function new(x:Float, y:Float, text:String, widthShit:Float = 100) + public function new(x:Float, y:Float, text:String, widthShit:Float = 100, ?bold:Bool = false, ?size:Int = 48) { super(x, y); this.widthShit = widthShit; + if (size != null) this.size = size; grpTexts = new FlxTypedSpriteGroup<FlxText>(); add(grpTexts); - var testText:FlxText = new FlxText(0, 0, 0, text, 48); + var testText:FlxText = new FlxText(0, 0, 0, text, this.size); testText.font = "5by7"; + testText.bold = bold; testText.updateHitbox(); grpTexts.add(testText); - var needed:Int = Math.ceil(widthShit / testText.frameWidth); + var needed:Int = Math.ceil(widthShit / testText.frameWidth) + 1; for (i in 0...needed) { var lmfao:Int = i + 1; - var coolText:FlxText = new FlxText((lmfao * testText.frameWidth) + (lmfao * 20), 0, 0, text, 48); + var coolText:FlxText = new FlxText((lmfao * testText.frameWidth) + (lmfao * 20), 0, 0, text, this.size); + coolText.font = "5by7"; + coolText.bold = bold; coolText.updateHitbox(); grpTexts.add(coolText); } } + function set_size(value:Int):Int + { + if (grpTexts != null) + { + grpTexts.forEach(function(txt:FlxText) { + txt.size = value; + }); + } + this.size = value; + return value; + } + function set_funnyColor(col:Int):Int { grpTexts.forEach(function(txt) { @@ -55,7 +73,7 @@ class BGScrollingText extends FlxSpriteGroup return col; } - override function update(elapsed:Float) + override public function update(elapsed:Float) { for (txt in grpTexts.group) { @@ -66,14 +84,16 @@ class BGScrollingText extends FlxSpriteGroup if (txt.x < -txt.frameWidth) { txt.x = grpTexts.group.members[grpTexts.length - 1].x + grpTexts.group.members[grpTexts.length - 1].frameWidth + placementOffset; + sortTextShit(); } } else { - if (txt.x > widthShit) + if (txt.x > txt.frameWidth * 2) { txt.x = grpTexts.group.members[0].x - grpTexts.group.members[0].frameWidth - placementOffset; + sortTextShit(); } } diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 596dfbed4..0f0521004 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -1,5 +1,6 @@ package funkin.freeplayStuff; +import funkin.shaderslmfao.HSVShader; import funkin.shaderslmfao.GaussianBlurShader; import flixel.group.FlxGroup; import flixel.FlxSprite; @@ -41,6 +42,8 @@ class SongMenuItem extends FlxSpriteGroup public var onConfirm:Void->Void; public var diffGrayscale:Grayscale; + public var hsvShader(default, set):HSVShader; + public function new(x:Float, y:Float, song:String, ?character:String) { super(x, y); @@ -109,6 +112,15 @@ class SongMenuItem extends FlxSpriteGroup selected = selected; // just to kickstart the set_selected } + function set_hsvShader(value:HSVShader):HSVShader + { + this.hsvShader = value; + capsule.shader = hsvShader; + songText.shader = hsvShader; + + return value; + } + function textAppear() { songText.scale.x = 1.7; diff --git a/source/funkin/shaderslmfao/BlendModesShader.hx b/source/funkin/shaderslmfao/BlendModesShader.hx new file mode 100644 index 000000000..6807a65c0 --- /dev/null +++ b/source/funkin/shaderslmfao/BlendModesShader.hx @@ -0,0 +1,23 @@ +package funkin.shaderslmfao; + +import flixel.addons.display.FlxRuntimeShader; +import funkin.Paths; +import openfl.utils.Assets; +import openfl.display.BitmapData; + +class BlendModesShader extends FlxRuntimeShader +{ + public var camera:BitmapData; + + public function new() + { + super(Assets.getText(Paths.frag('blendModes'))); + } + + public function setCamera(camera:BitmapData):Void + { + this.camera = camera; + + this.setBitmapData('camera', camera); + } +} diff --git a/source/funkin/ui/title/FlxSpriteOverlay.hx b/source/funkin/ui/title/FlxSpriteOverlay.hx new file mode 100644 index 000000000..2e0ac987e --- /dev/null +++ b/source/funkin/ui/title/FlxSpriteOverlay.hx @@ -0,0 +1,74 @@ +package funkin.ui.title; + +import flixel.FlxSprite; +import funkin.shaderslmfao.BlendModesShader; +import openfl.display.BitmapData; +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.graphics.frames.FlxFrame.FlxFrameAngle; + +class FlxSpriteOverlay extends FlxSprite +{ + var blendShader:BlendModesShader; + var dipshitBitmap:BitmapData; + var temp:FlxSprite; + + public function new(x:Float, y:Float) + { + super(x, y); + temp = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, 0xFF000000); + blendShader = new BlendModesShader(); + dipshitBitmap = new BitmapData(2180, 1720, true, 0xFFCC00CC); + } + + override function drawComplex(camera:FlxCamera):Void + { + _frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY()); + _matrix.translate(-origin.x, -origin.y); + _matrix.scale(scale.x, scale.y); + if (bakedRotationAngle <= 0) + { + updateTrig(); + if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle); + } + getScreenPosition(_point, camera).subtractPoint(offset); + _point.add(origin.x, origin.y); + _matrix.translate(_point.x, _point.y); + if (isPixelPerfectRender(camera)) + { + _matrix.tx = Math.floor(_matrix.tx); + _matrix.ty = Math.floor(_matrix.ty); + } + + var sprRect = getScreenBounds(); + + dipshitBitmap.draw(camera.canvas, camera.canvas.transform.matrix); + blendShader.setCamera(dipshitBitmap); + + FlxG.bitmapLog.add(dipshitBitmap); + + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, blendShader); + } + + function copyToFlash(rect):openfl.geom.Rectangle + { + var flashRect = new openfl.geom.Rectangle(); + flashRect.x = rect.x; + flashRect.y = rect.y; + flashRect.width = rect.width; + flashRect.height = rect.height; + return flashRect; + } + + override public function isSimpleRender(?camera:FlxCamera):Bool + { + if (FlxG.renderBlit) + { + return super.isSimpleRender(camera); + } + else + { + return false; + } + } +} diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 313c578a3..9820e4ecc 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -23,6 +23,7 @@ import openfl.events.MouseEvent; import openfl.events.NetStatusEvent; import openfl.media.Video; import openfl.net.NetStream; +import openfl.display.BlendMode; #if desktop #end @@ -101,7 +102,7 @@ class TitleState extends MusicBeatState var logoBl:FlxSprite; var outlineShaderShit:TitleOutline; - var gfDance:FlxSprite; + var gfDance:FlxSpriteOverlay; var danceLeft:Bool = false; var titleText:FlxSprite; var maskShader = new LeftMaskShader(); @@ -124,13 +125,11 @@ class TitleState extends MusicBeatState outlineShaderShit = new TitleOutline(); - gfDance = new FlxSprite(FlxG.width * 0.4, FlxG.height * 0.07); + gfDance = new FlxSpriteOverlay(FlxG.width * 0.4, FlxG.height * 0.07); gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle'); gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false); gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false); - add(gfDance); - // maskShader.swagSprX = gfDance.x; // maskShader.swagMaskX = gfDance.x + 200; // maskShader.frameUV = gfDance.frame.uv; @@ -142,6 +141,8 @@ class TitleState extends MusicBeatState add(logoBl); + add(gfDance); + titleText = new FlxSprite(100, FlxG.height * 0.8); titleText.frames = Paths.getSparrowAtlas('titleEnter'); titleText.animation.addByPrefix('idle', "Press Enter to Begin", 24); @@ -245,6 +246,8 @@ class TitleState extends MusicBeatState override function update(elapsed:Float) { + FlxG.bitmapLog.add(FlxG.camera.buffer); + #if HAS_PITCH if (FlxG.keys.pressed.UP) FlxG.sound.music.pitch += 0.5 * elapsed; From 5ddc4876bcc1f2022b8a8696ac74142e57dcfa7a Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 28 Aug 2023 22:01:25 -0400 Subject: [PATCH 61/74] boyfriend animations swapped to atlas sprites --- art | 2 +- assets | 2 +- source/funkin/FreeplayState.hx | 13 +--- source/funkin/freeplayStuff/DJBoyfriend.hx | 76 ++++++++++++------- .../funkin/freeplayStuff/DifficultyStars.hx | 6 -- source/funkin/freeplayStuff/FreeplayFlames.hx | 3 - source/funkin/ui/title/FlxSpriteOverlay.hx | 8 +- 7 files changed, 56 insertions(+), 54 deletions(-) diff --git a/art b/art index e8d96feb8..769b84ffd 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit e8d96feb8af616f360e68538b7347fae84d8308f +Subproject commit 769b84ffd7550b18fb3719cf4cb6b48296bdd097 diff --git a/assets b/assets index 20553cc46..2d4cd8512 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 20553cc4687ec975427b37eda80e74e381d9a2ef +Subproject commit 2d4cd85129db9aea8cdba62ef43313f99f13c857 diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 1c263cf5f..9447488ff 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -200,8 +200,6 @@ class FreeplayState extends MusicBeatSubState moreWays.speed = 6.8; grpTxtScrolls.add(moreWays); - FlxG.debugger.track(moreWays, "HotBlooded 1"); - exitMovers.set([moreWays], { x: FlxG.width * 2, @@ -212,7 +210,6 @@ class FreeplayState extends MusicBeatSubState funnyScroll.funnyColor = 0xFFff9963; funnyScroll.speed = -3.8; grpTxtScrolls.add(funnyScroll); - FlxG.debugger.track(funnyScroll, "Boyfriend 1"); exitMovers.set([funnyScroll], { @@ -230,13 +227,11 @@ class FreeplayState extends MusicBeatSubState x: FlxG.width * 2, speed: 0.4, }); - FlxG.debugger.track(txtNuts, "Protect yo nuts 1"); var funnyScroll2:BGScrollingText = new BGScrollingText(0, 335, "BOYFRIEND", FlxG.width / 2, false, 60); funnyScroll2.funnyColor = 0xFFff9963; funnyScroll2.speed = -3.8; grpTxtScrolls.add(funnyScroll2); - FlxG.debugger.track(funnyScroll2, "Boyfriend 2"); exitMovers.set([funnyScroll2], { @@ -248,7 +243,6 @@ class FreeplayState extends MusicBeatSubState moreWays2.funnyColor = 0xFFfff383; moreWays2.speed = 6.8; grpTxtScrolls.add(moreWays2); - FlxG.debugger.track(moreWays2, "HotBlooded 2"); exitMovers.set([moreWays2], { @@ -260,7 +254,6 @@ class FreeplayState extends MusicBeatSubState funnyScroll3.funnyColor = 0xFFfea400; funnyScroll3.speed = -3.8; grpTxtScrolls.add(funnyScroll3); - FlxG.debugger.track(funnyScroll3, "Boyfriend 3"); exitMovers.set([funnyScroll3], { @@ -268,7 +261,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.3 }); - dj = new DJBoyfriend(0, -100); + // dj = new DJBoyfriend(0, -100); + dj = new DJBoyfriend(640, 366); exitMovers.set([dj], { x: -dj.width * 1.6, @@ -568,9 +562,6 @@ class FreeplayState extends MusicBeatSubState var hsvShader:HSVShader = new HSVShader(); - FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ["hue", "saturation", "value"])); - FlxG.debugger.track(hsvShader, "capsule shader"); - var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem); randomCapsule.init(FlxG.width, 0, "Random"); randomCapsule.onConfirm = function() { diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 5bee4129a..1a7d9b9ed 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -3,8 +3,9 @@ package funkin.freeplayStuff; import flixel.FlxSprite; import flixel.util.FlxSignal; import funkin.util.assets.FlxAnimationUtil; +import funkin.graphics.adobeanimate.FlxAtlasSprite; -class DJBoyfriend extends FlxSprite +class DJBoyfriend extends FlxAtlasSprite { // Represents the sprite's current status. // Without state machines I would have driven myself crazy years ago. @@ -27,13 +28,31 @@ class DJBoyfriend extends FlxSprite public function new(x:Float, y:Float) { - super(x, y); + super(x, y, Paths.animateAtlas("freeplay/freeplay-boyfriend", "preload")); animOffsets = new Map<String, Array<Dynamic>>(); setupAnimations(); + trace(listAnimations()); - animation.finishCallback = onFinishAnim; + FlxG.debugger.track(this); + + anim.onComplete = onFinishAnim; + } + + /* + [remote hand under,boyfriend top head,brim piece,arm cringe l,red lazer,dj arm in,bf fist pump arm,hand raised right,forearm left,fist shaking,bf smile eyes closed face,arm cringe r,bf clenched face,face shrug,boyfriend falling,blue tint 1,shirt sleeve,bf clenched fist,head BF relaxed,blue tint 2,hand down left,blue tint 3,blue tint 4,head less smooshed,blue tint 5,boyfriend freeplay,BF head slight turn,blue tint 6,arm shrug l,blue tint 7,shoulder raised w sleeve,blue tint 8,fist pump face,blue tint 9,foot rested light,hand turnaround,arm chill right,Boyfriend DJ,arm shrug r,head back bf,hat top piece,dad bod,face surprise snap,Boyfriend DJ fist pump,office chair,foot rested right,chest down,office chair upright,body chill,bf dj afk,head mouth open dad,BF Head defalt HAIR BLOWING,hand shrug l,face piece,foot wag,turn table,shoulder up left,turntable lights,boyfriend dj body shirt blowing,body chunk turned,hand down right,dj arm out,hand shrug r,body chest out,rave hand,palm,chill face default,head back semi bf,boyfriend bottom head,DJ arm,shoulder right dad,bf surprise,boyfriend dj body,hs1,Boyfriend DJ watchin tv OG,spinning disk,hs2,arm chill left,boyfriend dj intro,hs3,hs4,chill face extra,hs5,remote hand upright,hs6,pant over table,face surprise,bf arm peace,arm turnaround,bf eyes 1,arm slammed table,eye squit,leg BF,head mid piece,arm backing,arm swoopin in,shoe right lowering,forearm right,hand out,blue tint 10,body falling back,remote thumb press,shoulder,hair spike single,bf bent + arm,crt,foot raised right,dad hand,chill face 1,chill face 2,clenched fist,head SMOOSHED,shoulder left dad,df1,body chunk upright,df2,df3,df4,hat front piece,df5,foot rested right 2,hand in,arm spun,shoe raised left,bf 1 finger hand,bf mouth 1,Boyfriend DJ confirm,forearm down ,hand raised left,remote thumb up] + */ + override public function listAnimations():Array<String> + { + var anims:Array<String> = []; + @:privateAccess + for (animKey in anim.symbolDictionary) + { + anims.push(animKey.name); + } + return anims; } public override function update(elapsed:Float):Void @@ -44,11 +63,11 @@ class DJBoyfriend extends FlxSprite { case Intro: // Play the intro animation then leave this state immediately. - if (getCurrentAnimation() != 'intro') playAnimation('intro', true); + if (getCurrentAnimation() != 'boyfriend dj intro') playFlashAnimation('boyfriend dj intro', true); timeSinceSpook = 0; case Idle: // We are in this state the majority of the time. - if (getCurrentAnimation() != 'idle' || animation.finished) + if (getCurrentAnimation() != 'Boyfriend DJ' || anim.finished) { if (timeSinceSpook > SPOOK_PERIOD) { @@ -56,18 +75,18 @@ class DJBoyfriend extends FlxSprite } else { - playAnimation('idle', false); + playFlashAnimation('Boyfriend DJ', false); } } timeSinceSpook += elapsed; case Confirm: - if (getCurrentAnimation() != 'confirm') playAnimation('confirm', false); + if (getCurrentAnimation() != 'Boyfriend DJ confirm') playFlashAnimation('Boyfriend DJ confirm', false); timeSinceSpook = 0; case Spook: - if (getCurrentAnimation() != 'spook') + if (getCurrentAnimation() != 'bf dj afk') { onSpook.dispatch(); - playAnimation('spook', false); + playFlashAnimation('bf dj afk', false); } timeSinceSpook = 0; default: @@ -75,20 +94,21 @@ class DJBoyfriend extends FlxSprite } } - function onFinishAnim(name:String):Void + function onFinishAnim():Void { + var name = anim.curSymbol.name; switch (name) { - case "intro": + case "boyfriend dj intro": // trace('Finished intro'); currentState = Idle; onIntroDone.dispatch(); - case "idle": + case "Boyfriend DJ": // trace('Finished idle'); - case "spook": + case "bf dj afk": // trace('Finished spook'); currentState = Idle; - case "confirm": + case "Boyfriend DJ confirm": // trace('Finished confirm'); } } @@ -100,19 +120,19 @@ class DJBoyfriend extends FlxSprite function setupAnimations():Void { - frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk')); + // frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk')); - animation.addByPrefix('intro', "boyfriend dj intro", 24, false); - addOffset('intro', 0, 0); + // animation.addByPrefix('intro', "boyfriend dj intro", 24, false); + addOffset('boyfriend dj intro', 8, 3); - animation.addByPrefix('idle', "Boyfriend DJ0", 24, false); - addOffset('idle', -4, -426); + // animation.addByPrefix('idle', "Boyfriend DJ0", 24, false); + addOffset('Boyfriend DJ', 0, 0); - animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24, false); - addOffset('confirm', 40, -451); + // animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24, false); + addOffset('Boyfriend DJ confirm', 0, 0); - animation.addByPrefix('spook', "bf dj afk0", 24, false); - addOffset('spook', -3, -272); + // animation.addByPrefix('spook', "bf dj afk0", 24, false); + addOffset('bf dj afk', 0, 0); } public function confirm():Void @@ -125,15 +145,15 @@ class DJBoyfriend extends FlxSprite animOffsets[name] = [x, y]; } - public function getCurrentAnimation():String + override public function getCurrentAnimation():String { - if (this.animation == null || this.animation.curAnim == null) return ""; - return this.animation.curAnim.name; + if (this.anim == null || this.anim.curSymbol == null) return ""; + return this.anim.curSymbol.name; } - public function playAnimation(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void + public function playFlashAnimation(id:String, ?Force:Bool = false, ?Reverse:Bool = false, ?Frame:Int = 0):Void { - animation.play(AnimName, Force, Reversed, Frame); + anim.play(id, Force, Reverse, Frame); applyAnimOffset(); } diff --git a/source/funkin/freeplayStuff/DifficultyStars.hx b/source/funkin/freeplayStuff/DifficultyStars.hx index 3078f0e9a..cc98fb07b 100644 --- a/source/funkin/freeplayStuff/DifficultyStars.hx +++ b/source/funkin/freeplayStuff/DifficultyStars.hx @@ -40,12 +40,6 @@ class DifficultyStars extends FlxSpriteGroup for (memb in flames.members) memb.shader = hsvShader; - - FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ["hue", "saturation", "value"])); - FlxG.debugger.track(hsvShader); - - FlxG.debugger.addTrackerProfile(new TrackerProfile(DifficultyStars, ["difficulty"])); - FlxG.debugger.track(this); } override function update(elapsed:Float):Void diff --git a/source/funkin/freeplayStuff/FreeplayFlames.hx b/source/funkin/freeplayStuff/FreeplayFlames.hx index cf606bdb8..8f54d210b 100644 --- a/source/funkin/freeplayStuff/FreeplayFlames.hx +++ b/source/funkin/freeplayStuff/FreeplayFlames.hx @@ -34,9 +34,6 @@ class FreeplayFlames extends FlxSpriteGroup }; add(flame); } - - FlxG.debugger.addTrackerProfile(new TrackerProfile(FreeplayFlames, ["flameTimer"])); - FlxG.debugger.track(this); } var properPositions:Bool = false; diff --git a/source/funkin/ui/title/FlxSpriteOverlay.hx b/source/funkin/ui/title/FlxSpriteOverlay.hx index 2e0ac987e..ddf58bbfd 100644 --- a/source/funkin/ui/title/FlxSpriteOverlay.hx +++ b/source/funkin/ui/title/FlxSpriteOverlay.hx @@ -42,12 +42,12 @@ class FlxSpriteOverlay extends FlxSprite var sprRect = getScreenBounds(); - dipshitBitmap.draw(camera.canvas, camera.canvas.transform.matrix); - blendShader.setCamera(dipshitBitmap); + // dipshitBitmap.draw(camera.canvas, camera.canvas.transform.matrix); + // blendShader.setCamera(dipshitBitmap); - FlxG.bitmapLog.add(dipshitBitmap); + // FlxG.bitmapLog.add(dipshitBitmap); - camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, blendShader); + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader); } function copyToFlash(rect):openfl.geom.Rectangle From 82bb619864246c6e7f9ddeed23786464c0ebdec7 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Mon, 28 Aug 2023 22:47:18 -0400 Subject: [PATCH 62/74] boyfriend tv looping --- source/funkin/freeplayStuff/DJBoyfriend.hx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 1a7d9b9ed..99981c8fa 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -89,6 +89,8 @@ class DJBoyfriend extends FlxAtlasSprite playFlashAnimation('bf dj afk', false); } timeSinceSpook = 0; + case TV: + if (getCurrentAnimation() != 'Boyfriend DJ watchin tv OG') playFlashAnimation('Boyfriend DJ watchin tv OG', true); default: // I shit myself. } @@ -109,6 +111,9 @@ class DJBoyfriend extends FlxAtlasSprite // trace('Finished spook'); currentState = Idle; case "Boyfriend DJ confirm": + + case "Boyfriend DJ watchin tv OG": + anim.play("Boyfriend DJ watchin tv OG", true, false, 112); // trace('Finished confirm'); } } @@ -176,4 +181,5 @@ enum DJBoyfriendState Idle; Confirm; Spook; + TV; } From 66f77a985fa8a6e5be6d138cff5d79dbc58e17be Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 29 Aug 2023 00:09:48 -0400 Subject: [PATCH 63/74] boyfriend blink code --- source/funkin/freeplayStuff/DJBoyfriend.hx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 99981c8fa..7292f1dcb 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -21,7 +21,10 @@ class DJBoyfriend extends FlxAtlasSprite // TODO: Switch this class to use SwagSprite instead. public var animOffsets:Map<String, Array<Dynamic>>; - static final SPOOK_PERIOD:Float = 180.0; + var gotSpooked:Bool = false; + + static final SPOOK_PERIOD:Float = 120.0; + static final TV_PERIOD:Float = 180.0; // Time since dad last SPOOKED you. var timeSinceSpook:Float = 0; @@ -36,8 +39,13 @@ class DJBoyfriend extends FlxAtlasSprite trace(listAnimations()); FlxG.debugger.track(this); + FlxG.console.registerObject("dj", this); anim.onComplete = onFinishAnim; + + FlxG.console.registerFunction("tv", function() { + currentState = TV; + }); } /* @@ -69,10 +77,14 @@ class DJBoyfriend extends FlxAtlasSprite // We are in this state the majority of the time. if (getCurrentAnimation() != 'Boyfriend DJ' || anim.finished) { - if (timeSinceSpook > SPOOK_PERIOD) + if (timeSinceSpook > SPOOK_PERIOD && !gotSpooked) { currentState = Spook; } + else if (timeSinceSpook > TV_PERIOD) + { + currentState = TV; + } else { playFlashAnimation('Boyfriend DJ', false); @@ -91,6 +103,7 @@ class DJBoyfriend extends FlxAtlasSprite timeSinceSpook = 0; case TV: if (getCurrentAnimation() != 'Boyfriend DJ watchin tv OG') playFlashAnimation('Boyfriend DJ watchin tv OG', true); + timeSinceSpook = 0; default: // I shit myself. } @@ -113,7 +126,8 @@ class DJBoyfriend extends FlxAtlasSprite case "Boyfriend DJ confirm": case "Boyfriend DJ watchin tv OG": - anim.play("Boyfriend DJ watchin tv OG", true, false, 112); + var frame:Int = FlxG.random.bool(33) ? 112 : 166; + anim.play("Boyfriend DJ watchin tv OG", true, false, frame); // trace('Finished confirm'); } } From ab5d6d2f485898257f16e6ef930348b315fcc901 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 29 Aug 2023 03:11:40 -0400 Subject: [PATCH 64/74] sounds in progress --- art | 2 +- assets | 2 +- hmm.json | 2 +- source/funkin/freeplayStuff/DJBoyfriend.hx | 31 ++++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/art b/art index 769b84ffd..61f8e43cf 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 769b84ffd7550b18fb3719cf4cb6b48296bdd097 +Subproject commit 61f8e43cf782ad480cd9ed29cd982562becb59a5 diff --git a/assets b/assets index 2d4cd8512..096b42fb7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2d4cd85129db9aea8cdba62ef43313f99f13c857 +Subproject commit 096b42fb7d263752cd39ab76ef0cc1bcb391ea51 diff --git a/hmm.json b/hmm.json index bb2832ba8..e830ff435 100644 --- a/hmm.json +++ b/hmm.json @@ -32,7 +32,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "37fd4ed45011063a38fdfe9bb6091671b3ac6c17", + "ref": "dd2903f", "url": "https://github.com/ninjamuffin99/flxanimate" }, { diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 7292f1dcb..e6e66e876 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -4,6 +4,7 @@ import flixel.FlxSprite; import flixel.util.FlxSignal; import funkin.util.assets.FlxAnimationUtil; import funkin.graphics.adobeanimate.FlxAtlasSprite; +import flixel.system.FlxSound; class DJBoyfriend extends FlxAtlasSprite { @@ -35,6 +36,15 @@ class DJBoyfriend extends FlxAtlasSprite animOffsets = new Map<String, Array<Dynamic>>(); + anim.callback = function(name, number) { + switch (name) + { + case "Boyfriend DJ watchin tv OG": + if (number == 85) runTvLogic(); + default: + } + }; + setupAnimations(); trace(listAnimations()); @@ -127,6 +137,11 @@ class DJBoyfriend extends FlxAtlasSprite case "Boyfriend DJ watchin tv OG": var frame:Int = FlxG.random.bool(33) ? 112 : 166; + if (FlxG.random.bool(10)) + { + frame = 60; + // boyfriend switches channel code? + } anim.play("Boyfriend DJ watchin tv OG", true, false, frame); // trace('Finished confirm'); } @@ -154,6 +169,22 @@ class DJBoyfriend extends FlxAtlasSprite addOffset('bf dj afk', 0, 0); } + var cartoonSnd:FlxSound; + + public function runTvLogic() + { + if (cartoonSnd == null) + { + // cartoonSnd = new FlxSound(); + // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); + // FlxG.sound.defaultSoundGroup.add(cartoonSnd); + // cartoonSnd.play(); + } + + // play sound of random flash toon + // if tv is already playing, play a new one + } + public function confirm():Void { currentState = Confirm; From 8f9206f6394b2af57501dc1ac8956393d40fb547 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 29 Aug 2023 05:37:24 -0400 Subject: [PATCH 65/74] python file to generate shitty audio --- .gitignore | 3 ++- art | 2 +- source/funkin/freeplayStuff/DJBoyfriend.hx | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d4aba58ac..b2fe731ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ APIStuff.hx dump/ export/ -RECOVER_*.fla \ No newline at end of file +RECOVER_*.fla +shitAudio/ diff --git a/art b/art index 61f8e43cf..d1aa2c6e8 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 61f8e43cf782ad480cd9ed29cd982562becb59a5 +Subproject commit d1aa2c6e81c0ddff8af3d6aac4700590cc5b0ef4 diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index e6e66e876..c4524bcfc 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -176,10 +176,10 @@ class DJBoyfriend extends FlxAtlasSprite if (cartoonSnd == null) { // cartoonSnd = new FlxSound(); - // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); // FlxG.sound.defaultSoundGroup.add(cartoonSnd); - // cartoonSnd.play(); } + // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); + // cartoonSnd.play(); // play sound of random flash toon // if tv is already playing, play a new one From 8c74fcd73ab474c4f24551f00277afebe12492c3 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 30 Aug 2023 20:46:30 -0400 Subject: [PATCH 66/74] playingCartoon bool woops --- source/funkin/freeplayStuff/DJBoyfriend.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index c4524bcfc..19f0ac958 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -171,6 +171,8 @@ class DJBoyfriend extends FlxAtlasSprite var cartoonSnd:FlxSound; + var playingCartoon:Bool = false; + public function runTvLogic() { if (cartoonSnd == null) From 546e15dcac213c68b86803176d9a5af60eedc3ad Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 30 Aug 2023 20:50:07 -0400 Subject: [PATCH 67/74] click polish --- source/funkin/freeplayStuff/DJBoyfriend.hx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 19f0ac958..3fa1d206e 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -171,14 +171,24 @@ class DJBoyfriend extends FlxAtlasSprite var cartoonSnd:FlxSound; - var playingCartoon:Bool = false; + public var playingCartoon:Bool = false; public function runTvLogic() { if (cartoonSnd == null) { - // cartoonSnd = new FlxSound(); - // FlxG.sound.defaultSoundGroup.add(cartoonSnd); + // tv is OFF, but getting turned on + FlxG.sound.play(Paths.sound('tv_on')); + + cartoonSnd = new FlxSound(); + FlxG.sound.defaultSoundGroup.add(cartoonSnd); + } + else + { + // plays it smidge after the click + new FlxTimer().start(0.5, 1, function(_) { + FlxG.sound.play(Paths.sound('channel_switch')); + }); } // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); // cartoonSnd.play(); From ba896508cc9f47316ead50c116d6b60c9c8abfac Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Thu, 31 Aug 2023 01:56:35 -0400 Subject: [PATCH 68/74] cartoons and stuff --- assets | 2 +- source/funkin/audio/FlxStreamSound.hx | 49 ++++++++++++++++++++++ source/funkin/freeplayStuff/DJBoyfriend.hx | 31 +++++++++++--- 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 source/funkin/audio/FlxStreamSound.hx diff --git a/assets b/assets index 096b42fb7..386dcac52 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 096b42fb7d263752cd39ab76ef0cc1bcb391ea51 +Subproject commit 386dcac52e7b8ddc8cc4ad3864bd8d51f01564d1 diff --git a/source/funkin/audio/FlxStreamSound.hx b/source/funkin/audio/FlxStreamSound.hx new file mode 100644 index 000000000..a572ad436 --- /dev/null +++ b/source/funkin/audio/FlxStreamSound.hx @@ -0,0 +1,49 @@ +package funkin.audio; + +import flash.media.Sound; +#if flash11 +import flash.utils.ByteArray; +#end +import flixel.sound.FlxSound; +import flixel.system.FlxAssets.FlxSoundAsset; +import openfl.Assets; +#if (openfl >= "8.0.0") +import openfl.utils.AssetType; +#end + +/** + * a FlxSound that just overrides loadEmbedded to allow for "streamed" sounds to load with better performance! + */ +class FlxStreamSound extends FlxSound +{ + public function new() + { + super(); + } + + override public function loadEmbedded(EmbeddedSound:FlxSoundAsset, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound + { + if (EmbeddedSound == null) return this; + + cleanup(true); + + if ((EmbeddedSound is Sound)) + { + _sound = EmbeddedSound; + } + else if ((EmbeddedSound is Class)) + { + _sound = Type.createInstance(EmbeddedSound, []); + } + else if ((EmbeddedSound is String)) + { + if (Assets.exists(EmbeddedSound, AssetType.SOUND) + || Assets.exists(EmbeddedSound, AssetType.MUSIC)) _sound = Assets.getMusic(EmbeddedSound); + else + FlxG.log.error('Could not find a Sound asset with an ID of \'$EmbeddedSound\'.'); + } + + // NOTE: can't pull ID3 info from embedded sound currently + return init(Looped, AutoDestroy, OnComplete); + } +} diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 3fa1d206e..58318df15 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -5,6 +5,8 @@ import flixel.util.FlxSignal; import funkin.util.assets.FlxAnimationUtil; import funkin.graphics.adobeanimate.FlxAtlasSprite; import flixel.system.FlxSound; +import flixel.util.FlxTimer; +import funkin.audio.FlxStreamSound; class DJBoyfriend extends FlxAtlasSprite { @@ -169,7 +171,7 @@ class DJBoyfriend extends FlxAtlasSprite addOffset('bf dj afk', 0, 0); } - var cartoonSnd:FlxSound; + var cartoonSnd:FlxStreamSound; public var playingCartoon:Bool = false; @@ -180,21 +182,40 @@ class DJBoyfriend extends FlxAtlasSprite // tv is OFF, but getting turned on FlxG.sound.play(Paths.sound('tv_on')); - cartoonSnd = new FlxSound(); + cartoonSnd = new FlxStreamSound(); FlxG.sound.defaultSoundGroup.add(cartoonSnd); } else { // plays it smidge after the click - new FlxTimer().start(0.5, 1, function(_) { + new FlxTimer().start(0.1, function(_) { FlxG.sound.play(Paths.sound('channel_switch')); }); } // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); // cartoonSnd.play(); - // play sound of random flash toon - // if tv is already playing, play a new one + loadCartoon(); + } + + function loadCartoon() + { + cartoonSnd.loadEmbedded(Paths.sound(getRandomFlashToon()), false, false, function() { + anim.play("Boyfriend DJ watchin tv OG", true, false, 60); + }); + cartoonSnd.play(true, FlxG.random.float(0, cartoonSnd.length)); + } + + var cartoonList:Array<String> = Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/")); + + function getRandomFlashToon():String + { + var randomFile = FlxG.random.getObject(cartoonList); + + randomFile = randomFile.replace("assets/sounds/", ""); + randomFile = randomFile.substring(0, randomFile.length - 4); + + return randomFile; } public function confirm():Void From 387acd211e47968c2407b18442800c89d24bd5cf Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 19 Sep 2023 01:31:54 -0400 Subject: [PATCH 69/74] icon scaling fix + removed temp lighting --- source/funkin/freeplayStuff/SongMenuItem.hx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 0f0521004..60af8d0f2 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -178,9 +178,8 @@ class SongMenuItem extends FlxSpriteGroup } pixelIcon.loadGraphic(Paths.image(charPath)); - pixelIcon.setGraphicSize(Std.int(pixelIcon.width * 2)); - pixelIcon.updateHitbox(); - pixelIcon.origin.x += 100; + pixelIcon.scale.x = pixelIcon.scale.y = 2; + pixelIcon.origin.x = 100; // pixelIcon.origin.x = capsule.origin.x; // pixelIcon.offset.x -= pixelIcon.origin.x; } From 4b1af216d4a86e354ec8c33c6a0139bcdc3397f7 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 19 Sep 2023 19:10:30 -0400 Subject: [PATCH 70/74] capsule alpha stuff in progress --- source/funkin/FreeplayState.hx | 7 +++---- source/funkin/freeplayStuff/SongMenuItem.hx | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 9447488ff..6d8c24bbf 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -585,11 +585,10 @@ class FreeplayState extends MusicBeatSubState funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; funnyMenu.targetPos.x = funnyMenu.x; funnyMenu.ID = i; - funnyMenu.alpha = 0.5; + funnyMenu.capsule.alpha = 0.5; funnyMenu.songText.visible = false; funnyMenu.favIcon.visible = tempSongs[i].isFav; funnyMenu.hsvShader = hsvShader; - // fp.updateScore(0); if (i < 8) funnyMenu.initJumpIn(Math.min(i, 4), force); @@ -943,7 +942,7 @@ class FreeplayState extends MusicBeatSubState function capsuleOnConfirmDefault():Void { - var poop:String = songs[curSelected].songName.toLowerCase(); + // var poop:String = songs[curSelected].songName.toLowerCase(); // does not work properly, always just accidentally sets it to normal anyways! /* if (!Assets.exists(Paths.json(songs[curSelected].songName + '/' + poop))) @@ -1035,7 +1034,7 @@ class FreeplayState extends MusicBeatSubState { index += 1; - capsule.selected = false; + capsule.selected = true; capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected))); diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 60af8d0f2..6301f14c4 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -15,10 +15,11 @@ import funkin.shaderslmfao.Grayscale; class SongMenuItem extends FlxSpriteGroup { - var capsule:FlxSprite; + public var capsule:FlxSprite; + var pixelIcon:FlxSprite; - public var selected(default, set):Bool = false; + public var selected(default, set):Bool; public var songTitle:String = "Test"; @@ -108,8 +109,6 @@ class SongMenuItem extends FlxSpriteGroup // grpHide.add(favIcon); setVisibleGrp(false); - - selected = selected; // just to kickstart the set_selected } function set_hsvShader(value:HSVShader):HSVShader @@ -144,6 +143,8 @@ class SongMenuItem extends FlxSpriteGroup } if (value) textAppear(); + + selectedAlpha(); } public function init(x:Float, y:Float, song:String, ?character:String) @@ -210,14 +211,16 @@ class SongMenuItem extends FlxSpriteGroup if (force) { - alpha = 1; + visible = true; + capsule.alpha = 1; setVisibleGrp(true); } else { new FlxTimer().start((xFrames.length / 24) * 2.5, function(_) { + visible = true; + capsule.alpha = 1; setVisibleGrp(true); - alpha = 1; }); } } @@ -226,7 +229,9 @@ class SongMenuItem extends FlxSpriteGroup public function forcePosition() { - alpha = 1; + visible = true; + capsule.alpha = 1; + selectedAlpha(); doLerp = true; doJumpIn = false; doJumpOut = false; @@ -303,6 +308,7 @@ class SongMenuItem extends FlxSpriteGroup function set_selected(value:Bool):Bool { + trace("set_selected: " + value); // cute one liners, lol! diffGrayscale.setAmount(value ? 0 : 0.8); songText.alpha = value ? 1 : 0.6; From 9926706e6e9c72ba70008db456bbb6d3376dc1d9 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Tue, 19 Sep 2023 23:27:07 -0400 Subject: [PATCH 71/74] litty fix --- source/funkin/FreeplayState.hx | 6 +++++- source/funkin/freeplayStuff/SongMenuItem.hx | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 6d8c24bbf..0f9615927 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -426,6 +426,7 @@ class FreeplayState extends MusicBeatSubState dj.onIntroDone.add(function() { // when boyfriend hits dat shiii // + albumArt.visible = true; albumArt.anim.play(""); albumArt.anim.onComplete = function() { @@ -468,6 +469,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(1.5 / 24, function(bold) { sillyStroke.width = 0; sillyStroke.height = 0; + changeSelection(); }); }); @@ -598,6 +600,8 @@ class FreeplayState extends MusicBeatSubState grpCapsules.add(funnyMenu); } + FlxG.console.registerFunction("changeSelection", changeSelection); + changeSelection(); changeDiff(); } @@ -1034,7 +1038,7 @@ class FreeplayState extends MusicBeatSubState { index += 1; - capsule.selected = true; + capsule.selected = index == curSelected + 1; capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.x = 270 + (60 * (Math.sin(index - curSelected))); diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 6301f14c4..3518e96ce 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -308,7 +308,6 @@ class SongMenuItem extends FlxSpriteGroup function set_selected(value:Bool):Bool { - trace("set_selected: " + value); // cute one liners, lol! diffGrayscale.setAmount(value ? 0 : 0.8); songText.alpha = value ? 1 : 0.6; From 9446f268e0acf427cc203930586f3e5db99c9e18 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Wed, 20 Sep 2023 22:57:41 -0400 Subject: [PATCH 72/74] diff stars pop in nicer --- source/funkin/FreeplayState.hx | 10 ++++++++++ source/funkin/freeplayStuff/DifficultyStars.hx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 0f9615927..906591656 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -331,7 +331,9 @@ class FreeplayState extends MusicBeatSubState var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1')); var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite')); + var difficultyStars:DifficultyStars = new DifficultyStars(140, 39); + difficultyStars.stars.visible = false; albumTitle.visible = false; albumArtist.visible = false; @@ -348,9 +350,16 @@ class FreeplayState extends MusicBeatSubState speed: 0.2, wait: 0.2 }); + exitMovers.set([difficultyStars], + { + x: FlxG.width * 1.2, + speed: 0.2, + wait: 0.3 + }); add(albumTitle); add(albumArtist); + add(difficultyStars); var overhangStuff:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 64, FlxColor.BLACK); overhangStuff.y -= overhangStuff.height; @@ -439,6 +448,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(35 / 24, function(_) { albumArtist.visible = true; + difficultyStars.stars.visible = true; }); FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut}); diff --git a/source/funkin/freeplayStuff/DifficultyStars.hx b/source/funkin/freeplayStuff/DifficultyStars.hx index cc98fb07b..8611727be 100644 --- a/source/funkin/freeplayStuff/DifficultyStars.hx +++ b/source/funkin/freeplayStuff/DifficultyStars.hx @@ -17,7 +17,7 @@ class DifficultyStars extends FlxSpriteGroup */ public var difficulty(default, set):Int = 1; - var stars:FlxAtlasSprite; + public var stars:FlxAtlasSprite; var flames:FreeplayFlames; From 8009045e76897eb13f0d5916a4603a4d000bfb13 Mon Sep 17 00:00:00 2001 From: Cameron Taylor <cameron.taylor.ninja@gmail.com> Date: Thu, 28 Sep 2023 20:29:19 -0400 Subject: [PATCH 73/74] fix to song selection --- source/funkin/FreeplayState.hx | 55 ++++++++++----------- source/funkin/freeplayStuff/SongMenuItem.hx | 8 ++- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 906591656..9ad364215 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -22,6 +22,7 @@ import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import funkin.data.song.SongRegistry; +import funkin.data.level.LevelRegistry; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; import funkin.Controls.Control; @@ -71,6 +72,7 @@ class FreeplayState extends MusicBeatSubState var grpSongs:FlxTypedGroup<Alphabet>; var grpCapsules:FlxTypedGroup<SongMenuItem>; + var curCapsule:SongMenuItem; var curPlaying:Bool = false; var dj:DJBoyfriend; @@ -120,40 +122,29 @@ class FreeplayState extends MusicBeatSubState addSong('Pyro', 'weekend1', 'darnell'); #end - var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); + // var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); - for (i in 0...initSonglist.length) - { - songs.push(new FreeplaySongData(initSonglist[i], 'tutorial', 'gf')); - } + // for (i in 0...initSonglist.length) + // { + // songs.push(new FreeplaySongData(initSonglist[i], 'tutorial', 'gf')); + // } if (FlxG.sound.music != null) { if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); } - // if (StoryMenuState.weekUnlocked[2] || isDebug) - addWeek(['Bopeebo', 'Fresh', 'Dadbattle'], 'week1', ['dad']); - - // if (StoryMenuState.weekUnlocked[2] || isDebug) - addWeek(['Spookeez', 'South', 'Monster'], 'week2', ['spooky', 'spooky', 'monster']); - - // if (StoryMenuState.weekUnlocked[3] || isDebug) - addWeek(['Pico', 'Philly-Nice', 'Blammed'], 'week3', ['pico']); - - // if (StoryMenuState.weekUnlocked[4] || isDebug) - addWeek(['Satin-Panties', 'High', 'MILF'], 'week4', ['mom']); - - // if (StoryMenuState.weekUnlocked[5] || isDebug) - addWeek(['Cocoa', 'Eggnog', 'Winter-Horrorland'], 'week5', ['parents-christmas', 'parents-christmas', 'monster-christmas']); - - // if (StoryMenuState.weekUnlocked[6] || isDebug) - addWeek(['Senpai', 'Roses', 'Thorns'], 'week6', ['senpai', 'senpai', 'spirit']); - - // if (StoryMenuState.weekUnlocked[7] || isDebug) - addWeek(['Ugh', 'Guns', 'Stress'], 'week7', ['tankman']); - - addWeek(["Darnell", "lit-up", "2hot", "blazin"], 'weekend1', ['darnell']); + // programmatically adds the songs via LevelRegistry and SongRegistry + for (coolWeek in LevelRegistry.instance.listBaseGameLevelIds()) + { + for (coolSong in LevelRegistry.instance.parseEntryData(coolWeek).songs) + { + var metadata = SongRegistry.instance.parseEntryMetadata(coolSong); + var char = metadata.playData.characters.opponent; + var songName = metadata.songName; + addSong(songName, coolWeek, char); + } + } // LOAD MUSIC @@ -593,7 +584,9 @@ class FreeplayState extends MusicBeatSubState var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem); funnyMenu.init(FlxG.width, 0, tempSongs[i].songName); if (tempSongs[i].songCharacter != null) funnyMenu.setCharacter(tempSongs[i].songCharacter); - funnyMenu.onConfirm = capsuleOnConfirmDefault; + funnyMenu.onConfirm = function() { + capsuleOnConfirmDefault(funnyMenu); + }; funnyMenu.y = funnyMenu.intendedY(i + 1) + 10; funnyMenu.targetPos.x = funnyMenu.x; funnyMenu.ID = i; @@ -954,7 +947,7 @@ class FreeplayState extends MusicBeatSubState } } - function capsuleOnConfirmDefault():Void + function capsuleOnConfirmDefault(cap:SongMenuItem):Void { // var poop:String = songs[curSelected].songName.toLowerCase(); @@ -969,7 +962,9 @@ class FreeplayState extends MusicBeatSubState }*/ PlayStatePlaylist.isStoryMode = false; - var targetSong:Song = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase()); + + var songId:String = cap.songTitle.toLowerCase(); + var targetSong:Song = SongRegistry.instance.fetchEntry(songId); var targetDifficulty:String = switch (curDifficulty) { case 0: diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 3518e96ce..5c022121a 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -166,14 +166,20 @@ class SongMenuItem extends FlxSpriteGroup { var charPath:String = "freeplay/icons/"; + trace(char); + switch (char) { case "monster-christmas": charPath += "monsterpixel"; - case "mom": + case "mom-car": charPath += "mommypixel"; case "dad": charPath += "daddypixel"; + case "darnell-blazin": + charPath += "darnellpixel"; + case "senpai-angry": + charPath += "senpaipixel"; default: charPath += char + "pixel"; } From f652beb382fb6d9a886657452e312f9196d4bc40 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Mon, 9 Oct 2023 20:04:21 -0400 Subject: [PATCH 74/74] Fix several merge errors. --- assets | 2 +- hmm.json | 8 ++++---- source/funkin/FreeplayState.hx | 10 ++++++++-- source/funkin/freeplayStuff/DJBoyfriend.hx | 2 +- source/funkin/freeplayStuff/SongMenuItem.hx | 19 +++++++++++++++++-- source/funkin/play/song/Song.hx | 2 +- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/assets b/assets index 386dcac52..4ee0c341f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 386dcac52e7b8ddc8cc4ad3864bd8d51f01564d1 +Subproject commit 4ee0c341fd213017b98314d47d28f08b8e1d4289 diff --git a/hmm.json b/hmm.json index e830ff435..3f420ac48 100644 --- a/hmm.json +++ b/hmm.json @@ -4,14 +4,14 @@ "name": "discord_rpc", "type": "git", "dir": null, - "ref": "2d83fa8", + "ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5", "url": "https://github.com/Aidan63/linc_discord-rpc" }, { "name": "flixel", "type": "git", "dir": null, - "ref": "32cee07a", + "ref": "32cee07a0e5f21e590a4b21234603b2cd5898b10", "url": "https://github.com/EliteMasterEric/flixel" }, { @@ -32,7 +32,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "dd2903f", + "ref": "dd2903f7dc7024335b981edf2a770760cec912e1", "url": "https://github.com/ninjamuffin99/flxanimate" }, { @@ -75,7 +75,7 @@ "name": "hxCodec", "type": "git", "dir": null, - "ref": "c8c47e7", + "ref": "c8c47e706ad82a423783006ed901b6d93c89a421", "url": "https://github.com/polybiusproxy/hxCodec" }, { diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 9ad364215..3ae32c2e4 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -13,6 +13,7 @@ import flixel.addons.ui.FlxInputText; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup; import flixel.group.FlxSpriteGroup; +import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; import flixel.math.FlxMath; @@ -31,6 +32,7 @@ import funkin.freeplayStuff.DJBoyfriend; import funkin.freeplayStuff.FreeplayScore; import funkin.freeplayStuff.LetterSort; import funkin.freeplayStuff.SongMenuItem; +import funkin.freeplayStuff.DifficultyStars; import funkin.play.HealthIcon; import funkin.play.PlayState; import funkin.shaderslmfao.AngleMask; @@ -118,8 +120,8 @@ class FreeplayState extends MusicBeatSubState #if debug isDebug = true; - addSong('Test', 'tutorial', 'bf-pixel'); - addSong('Pyro', 'weekend1', 'darnell'); + // addSong('Test', 'tutorial', 'bf-pixel'); + // addSong('Pyro', 'weekend1', 'darnell'); #end // var initSonglist = CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); @@ -995,6 +997,10 @@ class FreeplayState extends MusicBeatSubState FlxG.sound.play(Paths.sound('confirmMenu')); dj.confirm(); + // Load and cache the song's charts. + // TODO: Do this in the loading state. + targetSong.cacheCharts(true); + new FlxTimer().start(1, function(tmr:FlxTimer) { LoadingState.loadAndSwitchState(new PlayState( { diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index 58318df15..ba0ce464d 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -206,7 +206,7 @@ class DJBoyfriend extends FlxAtlasSprite cartoonSnd.play(true, FlxG.random.float(0, cartoonSnd.length)); } - var cartoonList:Array<String> = Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/")); + var cartoonList:Array<String> = openfl.utils.Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/")); function getRandomFlashToon():String { diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 5c022121a..5fd7eb576 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -159,8 +159,9 @@ class SongMenuItem extends FlxSpriteGroup } /** - * [Description] - * @param char Should be songCharacter, and will get translated to the correct path via switch + * Set the character displayed next to this song in the freeplay menu. + * @param char The character ID used by this song. + * If the character has no freeplay icon, a warning will be thrown and nothing will display. */ public function setCharacter(char:String) { @@ -184,6 +185,12 @@ class SongMenuItem extends FlxSpriteGroup charPath += char + "pixel"; } + if (!openfl.utils.Assets.exists(Paths.image(charPath))) + { + trace('[WARN] Character ${char} has no freeplay icon.'); + return; + } + pixelIcon.loadGraphic(Paths.image(charPath)); pixelIcon.scale.x = pixelIcon.scale.y = 2; pixelIcon.origin.x = 100; @@ -312,6 +319,14 @@ class SongMenuItem extends FlxSpriteGroup return (index * ((height * realScaled) + 10)) + 120; } + /** + * Merely a helper function to call set_selected, to make sure that the alpha is correct on the rankings/selections + */ + public function selectedAlpha():Void + { + selected = selected; + } + function set_selected(value:Bool):Bool { // cute one liners, lol! diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index f3fc89a86..d11c7744b 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -117,7 +117,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>, validScore:Bool = false):Song { - var result:Song = new Song(songId, true); + var result:Song = new Song(songId); result._metadata.clear(); for (meta in metadata)