From 3597b09a465585595510914d4a79bf3d6d4a79f3 Mon Sep 17 00:00:00 2001 From: Eric Myllyoja <ericmyllyoja@gmail.com> Date: Fri, 16 Sep 2022 15:37:00 -0400 Subject: [PATCH] Finished song data loader --- source/funkin/LatencyState.hx | 8 +- source/funkin/play/character/BaseCharacter.hx | 4 - source/funkin/play/song/Song.hx | 65 +++-- source/funkin/play/song/SongData.hx | 231 ++++++++++++++++++ source/funkin/play/song/SongMigrator.hx | 4 +- source/funkin/play/song/SongValidator.hx | 1 + .../ui/stageBuildShit/StageOffsetSubstate.hx | 12 +- 7 files changed, 289 insertions(+), 36 deletions(-) diff --git a/source/funkin/LatencyState.hx b/source/funkin/LatencyState.hx index 3f2778762..d3a790104 100644 --- a/source/funkin/LatencyState.hx +++ b/source/funkin/LatencyState.hx @@ -139,17 +139,17 @@ class LatencyState extends MusicBeatSubstate super.create(); } - override function stepHit() + override function stepHit():Bool { if (curStep % 4 == 2) { blocks.members[((curBeat % 8) + 1) % 8].alpha = 0.5; } - super.stepHit(); + return super.stepHit(); } - override function beatHit() + override function beatHit():Bool { if (curBeat % 8 == 0) blocks.forEach(blok -> @@ -160,7 +160,7 @@ class LatencyState extends MusicBeatSubstate blocks.members[curBeat % 8].alpha = 1; // block.visible = !block.visible; - super.beatHit(); + return super.beatHit(); } override function update(elapsed:Float) diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 6d08fcd7c..68cd5e68d 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -335,11 +335,7 @@ class BaseCharacter extends Bopper var shouldStopSinging:Bool = (this.characterType == BF) ? !isHoldingNote() : true; FlxG.watch.addQuick('singTimeMs-${characterId}', singTimeMs); -<<<<<<< HEAD - if (holdTimer > singTimeMs && shouldStopSinging) // && !getCurrentAnimation().endsWith("miss") -======= if (holdTimer > singTimeMs && shouldStopSinging) ->>>>>>> origin/note-redux { // trace('holdTimer reached ${holdTimer}sec (> ${singTimeMs}), stopping sing animation'); holdTimer = 0; diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 77ae1bd56..aa9095f8c 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -1,7 +1,10 @@ package funkin.play.song; import funkin.play.song.SongData.SongDataParser; +import funkin.play.song.SongData.SongEventData; import funkin.play.song.SongData.SongMetadata; +import funkin.play.song.SongData.SongNoteData; +import funkin.play.song.SongData.SongPlayableChar; import funkin.play.song.SongData.SongTimeChange; import funkin.play.song.SongData.SongTimeFormat; @@ -20,12 +23,14 @@ class Song // implements IPlayStateScriptedClass final _metadata:Array<SongMetadata>; + final variations:Array<String>; final difficulties:Map<String, SongDifficulty>; public function new(id:String) { this.songId = id; + variations = []; difficulties = new Map<String, SongDifficulty>(); _metadata = SongDataParser.parseSongMetadata(songId); @@ -35,6 +40,9 @@ class Song // implements IPlayStateScriptedClass } populateFromMetadata(); + + // TODO: Disable later. + cacheCharts(); } function populateFromMetadata() @@ -46,6 +54,8 @@ class Song // implements IPlayStateScriptedClass { var difficulty = new SongDifficulty(diffId, metadata.variation); + variations.push(metadata.variation); + difficulty.songName = metadata.songName; difficulty.songArtist = metadata.artist; difficulty.timeFormat = metadata.timeFormat; @@ -54,28 +64,48 @@ class Song // implements IPlayStateScriptedClass difficulty.loop = metadata.loop; difficulty.generatedBy = metadata.generatedBy; + difficulty.stage = metadata.playData.stage; + // difficulty.noteSkin = metadata.playData.noteSkin; + + difficulty.chars = new Map<String, SongPlayableChar>(); + for (charId in metadata.playData.playableChars.keys()) + { + var char = metadata.playData.playableChars.get(charId); + + difficulty.chars.set(charId, char); + } + difficulties.set(diffId, difficulty); } } } - /** - * Parse and cache the chart for a specific difficulty. - */ - public function cacheChart(diffId:String) - { - getDifficulty(diffId).cacheChart(); - } - /** * Parse and cache the chart for all difficulties of this song. */ public function cacheCharts() { - for (difficulty in difficulties) + trace('Caching ${variations.length} chart files for song $songId'); + for (variation in variations) { - difficulty.cacheChart(); + var chartData = SongDataParser.parseSongChartData(songId, variation); + + for (diffId in chartData.notes.keys()) + { + trace(' Difficulty $diffId'); + var difficulty = difficulties.get(diffId); + if (difficulty == null) + { + trace('Could not find difficulty $diffId for song $songId'); + continue; + } + + difficulty.notes = chartData.notes.get(diffId); + difficulty.scrollSpeed = chartData.scrollSpeed.get(diffId); + difficulty.events = chartData.events; + } } + trace('Done caching charts.'); } /** @@ -124,9 +154,13 @@ class SongDifficulty public var timeChanges:Array<SongTimeChange> = []; - public var scrollSpeed(default, null):Float = SongValidator.DEFAULT_SCROLLSPEED; + public var stage:String = SongValidator.DEFAULT_STAGE; + public var chars:Map<String, SongPlayableChar> = null; - // public var notes(default, null):Array<; + public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED; + + public var notes:Array<SongNoteData>; + public var events:Array<SongEventData>; public function new(diffId:String, variation:String) { @@ -134,13 +168,8 @@ class SongDifficulty this.variation = variation; } - public function cacheChart():Void - { - // TODO: Parse chart data - } - public function clearChart():Void { - // notes = null; + notes = null; } } diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index 64f04c825..bdf9d9043 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -249,6 +249,231 @@ typedef RawSongPlayableChar = var i:String; } +typedef RawSongNoteData = +{ + /** + * The timestamp of the note. The timestamp is in the format of the song's time format. + */ + var t:Float; + + /** + * Data for the note. Represents the index on the strumline. + * 0 = left, 1 = down, 2 = up, 3 = right + * `floor(direction / strumlineSize)` specifies which strumline the note is on. + * 0 = player, 1 = opponent, etc. + */ + var d:Int; + + /** + * Length of the note, if applicable. + * Defaults to 0 for single notes. + */ + var l:Float; + + /** + * The kind of the note. + * This can allow the note to include information used for custom behavior. + * Defaults to blank or `"normal"`. + */ + var k:String; +} + +abstract SongNoteData(RawSongNoteData) +{ + public function new(time:Float, data:Int, length:Float = 0, kind:String = "") + { + this = { + t: time, + d: data, + l: length, + k: kind + }; + } + + public var time(get, set):Float; + + public function get_time():Float + { + return this.t; + } + + public function set_time(value:Float):Float + { + return this.t = value; + } + + /** + * The raw data for the note. + */ + public var data(get, set):Int; + + public function get_data():Int + { + return this.d; + } + + public function set_data(value:Int):Int + { + return this.d = value; + } + + /** + * The direction of the note, if applicable. + * Strips the strumline index from the data. + * + * 0 = left, 1 = down, 2 = up, 3 = right + */ + public inline function getDirection(strumlineSize:Int = 4):Int + { + return this.d % strumlineSize; + } + + /** + * The strumline index of the note, if applicable. + * Strips the direction from the data. + * + * 0 = player, 1 = opponent, etc. + */ + public inline function getStrumlineIndex(strumlineSize:Int = 4):Int + { + return Math.floor(this.d / strumlineSize); + } + + public var length(get, set):Float; + + public function get_length():Float + { + return this.l; + } + + public function set_length(value:Float):Float + { + return this.l = value; + } + + public var kind(get, set):String; + + public function get_kind():String + { + if (this.k == null || this.k == '') + return 'normal'; + + return this.k; + } + + public function set_kind(value:String):String + { + if (value == 'normal' || value == '') + value = null; + return this.k = value; + } +} + +typedef RawSongEventData = +{ + /** + * The timestamp of the event. The timestamp is in the format of the song's time format. + */ + var t:Float; + + /** + * The kind of the event. + * Examples include "FocusCamera" and "PlayAnimation" + * Custom events can be added by scripts with the `ScriptedSongEvent` class. + */ + var e:String; + + /** + * The data for the event. + * This can allow the event to include information used for custom behavior. + * Data type depends on the event kind. It can be anything that's JSON serializable. + */ + var v:Dynamic; +} + +abstract SongEventData(RawSongEventData) +{ + public function new(time:Float, event:String, value:Dynamic = null) + { + this = { + t: time, + e: event, + v: value + }; + } + + public var time(get, set):Float; + + public function get_time():Float + { + return this.t; + } + + public function set_time(value:Float):Float + { + return this.t = value; + } + + public var event(get, set):String; + + public function get_event():String + { + return this.e; + } + + public function set_event(value:String):String + { + return this.e = value; + } + + public var value(get, set):Dynamic; + + public function get_value():Dynamic + { + return this.v; + } + + public function set_value(value:Dynamic):Dynamic + { + return this.v = value; + } + + public inline function getBool():Bool + { + return cast this.v; + } + + public inline function getInt():Int + { + return cast this.v; + } + + public inline function getFloat():Float + { + return cast this.v; + } + + public inline function getString():String + { + return cast this.v; + } + + public inline function getArray():Array<Dynamic> + { + return cast this.v; + } + + public inline function getMap():DynamicAccess<Dynamic> + { + return cast this.v; + } + + public inline function getBoolArray():Array<Bool> + { + return cast this.v; + } +} + abstract SongPlayableChar(RawSongPlayableChar) { public function new(girlfriend:String, opponent:String, inst:String = "") @@ -299,6 +524,12 @@ abstract SongPlayableChar(RawSongPlayableChar) typedef SongChartData = { + var version:Version; + + var scrollSpeed:DynamicAccess<Float>; + var events:Array<SongEventData>; + var notes:DynamicAccess<Array<SongNoteData>>; + var generatedBy:String; }; typedef RawSongTimeChange = diff --git a/source/funkin/play/song/SongMigrator.hx b/source/funkin/play/song/SongMigrator.hx index 36573a9fd..9199d7d14 100644 --- a/source/funkin/play/song/SongMigrator.hx +++ b/source/funkin/play/song/SongMigrator.hx @@ -54,9 +54,9 @@ class SongMigrator { trace('[SONGDATA] Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.'); - var songMetadata:SongMetadata = cast jsonData; + var songChartData:SongChartData = cast jsonData; - return songMetadata; + return songChartData; } else { diff --git a/source/funkin/play/song/SongValidator.hx b/source/funkin/play/song/SongValidator.hx index 413c28e1a..8e52bd33e 100644 --- a/source/funkin/play/song/SongValidator.hx +++ b/source/funkin/play/song/SongValidator.hx @@ -18,6 +18,7 @@ class SongValidator public static final DEFAULT_DIVISIONS:Int = -1; public static final DEFAULT_LOOP:Bool = false; public static final DEFAULT_GENERATEDBY:String = "Unknown"; + public static final DEFAULT_STAGE:String = "mainStage"; public static final DEFAULT_SCROLLSPEED:Float = 1.0; /** diff --git a/source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx b/source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx index 6c43d42c6..c40c9a4aa 100644 --- a/source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx +++ b/source/funkin/ui/stageBuildShit/StageOffsetSubstate.hx @@ -1,18 +1,14 @@ package funkin.ui.stageBuildShit; import flixel.FlxSprite; +import flixel.input.mouse.FlxMouseEvent; import flixel.input.mouse.FlxMouseEventManager; import flixel.math.FlxPoint; import flixel.ui.FlxButton; import funkin.play.PlayState; -import funkin.play.character.BaseCharacter; +import funkin.play.stage.StageData.StageDataParser; import funkin.play.stage.StageData; -import haxe.Json; -import haxe.ui.ComponentBuilder; import haxe.ui.RuntimeComponentBuilder; -import haxe.ui.Toolkit; -import haxe.ui.components.Button; -import haxe.ui.containers.HBox; import haxe.ui.containers.VBox; import haxe.ui.core.Component; import openfl.Assets; @@ -46,7 +42,7 @@ class StageOffsetSubstate extends MusicBeatSubstate for (thing in PlayState.instance.currentStage) { - FlxMouseEventManager.add(thing, spr -> + FlxMouseEvent.add(thing, spr -> { char = cast thing; trace("JUST PRESSED!"); @@ -94,7 +90,7 @@ class StageOffsetSubstate extends MusicBeatSubstate { for (thing in PlayState.instance.currentStage) { - FlxMouseEventManager.remove(thing); + FlxMouseEvent.remove(thing); thing.alpha = 1; }