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