mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
Merge bee50a2ad9
into 0d8e4a5330
This commit is contained in:
commit
5479b51577
9 changed files with 401 additions and 305 deletions
|
@ -3,6 +3,7 @@ package funkin.data.dialogue.conversation;
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
||||||
|
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -14,74 +15,8 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
||||||
|
|
||||||
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):ConversationRegistry;
|
|
||||||
static var _instance:Null<ConversationRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():ConversationRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new ConversationRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<ConversationData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Conversation
|
|
||||||
{
|
|
||||||
return ScriptedConversation.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedConversation.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -16,15 +17,6 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||||
|
|
||||||
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):PlayerRegistry;
|
|
||||||
static var _instance:Null<PlayerRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():PlayerRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new PlayerRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping between stage character IDs and Freeplay playable character IDs.
|
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||||
*/
|
*/
|
||||||
|
@ -131,63 +123,6 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||||
return ownedCharacterIds.exists(characterId);
|
return ownedCharacterIds.exists(characterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<PlayerData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<PlayerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<PlayerData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<PlayerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):PlayableCharacter
|
|
||||||
{
|
|
||||||
return ScriptedPlayableCharacter.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedPlayableCharacter.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the playable characters from the base game, in order.
|
* A list of all the playable characters from the base game, in order.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,6 +4,7 @@ import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
||||||
import funkin.data.notestyle.NoteStyleData;
|
import funkin.data.notestyle.NoteStyleData;
|
||||||
|
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -15,15 +16,6 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||||
|
|
||||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||||
|
|
||||||
public static var instance(get, never):NoteStyleRegistry;
|
|
||||||
static var _instance:Null<NoteStyleRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():NoteStyleRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new NoteStyleRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
|
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
|
||||||
|
@ -33,61 +25,4 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||||
{
|
{
|
||||||
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<NoteStyleData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<NoteStyleData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.ignoreUnknownVariables = false;
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedNoteStyle.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import funkin.util.VersionUtil;
|
||||||
using funkin.data.song.migrator.SongDataMigrator;
|
using funkin.data.song.migrator.SongDataMigrator;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -39,19 +40,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
return '${Constants.TITLE} - ${Constants.VERSION}';
|
return '${Constants.TITLE} - ${Constants.VERSION}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: What if there was a Singleton macro which automatically created the property for us?
|
|
||||||
*/
|
|
||||||
public static var instance(get, never):SongRegistry;
|
|
||||||
|
|
||||||
static var _instance:Null<SongRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():SongRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new SongRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
||||||
|
@ -417,16 +405,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Song
|
|
||||||
{
|
|
||||||
return ScriptedSong.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedSong.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
|
@ -508,8 +486,32 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
public function listBaseGameSongIds():Array<String>
|
public function listBaseGameSongIds():Array<String>
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"tutorial", "bopeebo", "fresh", "dadbattle", "spookeez", "south", "monster", "pico", "philly-nice", "blammed", "satin-panties", "high", "milf", "cocoa",
|
"tutorial",
|
||||||
"eggnog", "winter-horrorland", "senpai", "roses", "thorns", "ugh", "guns", "stress", "darnell", "lit-up", "2hot", "blazin"
|
"bopeebo",
|
||||||
|
"fresh",
|
||||||
|
"dadbattle",
|
||||||
|
"spookeez",
|
||||||
|
"south",
|
||||||
|
"monster",
|
||||||
|
"pico",
|
||||||
|
"philly-nice",
|
||||||
|
"blammed",
|
||||||
|
"satin-panties",
|
||||||
|
"high",
|
||||||
|
"milf",
|
||||||
|
"cocoa",
|
||||||
|
"eggnog",
|
||||||
|
"winter-horrorland",
|
||||||
|
"senpai",
|
||||||
|
"roses",
|
||||||
|
"thorns",
|
||||||
|
"ugh",
|
||||||
|
"guns",
|
||||||
|
"stress",
|
||||||
|
"darnell",
|
||||||
|
"lit-up",
|
||||||
|
"2hot",
|
||||||
|
"blazin"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,23 +30,14 @@ import funkin.util.EaseUtil;
|
||||||
*
|
*
|
||||||
* This shit is great for modders but it's pretty elaborate for how much it'll actually be used, lolol. -Eric
|
* This shit is great for modders but it's pretty elaborate for how much it'll actually be used, lolol. -Eric
|
||||||
*/
|
*/
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The ID of the conversation.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of the conversation.
|
* The current state of the conversation.
|
||||||
*/
|
*/
|
||||||
var state:ConversationState = ConversationState.Start;
|
var state:ConversationState = ConversationState.Start;
|
||||||
|
|
||||||
/**
|
|
||||||
* Conversation data as parsed from the JSON file.
|
|
||||||
*/
|
|
||||||
public final _data:ConversationData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current entry in the dialogue.
|
* The current entry in the dialogue.
|
||||||
*/
|
*/
|
||||||
|
@ -631,16 +622,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||||
outroTween = null;
|
outroTween = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
|
||||||
{
|
|
||||||
return 'Conversation($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
return ConversationRegistry.instance.parseEntryDataWithMigration(id, ConversationRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Managing things with a single enum is a lot easier than a multitude of flags.
|
// Managing things with a single enum is a lot easier than a multitude of flags.
|
||||||
|
|
|
@ -18,13 +18,9 @@ using funkin.data.animation.AnimationData.AnimationDataUtil;
|
||||||
* and provides convenience methods for building sprites based on them.
|
* and provides convenience methods for building sprites based on them.
|
||||||
*/
|
*/
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The ID of the note style.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note style data as parsed from the JSON file.
|
* Note style data as parsed from the JSON file.
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +32,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
*/
|
*/
|
||||||
var fallback(get, never):Null<NoteStyle>;
|
var fallback(get, never):Null<NoteStyle>;
|
||||||
|
|
||||||
function get_fallback():Null<NoteStyle> {
|
function get_fallback():Null<NoteStyle>
|
||||||
|
{
|
||||||
if (_data == null || _data.fallback == null) return null;
|
if (_data == null || _data.fallback == null) return null;
|
||||||
return NoteStyleRegistry.instance.fetchEntry(_data.fallback);
|
return NoteStyleRegistry.instance.fetchEntry(_data.fallback);
|
||||||
}
|
}
|
||||||
|
@ -880,13 +877,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy():Void {}
|
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
return 'NoteStyle($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):NoteStyleData
|
static function _fetchData(id:String):NoteStyleData
|
||||||
{
|
{
|
||||||
var result = NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
var result = NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||||
|
|
|
@ -28,6 +28,7 @@ import funkin.util.SortUtil;
|
||||||
* can be used to perform custom gameplay behaviors only on specific songs.
|
* can be used to perform custom gameplay behaviors only on specific songs.
|
||||||
*/
|
*/
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -65,19 +66,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
*/
|
*/
|
||||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal ID of the song.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Song metadata as parsed from the JSON file.
|
|
||||||
* This is the data for the `default` variation specifically,
|
|
||||||
* and is needed for the IRegistryEntry interface.
|
|
||||||
* Will only be null if the song data could not be loaded.
|
|
||||||
*/
|
|
||||||
public final _data:Null<SongMetadata>;
|
|
||||||
|
|
||||||
// key = variation id, value = metadata
|
// key = variation id, value = metadata
|
||||||
final _metadata:Map<String, SongMetadata>;
|
final _metadata:Map<String, SongMetadata>;
|
||||||
|
|
||||||
|
@ -624,13 +612,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
return 'Song($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy():Void {}
|
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent):Void {};
|
public function onPause(event:PauseScriptEvent):Void {};
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent):Void {};
|
public function onResume(event:ScriptEvent):Void {};
|
||||||
|
|
|
@ -10,18 +10,11 @@ import funkin.play.scoring.Scoring.ScoringRank;
|
||||||
* Can be scripted to override each function, for custom behavior.
|
* Can be scripted to override each function, for custom behavior.
|
||||||
*/
|
*/
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
|
@:build(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
class PlayableCharacter implements IRegistryEntry<PlayerData>
|
class PlayableCharacter implements IRegistryEntry<PlayerData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The ID of the playable character.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
public final id:String;
|
||||||
|
|
||||||
/**
|
|
||||||
* Playable character data as parsed from the JSON file.
|
|
||||||
*/
|
|
||||||
public final _data:Null<PlayerData>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param id The ID of the JSON file to parse.
|
* @param id The ID of the JSON file to parse.
|
||||||
*/
|
*/
|
||||||
|
@ -156,25 +149,4 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
||||||
{
|
{
|
||||||
return _data?.unlocked ?? true;
|
return _data?.unlocked ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the character is destroyed.
|
|
||||||
* TODO: Document when this gets called
|
|
||||||
*/
|
|
||||||
public function destroy():Void {}
|
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
return 'PlayableCharacter($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve and parse the JSON data for a playable character by ID.
|
|
||||||
* @param id The ID of the character
|
|
||||||
* @return The parsed player data, or null if not found or invalid
|
|
||||||
*/
|
|
||||||
static function _fetchData(id:String):Null<PlayerData>
|
|
||||||
{
|
|
||||||
return PlayerRegistry.instance.parseEntryDataWithMigration(id, PlayerRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
365
source/funkin/util/macro/RegistryMacro.hx
Normal file
365
source/funkin/util/macro/RegistryMacro.hx
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
import haxe.macro.Type;
|
||||||
|
|
||||||
|
using haxe.macro.ExprTools;
|
||||||
|
using haxe.macro.TypeTools;
|
||||||
|
using StringTools;
|
||||||
|
|
||||||
|
class RegistryMacro
|
||||||
|
{
|
||||||
|
public static macro function buildRegistry():Array<Field>
|
||||||
|
{
|
||||||
|
var fields = Context.getBuildFields();
|
||||||
|
|
||||||
|
var cls = Context.getLocalClass().get();
|
||||||
|
|
||||||
|
if (!cls.name.endsWith('Registry'))
|
||||||
|
{
|
||||||
|
throw '${cls.module}.${cls.name} needs to end with "Registry"';
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeParams = getTypeParams(cls);
|
||||||
|
var entryCls = typeParams.entryCls;
|
||||||
|
var jsonCls = typeParams.jsonCls;
|
||||||
|
var scriptedEntryCls = getScriptedEntryClass(entryCls);
|
||||||
|
|
||||||
|
fields = fields.concat(buildRegistryVariables(cls));
|
||||||
|
fields = fields.concat(buildRegistryMethods(cls));
|
||||||
|
|
||||||
|
buildEntryImpl(entryCls, cls);
|
||||||
|
buildRegistryImpl(cls, entryCls, scriptedEntryCls, jsonCls);
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static macro function buildEntry():Array<Field>
|
||||||
|
{
|
||||||
|
var fields = Context.getBuildFields();
|
||||||
|
|
||||||
|
var cls = Context.getLocalClass().get();
|
||||||
|
|
||||||
|
var entryData = getEntryData(cls);
|
||||||
|
|
||||||
|
// since the registries also use a build macro
|
||||||
|
// the fields aren't callable unless we first get
|
||||||
|
// the class type of the registry
|
||||||
|
makeFieldsCallable(cls);
|
||||||
|
|
||||||
|
fields = fields.concat(buildEntryVariables(cls, entryData));
|
||||||
|
fields = fields.concat(buildEntryMethods(cls));
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
static function makeFieldsCallable(cls:ClassType)
|
||||||
|
{
|
||||||
|
// TODO: lets not have this if statement
|
||||||
|
// like what the hell is wrong with this
|
||||||
|
if (cls.name == 'Song')
|
||||||
|
{
|
||||||
|
MacroUtil.getClassTypeFromExpr(macro funkin.data.song.SongRegistry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registries:Array<String> = [];
|
||||||
|
for (localImport in Context.getLocalImports())
|
||||||
|
{
|
||||||
|
var names = [];
|
||||||
|
for (path in localImport.path)
|
||||||
|
{
|
||||||
|
names.push(path.name);
|
||||||
|
}
|
||||||
|
var fullName = names.join('.');
|
||||||
|
|
||||||
|
if (fullName.endsWith('Registry'))
|
||||||
|
{
|
||||||
|
registries.push(fullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (registry in registries)
|
||||||
|
{
|
||||||
|
MacroUtil.getClassTypeFromExpr(Context.parse(registry, Context.currentPos()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function fieldAlreadyExists(name:String):Bool
|
||||||
|
{
|
||||||
|
for (field in Context.getBuildFields())
|
||||||
|
{
|
||||||
|
if (field.name == name && !((field.access ?? []).contains(Access.AAbstract)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldAlreadyExistsSuper(name:String, superClass:Null<ClassType>)
|
||||||
|
{
|
||||||
|
if (superClass == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (field in superClass.fields.get())
|
||||||
|
{
|
||||||
|
if (field.name == name && !field.isAbstract)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively check superclasses
|
||||||
|
return fieldAlreadyExistsSuper(name, superClass.superClass?.t.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldAlreadyExistsSuper(name, Context.getLocalClass().get().superClass?.t.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getTypeParams(cls:ClassType):RegistryTypeParamsNew
|
||||||
|
{
|
||||||
|
switch (cls.superClass.t.get().kind)
|
||||||
|
{
|
||||||
|
case KGenericInstance(_, params):
|
||||||
|
var typeParams:Array<Dynamic> = [];
|
||||||
|
for (param in params)
|
||||||
|
{
|
||||||
|
switch (param)
|
||||||
|
{
|
||||||
|
case TInst(t, _):
|
||||||
|
typeParams.push(t.get());
|
||||||
|
case TType(t, _):
|
||||||
|
typeParams.push(t.get());
|
||||||
|
default:
|
||||||
|
throw 'Not a class';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {entryCls: typeParams[0], jsonCls: typeParams[1]};
|
||||||
|
default:
|
||||||
|
throw 'Not in the correct format';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getScriptedEntryClass(entryCls:ClassType):ClassType
|
||||||
|
{
|
||||||
|
var scriptedEntryClsName = entryCls.pack.join('.') + '.Scripted' + entryCls.name;
|
||||||
|
switch (Context.getType(scriptedEntryClsName))
|
||||||
|
{
|
||||||
|
case Type.TInst(t, _):
|
||||||
|
return t.get();
|
||||||
|
default:
|
||||||
|
throw 'Not A Class (${scriptedEntryClsName})';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getEntryData(cls:ClassType):Dynamic // DefType or ClassType
|
||||||
|
{
|
||||||
|
switch (cls.interfaces[0].params[0])
|
||||||
|
{
|
||||||
|
case Type.TInst(t, _):
|
||||||
|
return t.get();
|
||||||
|
case Type.TType(t, _):
|
||||||
|
return t.get();
|
||||||
|
default:
|
||||||
|
throw 'Entry Data is not a class or typedef';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildRegistryVariables(cls:ClassType):Array<Field>
|
||||||
|
{
|
||||||
|
var clsType:ComplexType = Context.getType('${cls.module}.${cls.name}').toComplexType();
|
||||||
|
|
||||||
|
var newInstance:String = 'new ${cls.module}.${cls.name}()';
|
||||||
|
|
||||||
|
return (macro class TempClass
|
||||||
|
{
|
||||||
|
static var _instance:Null<$clsType>;
|
||||||
|
public static var instance(get, never):$clsType;
|
||||||
|
|
||||||
|
static function get_instance():$clsType
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = ${Context.parse(newInstance, Context.currentPos())};
|
||||||
|
}
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}).fields.filter((field) -> return !fieldAlreadyExists(field.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildRegistryMethods(cls:ClassType):Array<Field>
|
||||||
|
{
|
||||||
|
var impl:String = 'funkin.macro.impl._${cls.name}_Impl';
|
||||||
|
|
||||||
|
return (macro class TempClass
|
||||||
|
{
|
||||||
|
function getScriptedClassNames()
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}.getScriptedClassNames(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptedEntry(clsName:String)
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}.createScriptedEntry(this, clsName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseEntryData(id:String)
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}.parseEntryData(this, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseEntryDataRaw(contents:String, ?fileName:String)
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}.parseEntryDataRaw(this, contents, fileName);
|
||||||
|
}
|
||||||
|
}).fields.filter((field) -> return !fieldAlreadyExists(field.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildEntryVariables(cls:ClassType, entryData:Dynamic):Array<Field>
|
||||||
|
{
|
||||||
|
var entryDataType:ComplexType = Context.getType('${entryData.module}.${entryData.name}').toComplexType();
|
||||||
|
|
||||||
|
return (macro class TempClass
|
||||||
|
{
|
||||||
|
public final id:String;
|
||||||
|
|
||||||
|
public final _data:Null<$entryDataType>;
|
||||||
|
}).fields.filter((field) -> return !fieldAlreadyExists(field.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildEntryMethods(cls:ClassType):Array<Field>
|
||||||
|
{
|
||||||
|
var impl:String = 'funkin.macro.impl._${cls.name}_Impl';
|
||||||
|
|
||||||
|
return (macro class TempClass
|
||||||
|
{
|
||||||
|
public function _fetchData(id:String)
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}._fetchData(this, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString()
|
||||||
|
{
|
||||||
|
return ${Context.parse(impl, Context.currentPos())}.toString(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
${Context.parse(impl, Context.currentPos())}.destroy(this);
|
||||||
|
}
|
||||||
|
}).fields.filter((field) -> !fieldAlreadyExists(field.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildRegistryImpl(cls:ClassType, entryCls:ClassType, scriptedEntryCls:ClassType, jsonCls:Dynamic):Void
|
||||||
|
{
|
||||||
|
var clsType:ComplexType = Context.getType('${cls.module}.${cls.name}').toComplexType();
|
||||||
|
|
||||||
|
var getScriptedClassName:String = '${scriptedEntryCls.module}.${scriptedEntryCls.name}';
|
||||||
|
|
||||||
|
var createScriptedEntry:String = '${scriptedEntryCls.module}.${scriptedEntryCls.name}.init(clsName, "unknown")';
|
||||||
|
|
||||||
|
var newJsonParser:String = 'new json2object.JsonParser<${jsonCls.module}.${jsonCls.name}>()';
|
||||||
|
|
||||||
|
Context.defineType(
|
||||||
|
{
|
||||||
|
pos: Context.currentPos(),
|
||||||
|
pack: ['funkin', 'macro', 'impl'],
|
||||||
|
name: '_${cls.name}_Impl',
|
||||||
|
kind: TypeDefKind.TDClass(null, [], false, false, false),
|
||||||
|
fields: (macro class TempClass
|
||||||
|
{
|
||||||
|
public static inline function getScriptedClassNames(me:$clsType)
|
||||||
|
{
|
||||||
|
return ${Context.parse(getScriptedClassName, Context.currentPos())}.listScriptClasses();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function createScriptedEntry(me:$clsType, clsName:String)
|
||||||
|
{
|
||||||
|
return ${Context.parse(createScriptedEntry, Context.currentPos())};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function parseEntryData(me:$clsType, id:String)
|
||||||
|
{
|
||||||
|
var parser = ${Context.parse(newJsonParser, Context.currentPos())};
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
switch (me.loadEntryFile(id))
|
||||||
|
{
|
||||||
|
case {fileName: fileName, contents: contents}:
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
@:privateAccess
|
||||||
|
me.printErrors(parser.errors, id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function parseEntryDataRaw(me:$clsType, contents:String, ?fileName:String)
|
||||||
|
{
|
||||||
|
var parser = ${Context.parse(newJsonParser, Context.currentPos())};
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
@:privateAccess
|
||||||
|
me.printErrors(parser.errors, fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
}).fields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildEntryImpl(cls:ClassType, registryCls:ClassType):Void
|
||||||
|
{
|
||||||
|
var clsType:ComplexType = Context.getType('${cls.module}.${cls.name}').toComplexType();
|
||||||
|
|
||||||
|
var registry:String = '${registryCls.module}.${registryCls.name}';
|
||||||
|
|
||||||
|
Context.defineType(
|
||||||
|
{
|
||||||
|
pos: Context.currentPos(),
|
||||||
|
pack: ['funkin', 'macro', 'impl'],
|
||||||
|
name: '_${cls.name}_Impl',
|
||||||
|
kind: TypeDefKind.TDClass(null, [], false, false, false),
|
||||||
|
fields: (macro class TempClass
|
||||||
|
{
|
||||||
|
public static inline function _fetchData(me:$clsType, id:String)
|
||||||
|
{
|
||||||
|
return $
|
||||||
|
{
|
||||||
|
Context.parse(registry, Context.currentPos())
|
||||||
|
}.instance.parseEntryDataWithMigration(id, ${Context.parse(registry, Context.currentPos())}.instance.fetchEntryVersion(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function toString(me:$clsType)
|
||||||
|
{
|
||||||
|
return $v{cls.name} + '(' + me.id + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function destroy(me:$clsType) {}
|
||||||
|
}).fields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
typedef RegistryTypeParamsNew =
|
||||||
|
{
|
||||||
|
var entryCls:ClassType;
|
||||||
|
var jsonCls:Dynamic; // DefType or ClassType
|
||||||
|
}
|
||||||
|
#end
|
Loading…
Reference in a new issue