From f790cd8fd60ed87c67dcc36715de46ce8bacada1 Mon Sep 17 00:00:00 2001 From: Eric Myllyoja <ericmyllyoja@gmail.com> Date: Tue, 6 Sep 2022 00:59:54 -0400 Subject: [PATCH] WIP --- source/funkin/play/song/ScriptedSong.hx | 8 ++ source/funkin/play/song/Song.hx | 38 +++++++ source/funkin/play/song/SongData.hx | 129 ++++++++++++++++++++++++ source/funkin/play/song/SongMitrator.hx | 12 +++ 4 files changed, 187 insertions(+) create mode 100644 source/funkin/play/song/ScriptedSong.hx create mode 100644 source/funkin/play/song/Song.hx create mode 100644 source/funkin/play/song/SongData.hx create mode 100644 source/funkin/play/song/SongMitrator.hx diff --git a/source/funkin/play/song/ScriptedSong.hx b/source/funkin/play/song/ScriptedSong.hx new file mode 100644 index 000000000..e89f68596 --- /dev/null +++ b/source/funkin/play/song/ScriptedSong.hx @@ -0,0 +1,8 @@ +package funkin.play.song; + +import polymod.hscript.HScriptedClass; + +@:hscriptClass +class ScriptedSong extends Song implements HScriptedClass +{ +} diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx new file mode 100644 index 000000000..fb86f4e20 --- /dev/null +++ b/source/funkin/play/song/Song.hx @@ -0,0 +1,38 @@ +package funkin.play.song; + +/** + * This is a data structure managing information about the current song. + * This structure is created when the game starts, and includes all the data + * from the `metadata.json` file. + * It also includes the chart data, but only when this is the currently loaded song. + * + * It also receives script events; scripted classes which extend this class + * can be used to perform custom gameplay behaviors only on specific songs. + */ +class Song implements IPlayStateScriptedClass +{ + public var songId(default, null):String; + + public var songName(get, null):String; + + final _metadata:SongMetadata; + final _chartData:SongChartData; + + public function new(id:String) + { + this.songId = songId; + + _metadata = SongDataParser.parseSongMetadata(this.songId); + if (_metadata == null) + { + throw 'Could not find song data for songId: $songId'; + } + } + + function get_songName():String + { + if (_metadata == null) + return null; + return _metadata.name; + } +} diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx new file mode 100644 index 000000000..b4992992e --- /dev/null +++ b/source/funkin/play/song/SongData.hx @@ -0,0 +1,129 @@ +package funkin.play.song; + +/** + * Contains utilities for loading and parsing stage data. + */ +class SongDataParser +{ + /** + * The current version string for the stage 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"; + + /** + * A list containing all the songs available to the game. + */ + static final songCache:Map<String, Stage> = new Map<String, Stage>(); + + static final DEFAULT_SONG_ID = 'UNKNOWN'; + + /** + * Parses and preloads the game's song metadata and scripts when the game starts. + * + * If you want to force song metadata to be reloaded, you can just call this function again. + */ + public static function loadSongCache():Void + { + clearSongCache(); + trace("[SONGDATA] Loading song cache..."); + + // + // SCRIPTED SONGS + // + var scriptedSongClassNames:Array<String> = ScriptedSong.listScriptClasses(); + trace(' Instantiating ${scriptedSongClassNames.length} scripted songs...'); + for (songCls in scriptedSongClassNames) + { + var song:Song = ScriptedSong.init(songCls, DEFAULT_SONG_ID); + if (song != null) + { + trace(' Loaded scripted song: ${song.songId}'); + songCache.set(song.songId, song); + } + else + { + trace(' Failed to instantiate scripted song class: ${songCls}'); + } + } + + // + // UNSCRIPTED SONGS + // + var songIdList:Array<String> = DataAssets.listDataFilesInPath('songs/'); + var unscriptedSongIds:Array<String> = songIdList.filter(function(songId:String):Bool + { + return !songCache.exists(songId); + }); + trace(' Instantiating ${unscriptedSongIds.length} non-scripted songs...'); + for (songId in unscriptedSongIds) + { + var song:Song; + try + { + stage = new Song(songId); + if (stage != null) + { + trace(' Loaded song data: ${song.songId}'); + songCache.set(song.songId, song); + } + } + catch (e) + { + trace(' An error occurred while loading song data: ${songId}'); + // Assume error was already logged. + continue; + } + } + + trace(' Successfully loaded ${Lambda.count(songCache)} stages.'); + } + + /** + * Retrieves a particular song from the cache. + */ + public static function fetchStage(songId:String):Null<Song> + { + if (songCache.exists(songId)) + { + var song:Song = songCache.get(songId); + trace('[STAGEDATA] Successfully fetch song: ${songId}'); + return song; + } + else + { + trace('[STAGEDATA] Failed to fetch song, not found in cache: ${songId}'); + return null; + } + } + + static function clearSongCache():Void + { + if (songCache != null) + { + for (song in songCache) + { + song.destroy(); + } + songCache.clear(); + } + } + + public static function parseSongMetadata(songId:String):Null<SongMetadata> + { + } + + static function loadSongMetadataFile(songPath:String, variant:String = ''):String + { + var songMetadataFilePath:String = Paths.json('stages/${stagePath}'); + var rawJson = Assets.getText(stageFilePath).trim(); + + while (!rawJson.endsWith("}")) + { + rawJson = rawJson.substr(0, rawJson.length - 1); + } + + return rawJson; + } +} diff --git a/source/funkin/play/song/SongMitrator.hx b/source/funkin/play/song/SongMitrator.hx new file mode 100644 index 000000000..cad03b8ff --- /dev/null +++ b/source/funkin/play/song/SongMitrator.hx @@ -0,0 +1,12 @@ +package funkin.play.song; + +class SongMigrator +{ + public static function migrateSongMetadata(song:Song, jsonData:Dynamic) + { + } + + public static function migrateSongChart(song:Song, jsonData:Dynamic) + { + } +}