diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8979e4de6..3d1f488f7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -110,6 +110,11 @@
"target": "windows",
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
},
+ {
+ "label": "Windows / Debug (Conversation Test)",
+ "target": "windows",
+ "args": ["-debug", "-DDIALOGUE"]
+ },
{
"label": "Windows / Debug (Straight to Chart Editor)",
"target": "windows",
diff --git a/Project.xml b/Project.xml
index f5d506688..9755a5e37 100644
--- a/Project.xml
+++ b/Project.xml
@@ -108,7 +108,6 @@
-
diff --git a/hmm.json b/hmm.json
index c8b1d911e..fa9a67057 100644
--- a/hmm.json
+++ b/hmm.json
@@ -156,11 +156,6 @@
"name": "thx.semver",
"type": "haxelib",
"version": "0.2.2"
- },
- {
- "name": "tink_json",
- "type": "haxelib",
- "version": "0.11.0"
}
]
}
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 23bc255f1..12a937b2a 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -21,9 +21,9 @@ import funkin.data.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
-import funkin.play.cutscene.dialogue.ConversationDataParser;
-import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
-import funkin.play.cutscene.dialogue.SpeakerDataParser;
+import funkin.data.dialogue.ConversationRegistry;
+import funkin.data.dialogue.DialogueBoxRegistry;
+import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.song.SongRegistry;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.modding.module.ModuleHandler;
@@ -208,22 +208,30 @@ class InitState extends FlxState
// GAME DATA PARSING
//
+ trace('Parsing game data...');
+
+ var perf_gameDataParse_start = haxe.Timer.stamp();
+
// NOTE: Registries and data parsers must be imported and not referenced with fully qualified names,
// to ensure build macros work properly.
SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries();
SongEventRegistry.loadEventCache();
- ConversationDataParser.loadConversationCache();
- DialogueBoxDataParser.loadDialogueBoxCache();
- SpeakerDataParser.loadSpeakerCache();
+ ConversationRegistry.instance.loadEntries();
+ DialogueBoxRegistry.instance.loadEntries();
+ SpeakerRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();
- CharacterDataParser.loadCharacterCache();
+ CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
ModuleHandler.buildModuleCallbacks();
ModuleHandler.loadModuleCache();
ModuleHandler.callOnCreate();
+
+ var perf_gameDataParse_end = haxe.Timer.stamp();
+
+ trace('Done parsing game data. Duration: ${perf_gameDataParse_end - perf_gameDataParse_start} seconds');
}
/**
@@ -241,6 +249,8 @@ class InitState extends FlxState
startLevel(defineLevel(), defineDifficulty());
#elseif FREEPLAY // -DFREEPLAY
FlxG.switchState(new FreeplayState());
+ #elseif DIALOGUE // -DDIALOGUE
+ FlxG.switchState(new funkin.ui.debug.dialogue.ConversationDebugState());
#elseif ANIMATE // -DANIMATE
FlxG.switchState(new funkin.ui.debug.anim.FlxAnimateTest());
#elseif WAVEFORM // -DWAVEFORM
diff --git a/source/funkin/data/DataParse.hx b/source/funkin/data/DataParse.hx
index 49dde0198..dbded3fba 100644
--- a/source/funkin/data/DataParse.hx
+++ b/source/funkin/data/DataParse.hx
@@ -120,6 +120,68 @@ class DataParse
}
}
+ public static function backdropData(json:Json, name:String):BackdropData
+ {
+ switch (json.value)
+ {
+ case JObject(fields):
+ var result:BackdropData = {};
+ var backdropType:String = '';
+
+ for (field in fields)
+ {
+ switch (field.name)
+ {
+ case 'backdropType':
+ backdropType = Tools.getValue(field.value);
+ }
+ Reflect.setField(result, field.name, Tools.getValue(field.value));
+ }
+
+ switch (backdropType)
+ {
+ case 'solid':
+ return SOLID(result);
+ default:
+ throw 'Expected Backdrop property $name to be specify a valid "type", but it was "${backdropType}".';
+ }
+
+ return null;
+ default:
+ throw 'Expected property $name to be an object, but it was ${json.value}.';
+ }
+ }
+
+ public static function outroData(json:Json, name:String):OutroData
+ {
+ switch (json.value)
+ {
+ case JObject(fields):
+ var result:OutroData = {};
+ var outroType:String = '';
+
+ for (field in fields)
+ {
+ switch (field.name)
+ {
+ case 'outroType':
+ outroType = Tools.getValue(field.value);
+ }
+ Reflect.setField(result, field.name, Tools.getValue(field.value));
+ }
+
+ switch (outroType)
+ {
+ case 'none':
+ return NONE(result);
+ case 'fade':
+ return FADE(result);
+ default:
+ throw 'Expected Outro property $name to be specify a valid "type", but it was "${outroType}".';
+ }
+ }
+ }
+
/**
* Parser which outputs a `Either`.
* Used by the FNF legacy JSON importer.
diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx
index 151e658b4..5488cbbbe 100644
--- a/source/funkin/modding/PolymodHandler.hx
+++ b/source/funkin/modding/PolymodHandler.hx
@@ -273,11 +273,11 @@ class PolymodHandler
LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries();
SongEventRegistry.loadEventCache();
- ConversationDataParser.loadConversationCache();
- DialogueBoxDataParser.loadDialogueBoxCache();
- SpeakerDataParser.loadSpeakerCache();
+ ConversationRegistry.instance.loadEntries();
+ DialogueBoxRegistry.instance.loadEntries();
+ SpeakerRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();
- CharacterDataParser.loadCharacterCache();
+ CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
ModuleHandler.loadModuleCache();
}
}
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index cc9debf13..aee9f2210 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -39,7 +39,7 @@ import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.cutscene.dialogue.Conversation;
-import funkin.play.cutscene.dialogue.ConversationDataParser;
+import funkin.data.dialogue.ConversationRegistry;
import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
import funkin.data.event.SongEventRegistry;
@@ -1662,7 +1662,7 @@ class PlayState extends MusicBeatSubState
{
isInCutscene = true;
- currentConversation = ConversationDataParser.fetchConversation(conversationId);
+ currentConversation = ConversationRegistry.instance.fetchEntry(conversationId);
if (currentConversation == null) return;
currentConversation.completeCallback = onConversationComplete;
diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx
index b2361c795..817c8caf3 100644
--- a/source/funkin/play/cutscene/dialogue/Conversation.hx
+++ b/source/funkin/play/cutscene/dialogue/Conversation.hx
@@ -13,7 +13,7 @@ import funkin.modding.IScriptedClass.IEventHandler;
import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.modding.events.ScriptEventDispatcher;
-import funkin.play.cutscene.dialogue.ConversationData.DialogueEntryData;
+import funkin.data.dialogue.ConversationData.DialogueEntryData;
import flixel.addons.display.FlxPieDial;
/**
diff --git a/source/funkin/play/cutscene/dialogue/ConversationData.hx b/source/funkin/play/cutscene/dialogue/ConversationData.hx
deleted file mode 100644
index 8c4aa9684..000000000
--- a/source/funkin/play/cutscene/dialogue/ConversationData.hx
+++ /dev/null
@@ -1,240 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import funkin.util.SerializerUtil;
-
-/**
- * Data about a conversation.
- * Includes what speakers are in the conversation, and what phrases they say.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.ConversationData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class ConversationData
-{
- public var version:String;
- public var backdrop:BackdropData;
- public var outro:OutroData;
- public var music:MusicData;
- public var dialogue:Array;
-
- public function new(version:String, backdrop:BackdropData, outro:OutroData, music:MusicData, dialogue:Array)
- {
- this.version = version;
- this.backdrop = backdrop;
- this.outro = outro;
- this.music = music;
- this.dialogue = dialogue;
- }
-
- public static function fromString(i:String):ConversationData
- {
- if (i == null || i == '') return null;
- var data:
- {
- version:String,
- backdrop:Dynamic, // TODO: tink.Json doesn't like when these are typed
- ?outro:Dynamic, // TODO: tink.Json doesn't like when these are typed
- ?music:Dynamic, // TODO: tink.Json doesn't like when these are typed
- dialogue:Array // TODO: tink.Json doesn't like when these are typed
- } = tink.Json.parse(i);
- return fromJson(data);
- }
-
- public static function fromJson(j:Dynamic):ConversationData
- {
- // TODO: Check version and perform migrations if necessary.
- if (j == null) return null;
- return new ConversationData(j.version, BackdropData.fromJson(j.backdrop), OutroData.fromJson(j.outro), MusicData.fromJson(j.music),
- j.dialogue.map(d -> DialogueEntryData.fromJson(d)));
- }
-
- public function toJson():Dynamic
- {
- return {
- version: this.version,
- backdrop: this.backdrop.toJson(),
- dialogue: this.dialogue.map(d -> d.toJson())
- };
- }
-}
-
-/**
- * Data about a single dialogue entry.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.ConversationData.DialogueEntryData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class DialogueEntryData
-{
- /**
- * The speaker who says this phrase.
- */
- public var speaker:String;
-
- /**
- * The animation the speaker will play.
- */
- public var speakerAnimation:String;
-
- /**
- * The text box that will appear.
- */
- public var box:String;
-
- /**
- * The animation the dialogue box will play.
- */
- public var boxAnimation:String;
-
- /**
- * The lines of text that will appear in the text box.
- */
- public var text:Array;
-
- /**
- * The relative speed at which the text will scroll.
- * @default 1.0
- */
- public var speed:Float = 1.0;
-
- public function new(speaker:String, speakerAnimation:String, box:String, boxAnimation:String, text:Array, speed:Float = null)
- {
- this.speaker = speaker;
- this.speakerAnimation = speakerAnimation;
- this.box = box;
- this.boxAnimation = boxAnimation;
- this.text = text;
- if (speed != null) this.speed = speed;
- }
-
- public static function fromJson(j:Dynamic):DialogueEntryData
- {
- if (j == null) return null;
- return new DialogueEntryData(j.speaker, j.speakerAnimation, j.box, j.boxAnimation, j.text, j.speed);
- }
-
- public function toJson():Dynamic
- {
- var result:Dynamic =
- {
- speaker: this.speaker,
- speakerAnimation: this.speakerAnimation,
- box: this.box,
- boxAnimation: this.boxAnimation,
- text: this.text,
- };
-
- if (this.speed != 1.0) result.speed = this.speed;
-
- return result;
- }
-}
-
-/**
- * Data about a backdrop.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.ConversationData.BackdropData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class BackdropData
-{
- public var type:BackdropType;
- public var data:Dynamic;
-
- public function new(typeStr:String, data:Dynamic)
- {
- this.type = typeStr;
- this.data = data;
- }
-
- public static function fromJson(j:Dynamic):BackdropData
- {
- if (j == null) return null;
- return new BackdropData(j.type, j.data);
- }
-
- public function toJson():Dynamic
- {
- return {
- type: this.type,
- data: this.data
- };
- }
-}
-
-enum abstract BackdropType(String) from String to String
-{
- public var SOLID:BackdropType = 'solid';
-}
-
-/**
- * Data about a music track.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.ConversationData.MusicData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class MusicData
-{
- public var asset:String;
-
- 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;
- this.looped = looped;
- this.fadeTime = fadeTime;
- }
-
- public static function fromJson(j:Dynamic):MusicData
- {
- if (j == null) return null;
- return new MusicData(j.asset, j.looped, j.fadeTime);
- }
-
- public function toJson():Dynamic
- {
- return {
- asset: this.asset,
- looped: this.looped,
- fadeTime: this.fadeTime
- };
- }
-}
-
-/**
- * Data about an outro.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.ConversationData.OutroData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class OutroData
-{
- public var type:OutroType;
- public var data:Dynamic;
-
- public function new(?typeStr:String, data:Dynamic)
- {
- this.type = typeStr ?? OutroType.NONE;
- this.data = data;
- }
-
- public static function fromJson(j:Dynamic):OutroData
- {
- if (j == null) return null;
- return new OutroData(j.type, j.data);
- }
-
- public function toJson():Dynamic
- {
- return {
- type: this.type,
- data: this.data
- };
- }
-}
-
-enum abstract OutroType(String) from String to String
-{
- public var NONE:OutroType = 'none';
- public var FADE:OutroType = 'fade';
-}
diff --git a/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx b/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx
deleted file mode 100644
index 9f80f8f9b..000000000
--- a/source/funkin/play/cutscene/dialogue/ConversationDataParser.hx
+++ /dev/null
@@ -1,163 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import openfl.Assets;
-import funkin.util.assets.DataAssets;
-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
-{
- public static final CONVERSATION_DATA_VERSION:String = '1.0.0';
- public static final CONVERSATION_DATA_VERSION_RULE:String = '1.0.x';
-
- static final conversationCache:Map = new Map();
- static final conversationScriptedClass:Map = new Map();
-
- static final DEFAULT_CONVERSATION_ID:String = 'UNKNOWN';
-
- /**
- * Parses and preloads the game's conversation data and scripts when the game starts.
- *
- * If you want to force conversations to be reloaded, you can just call this function again.
- */
- public static function loadConversationCache():Void
- {
- clearConversationCache();
- trace('Loading dialogue conversation cache...');
-
- //
- // SCRIPTED CONVERSATIONS
- //
- var scriptedConversationClassNames:Array = ScriptedConversation.listScriptClasses();
- trace(' Instantiating ${scriptedConversationClassNames.length} scripted conversations...');
- for (conversationCls in scriptedConversationClassNames)
- {
- var conversation:Conversation = ScriptedConversation.init(conversationCls, DEFAULT_CONVERSATION_ID);
- if (conversation != null)
- {
- trace(' Loaded scripted conversation: ${conversationCls}');
- // Disable the rendering logic for conversation until it's loaded.
- // Note that kill() =/= destroy()
- conversation.kill();
-
- // Then store it.
- conversationCache.set(conversation.conversationId, conversation);
- }
- else
- {
- trace(' Failed to instantiate scripted conversation class: ${conversationCls}');
- }
- }
-
- //
- // UNSCRIPTED CONVERSATIONS
- //
- // Scripts refers to code here, not the actual dialogue.
- var conversationIdList:Array = DataAssets.listDataFilesInPath('dialogue/conversations/');
- // Filter out conversations that are scripted.
- var unscriptedConversationIds:Array = conversationIdList.filter(function(conversationId:String):Bool {
- return !conversationCache.exists(conversationId);
- });
- trace(' Fetching data for ${unscriptedConversationIds.length} conversations...');
- for (conversationId in unscriptedConversationIds)
- {
- try
- {
- var conversation:Conversation = new Conversation(conversationId);
- // Say something offensive to kill the conversation.
- // We will revive it later.
- conversation.kill();
- if (conversation != null)
- {
- trace(' Loaded conversation data: ${conversation.conversationId}');
- conversationCache.set(conversation.conversationId, conversation);
- }
- }
- catch (e)
- {
- trace(e);
- continue;
- }
- }
- }
-
- /**
- * Fetches data for a conversation and returns a Conversation instance,
- * ready to be displayed.
- * @param conversationId The ID of the conversation to fetch.
- * @return The conversation instance, or null if the conversation was not found.
- */
- public static function fetchConversation(conversationId:String):Null
- {
- if (conversationId != null && conversationId != '' && conversationCache.exists(conversationId))
- {
- trace('Successfully fetched conversation: ${conversationId}');
- var conversation:Conversation = conversationCache.get(conversationId);
- // ...ANYway...
- conversation.revive();
- return conversation;
- }
- else
- {
- trace('Failed to fetch conversation, not found in cache: ${conversationId}');
- return null;
- }
- }
-
- static function clearConversationCache():Void
- {
- if (conversationCache != null)
- {
- for (conversation in conversationCache)
- {
- conversation.destroy();
- }
- conversationCache.clear();
- }
- }
-
- public static function listConversationIds():Array
- {
- return conversationCache.keys().array();
- }
-
- /**
- * Load a conversation's JSON file, parse its data, and return it.
- *
- * @param conversationId The conversation to load.
- * @return The conversation data, or null if validation failed.
- */
- public static function parseConversationData(conversationId:String):Null
- {
- trace('Parsing conversation data: ${conversationId}');
- var rawJson:String = loadConversationFile(conversationId);
-
- try
- {
- var conversationData:ConversationData = ConversationData.fromString(rawJson);
- return conversationData;
- }
- catch (e)
- {
- trace('Failed to parse conversation ($conversationId).');
- trace(e);
- return null;
- }
- }
-
- static function loadConversationFile(conversationPath:String):String
- {
- var conversationFilePath:String = Paths.json('dialogue/conversations/${conversationPath}');
- var rawJson:String = Assets.getText(conversationFilePath).trim();
-
- while (!rawJson.endsWith('}') && rawJson.length > 0)
- {
- rawJson = rawJson.substr(0, rawJson.length - 1);
- }
-
- return rawJson;
- }
-}
diff --git a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx b/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx
deleted file mode 100644
index 13697b9f4..000000000
--- a/source/funkin/play/cutscene/dialogue/ConversationDebugState.hx
+++ /dev/null
@@ -1,61 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import flixel.FlxState;
-import funkin.modding.events.ScriptEventDispatcher;
-import funkin.modding.events.ScriptEvent;
-import flixel.util.FlxColor;
-import funkin.ui.MusicBeatState;
-
-/**
- * A state with displays a conversation with no background.
- * Used for testing.
- * @param conversationId The conversation to display.
- */
-class ConversationDebugState extends MusicBeatState
-{
- final conversationId:String = 'senpai';
-
- var conversation:Conversation;
-
- public function new()
- {
- super();
-
- // TODO: Fix this BS
- Paths.setCurrentLevel('week6');
- }
-
- public override function create():Void
- {
- conversation = ConversationDataParser.fetchConversation(conversationId);
- conversation.completeCallback = onConversationComplete;
- add(conversation);
-
- ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
- }
-
- function onConversationComplete():Void
- {
- remove(conversation);
- conversation = null;
- }
-
- public override function update(elapsed:Float):Void
- {
- super.update(elapsed);
-
- if (conversation != null)
- {
- if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation();
-
- if (controls.CUTSCENE_SKIP)
- {
- conversation.trySkipConversation(elapsed);
- }
- else
- {
- conversation.trySkipConversation(-1);
- }
- }
- }
-}
diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx
index cdac3c233..4df34badc 100644
--- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx
+++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx
@@ -235,7 +235,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
{
textDisplay = new FlxTypeText(0, 0, 300, '', 32);
textDisplay.fieldWidth = boxData.text.width;
- textDisplay.setFormat('Pixel Arial 11 Bold', boxData.text.size, FlxColor.fromString(boxData.text.color), LEFT, SHADOW,
+ textDisplay.setFormat(boxData.text.fontFamily, boxData.text.size, FlxColor.fromString(boxData.text.color), LEFT, SHADOW,
FlxColor.fromString(boxData.text.shadowColor ?? '#00000000'), false);
textDisplay.borderSize = boxData.text.shadowWidth ?? 2;
textDisplay.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)];
diff --git a/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx b/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx
deleted file mode 100644
index 801a01dd7..000000000
--- a/source/funkin/play/cutscene/dialogue/DialogueBoxData.hx
+++ /dev/null
@@ -1,124 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import funkin.data.animation.AnimationData;
-import funkin.util.SerializerUtil;
-
-/**
- * Data about a text box.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.DialogueBoxData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class DialogueBoxData
-{
- public var version:String;
- public var name:String;
- public var assetPath:String;
- public var flipX:Bool;
- public var flipY:Bool;
- public var isPixel:Bool;
- public var offsets:Array;
- public var text:DialogueBoxTextData;
- public var scale:Float;
- public var animations:Array;
-
- public function new(version:String, name:String, assetPath:String, flipX:Bool = false, flipY:Bool = false, isPixel:Bool = false, offsets:Null>,
- text:DialogueBoxTextData, scale:Float = 1.0, animations:Array)
- {
- this.version = version;
- this.name = name;
- this.assetPath = assetPath;
- this.flipX = flipX;
- this.flipY = flipY;
- this.isPixel = isPixel;
- this.offsets = offsets ?? [0, 0];
- this.text = text;
- this.scale = scale;
- this.animations = animations;
- }
-
- public static function fromString(i:String):DialogueBoxData
- {
- if (i == null || i == '') return null;
- var data:
- {
- version:String,
- name:String,
- assetPath:String,
- flipX:Bool,
- flipY:Bool,
- isPixel:Bool,
- ?offsets:Array,
- text:Dynamic,
- scale:Float,
- animations:Array
- } = tink.Json.parse(i);
- return fromJson(data);
- }
-
- public static function fromJson(j:Dynamic):DialogueBoxData
- {
- // TODO: Check version and perform migrations if necessary.
- if (j == null) return null;
- return new DialogueBoxData(j.version, j.name, j.assetPath, j.flipX, j.flipY, j.isPixel, j.offsets, DialogueBoxTextData.fromJson(j.text), j.scale,
- j.animations);
- }
-
- public function toJson():Dynamic
- {
- return {
- version: this.version,
- name: this.name,
- assetPath: this.assetPath,
- flipX: this.flipX,
- flipY: this.flipY,
- isPixel: this.isPixel,
- offsets: this.offsets,
- scale: this.scale,
- animations: this.animations
- };
- }
-}
-
-/**
- * Data about text in a text box.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.DialogueBoxTextData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class DialogueBoxTextData
-{
- public var offsets:Array;
- public var width:Int;
- public var size:Int;
- public var color:String;
- public var shadowColor:Null;
- public var shadowWidth:Null;
-
- public function new(offsets:Null>, ?width:Int, ?size:Int, color:String, ?shadowColor:String, shadowWidth:Null)
- {
- this.offsets = offsets ?? [0, 0];
- this.width = width ?? 300;
- this.size = size ?? 32;
- this.color = color;
- this.shadowColor = shadowColor;
- this.shadowWidth = shadowWidth;
- }
-
- public static function fromJson(j:Dynamic):DialogueBoxTextData
- {
- // TODO: Check version and perform migrations if necessary.
- if (j == null) return null;
- return new DialogueBoxTextData(j.offsets, j.width, j.size, j.color, j.shadowColor, j.shadowWidth);
- }
-
- public function toJson():Dynamic
- {
- return {
- offsets: this.offsets,
- width: this.width,
- size: this.size,
- color: this.color,
- shadowColor: this.shadowColor,
- shadowWidth: this.shadowWidth,
- };
- }
-}
diff --git a/source/funkin/play/cutscene/dialogue/DialogueBoxDataParser.hx b/source/funkin/play/cutscene/dialogue/DialogueBoxDataParser.hx
deleted file mode 100644
index cb00dd80d..000000000
--- a/source/funkin/play/cutscene/dialogue/DialogueBoxDataParser.hx
+++ /dev/null
@@ -1,159 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import openfl.Assets;
-import funkin.util.assets.DataAssets;
-import funkin.play.cutscene.dialogue.DialogueBox;
-import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
-
-/**
- * Contains utilities for loading and parsing dialogueBox data.
- */
-class DialogueBoxDataParser
-{
- public static final DIALOGUE_BOX_DATA_VERSION:String = '1.0.0';
- public static final DIALOGUE_BOX_DATA_VERSION_RULE:String = '1.0.x';
-
- static final dialogueBoxCache:Map = new Map();
-
- static final dialogueBoxScriptedClass:Map = new Map();
-
- static final DEFAULT_DIALOGUE_BOX_ID:String = 'UNKNOWN';
-
- /**
- * Parses and preloads the game's dialogueBox data and scripts when the game starts.
- *
- * If you want to force dialogue boxes to be reloaded, you can just call this function again.
- */
- public static function loadDialogueBoxCache():Void
- {
- clearDialogueBoxCache();
- trace('Loading dialogue box cache...');
-
- //
- // SCRIPTED CONVERSATIONS
- //
- var scriptedDialogueBoxClassNames:Array = ScriptedDialogueBox.listScriptClasses();
- trace(' Instantiating ${scriptedDialogueBoxClassNames.length} scripted dialogue boxes...');
- for (dialogueBoxCls in scriptedDialogueBoxClassNames)
- {
- var dialogueBox:DialogueBox = ScriptedDialogueBox.init(dialogueBoxCls, DEFAULT_DIALOGUE_BOX_ID);
- if (dialogueBox != null)
- {
- trace(' Loaded scripted dialogue box: ${dialogueBox.dialogueBoxName}');
- // Disable the rendering logic for dialogueBox until it's loaded.
- // Note that kill() =/= destroy()
- dialogueBox.kill();
-
- // Then store it.
- dialogueBoxCache.set(dialogueBox.dialogueBoxId, dialogueBox);
- }
- else
- {
- trace(' Failed to instantiate scripted dialogueBox class: ${dialogueBoxCls}');
- }
- }
-
- //
- // UNSCRIPTED CONVERSATIONS
- //
- // Scripts refers to code here, not the actual dialogue.
- var dialogueBoxIdList:Array = DataAssets.listDataFilesInPath('dialogue/boxes/');
- // Filter out dialogue boxes that are scripted.
- var unscriptedDialogueBoxIds:Array = dialogueBoxIdList.filter(function(dialogueBoxId:String):Bool {
- return !dialogueBoxCache.exists(dialogueBoxId);
- });
- trace(' Fetching data for ${unscriptedDialogueBoxIds.length} dialogue boxes...');
- for (dialogueBoxId in unscriptedDialogueBoxIds)
- {
- try
- {
- var dialogueBox:DialogueBox = new DialogueBox(dialogueBoxId);
- if (dialogueBox != null)
- {
- trace(' Loaded dialogueBox data: ${dialogueBox.dialogueBoxName}');
- dialogueBoxCache.set(dialogueBox.dialogueBoxId, dialogueBox);
- }
- }
- catch (e)
- {
- trace(e);
- continue;
- }
- }
- }
-
- /**
- * Fetches data for a dialogueBox and returns a DialogueBox instance,
- * ready to be displayed.
- * @param dialogueBoxId The ID of the dialogueBox to fetch.
- * @return The dialogueBox instance, or null if the dialogueBox was not found.
- */
- public static function fetchDialogueBox(dialogueBoxId:String):Null
- {
- if (dialogueBoxId != null && dialogueBoxId != '' && dialogueBoxCache.exists(dialogueBoxId))
- {
- trace('Successfully fetched dialogueBox: ${dialogueBoxId}');
- var dialogueBox:DialogueBox = dialogueBoxCache.get(dialogueBoxId);
- dialogueBox.revive();
- return dialogueBox;
- }
- else
- {
- trace('Failed to fetch dialogueBox, not found in cache: ${dialogueBoxId}');
- return null;
- }
- }
-
- static function clearDialogueBoxCache():Void
- {
- if (dialogueBoxCache != null)
- {
- for (dialogueBox in dialogueBoxCache)
- {
- dialogueBox.destroy();
- }
- dialogueBoxCache.clear();
- }
- }
-
- public static function listDialogueBoxIds():Array
- {
- return dialogueBoxCache.keys().array();
- }
-
- /**
- * Load a dialogueBox's JSON file, parse its data, and return it.
- *
- * @param dialogueBoxId The dialogueBox to load.
- * @return The dialogueBox data, or null if validation failed.
- */
- public static function parseDialogueBoxData(dialogueBoxId:String):Null
- {
- var rawJson:String = loadDialogueBoxFile(dialogueBoxId);
-
- try
- {
- var dialogueBoxData:DialogueBoxData = DialogueBoxData.fromString(rawJson);
- return dialogueBoxData;
- }
- catch (e)
- {
- trace('Failed to parse dialogueBox ($dialogueBoxId).');
- trace(e);
- return null;
- }
- }
-
- static function loadDialogueBoxFile(dialogueBoxPath:String):String
- {
- var dialogueBoxFilePath:String = Paths.json('dialogue/boxes/${dialogueBoxPath}');
- var rawJson:String = Assets.getText(dialogueBoxFilePath).trim();
-
- while (!rawJson.endsWith('}') && rawJson.length > 0)
- {
- rawJson = rawJson.substr(0, rawJson.length - 1);
- }
-
- return rawJson;
- }
-}
diff --git a/source/funkin/play/cutscene/dialogue/ScriptedConversation.hx b/source/funkin/play/cutscene/dialogue/ScriptedConversation.hx
index 4fe383a5e..cb7344273 100644
--- a/source/funkin/play/cutscene/dialogue/ScriptedConversation.hx
+++ b/source/funkin/play/cutscene/dialogue/ScriptedConversation.hx
@@ -1,4 +1,10 @@
package funkin.play.cutscene.dialogue;
+/**
+ * A script that can be tied to a Conversation.
+ * Create a scripted class that extends Conversation to use this.
+ * This allows you to customize how a specific conversation appears and behaves.
+ * Someone clever could use this to add branching dialogue I think.
+ */
@:hscriptClass
class ScriptedConversation extends Conversation implements polymod.hscript.HScriptedClass {}
diff --git a/source/funkin/play/cutscene/dialogue/ScriptedDialogueBox.hx b/source/funkin/play/cutscene/dialogue/ScriptedDialogueBox.hx
index a1b36c7c2..7689fc0d9 100644
--- a/source/funkin/play/cutscene/dialogue/ScriptedDialogueBox.hx
+++ b/source/funkin/play/cutscene/dialogue/ScriptedDialogueBox.hx
@@ -1,4 +1,9 @@
package funkin.play.cutscene.dialogue;
+/**
+ * A script that can be tied to a DialogueBox.
+ * Create a scripted class that extends DialogueBox to use this.
+ * This allows you to customize how a specific dialogue box appears.
+ */
@:hscriptClass
class ScriptedDialogueBox extends DialogueBox implements polymod.hscript.HScriptedClass {}
diff --git a/source/funkin/play/cutscene/dialogue/SpeakerData.hx b/source/funkin/play/cutscene/dialogue/SpeakerData.hx
deleted file mode 100644
index 88883ead8..000000000
--- a/source/funkin/play/cutscene/dialogue/SpeakerData.hx
+++ /dev/null
@@ -1,78 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import funkin.data.animation.AnimationData;
-
-/**
- * Data about a conversation.
- * Includes what speakers are in the conversation, and what phrases they say.
- */
-@:jsonParse(j -> funkin.play.cutscene.dialogue.SpeakerData.fromJson(j))
-@:jsonStringify(v -> v.toJson())
-class SpeakerData
-{
- public var version:String;
- public var name:String;
- public var assetPath:String;
- public var flipX:Bool;
- public var isPixel:Bool;
- public var offsets:Array;
- public var scale:Float;
- public var animations:Array;
-
- public function new(version:String, name:String, assetPath:String, animations:Array, ?offsets:Array, flipX:Bool = false,
- isPixel:Bool = false, ?scale:Float = 1.0)
- {
- this.version = version;
- this.name = name;
- this.assetPath = assetPath;
- this.animations = animations;
-
- this.offsets = offsets;
- if (this.offsets == null || this.offsets == []) this.offsets = [0, 0];
-
- this.flipX = flipX;
- this.isPixel = isPixel;
- this.scale = scale;
- }
-
- public static function fromString(i:String):SpeakerData
- {
- if (i == null || i == '') return null;
- var data:
- {
- version:String,
- name:String,
- assetPath:String,
- animations:Array,
- ?offsets:Array,
- ?flipX:Bool,
- ?isPixel:Bool,
- ?scale:Float
- } = tink.Json.parse(i);
- return fromJson(data);
- }
-
- public static function fromJson(j:Dynamic):SpeakerData
- {
- // TODO: Check version and perform migrations if necessary.
- if (j == null) return null;
- return new SpeakerData(j.version, j.name, j.assetPath, j.animations, j.offsets, j.flipX, j.isPixel, j.scale);
- }
-
- public function toJson():Dynamic
- {
- var result:Dynamic =
- {
- version: this.version,
- name: this.name,
- assetPath: this.assetPath,
- animations: this.animations,
- flipX: this.flipX,
- isPixel: this.isPixel
- };
-
- if (this.scale != 1.0) result.scale = this.scale;
-
- return result;
- }
-}
diff --git a/source/funkin/play/cutscene/dialogue/SpeakerDataParser.hx b/source/funkin/play/cutscene/dialogue/SpeakerDataParser.hx
deleted file mode 100644
index f7ddb099f..000000000
--- a/source/funkin/play/cutscene/dialogue/SpeakerDataParser.hx
+++ /dev/null
@@ -1,159 +0,0 @@
-package funkin.play.cutscene.dialogue;
-
-import openfl.Assets;
-import funkin.util.assets.DataAssets;
-import funkin.play.cutscene.dialogue.Speaker;
-import funkin.play.cutscene.dialogue.ScriptedSpeaker;
-
-/**
- * Contains utilities for loading and parsing speaker data.
- */
-class SpeakerDataParser
-{
- public static final SPEAKER_DATA_VERSION:String = '1.0.0';
- public static final SPEAKER_DATA_VERSION_RULE:String = '1.0.x';
-
- static final speakerCache:Map = new Map();
-
- static final speakerScriptedClass:Map = new Map();
-
- static final DEFAULT_SPEAKER_ID:String = 'UNKNOWN';
-
- /**
- * Parses and preloads the game's speaker data and scripts when the game starts.
- *
- * If you want to force speakers to be reloaded, you can just call this function again.
- */
- public static function loadSpeakerCache():Void
- {
- clearSpeakerCache();
- trace('Loading dialogue speaker cache...');
-
- //
- // SCRIPTED CONVERSATIONS
- //
- var scriptedSpeakerClassNames:Array = ScriptedSpeaker.listScriptClasses();
- trace(' Instantiating ${scriptedSpeakerClassNames.length} scripted speakers...');
- for (speakerCls in scriptedSpeakerClassNames)
- {
- var speaker:Speaker = ScriptedSpeaker.init(speakerCls, DEFAULT_SPEAKER_ID);
- if (speaker != null)
- {
- trace(' Loaded scripted speaker: ${speaker.speakerName}');
- // Disable the rendering logic for speaker until it's loaded.
- // Note that kill() =/= destroy()
- speaker.kill();
-
- // Then store it.
- speakerCache.set(speaker.speakerId, speaker);
- }
- else
- {
- trace(' Failed to instantiate scripted speaker class: ${speakerCls}');
- }
- }
-
- //
- // UNSCRIPTED CONVERSATIONS
- //
- // Scripts refers to code here, not the actual dialogue.
- var speakerIdList:Array = DataAssets.listDataFilesInPath('dialogue/speakers/');
- // Filter out speakers that are scripted.
- var unscriptedSpeakerIds:Array = speakerIdList.filter(function(speakerId:String):Bool {
- return !speakerCache.exists(speakerId);
- });
- trace(' Fetching data for ${unscriptedSpeakerIds.length} speakers...');
- for (speakerId in unscriptedSpeakerIds)
- {
- try
- {
- var speaker:Speaker = new Speaker(speakerId);
- if (speaker != null)
- {
- trace(' Loaded speaker data: ${speaker.speakerName}');
- speakerCache.set(speaker.speakerId, speaker);
- }
- }
- catch (e)
- {
- trace(e);
- continue;
- }
- }
- }
-
- /**
- * Fetches data for a speaker and returns a Speaker instance,
- * ready to be displayed.
- * @param speakerId The ID of the speaker to fetch.
- * @return The speaker instance, or null if the speaker was not found.
- */
- public static function fetchSpeaker(speakerId:String):Null
- {
- if (speakerId != null && speakerId != '' && speakerCache.exists(speakerId))
- {
- trace('Successfully fetched speaker: ${speakerId}');
- var speaker:Speaker = speakerCache.get(speakerId);
- speaker.revive();
- return speaker;
- }
- else
- {
- trace('Failed to fetch speaker, not found in cache: ${speakerId}');
- return null;
- }
- }
-
- static function clearSpeakerCache():Void
- {
- if (speakerCache != null)
- {
- for (speaker in speakerCache)
- {
- speaker.destroy();
- }
- speakerCache.clear();
- }
- }
-
- public static function listSpeakerIds():Array
- {
- return speakerCache.keys().array();
- }
-
- /**
- * Load a speaker's JSON file, parse its data, and return it.
- *
- * @param speakerId The speaker to load.
- * @return The speaker data, or null if validation failed.
- */
- public static function parseSpeakerData(speakerId:String):Null
- {
- var rawJson:String = loadSpeakerFile(speakerId);
-
- try
- {
- var speakerData:SpeakerData = SpeakerData.fromString(rawJson);
- return speakerData;
- }
- catch (e)
- {
- trace('Failed to parse speaker ($speakerId).');
- trace(e);
- return null;
- }
- }
-
- static function loadSpeakerFile(speakerPath:String):String
- {
- var speakerFilePath:String = Paths.json('dialogue/speakers/${speakerPath}');
- var rawJson:String = Assets.getText(speakerFilePath).trim();
-
- while (!rawJson.endsWith('}') && rawJson.length > 0)
- {
- rawJson = rawJson.substr(0, rawJson.length - 1);
- }
-
- return rawJson;
- }
-}
diff --git a/tests/unit/project.xml b/tests/unit/project.xml
index 2e505e015..dfbf06502 100644
--- a/tests/unit/project.xml
+++ b/tests/unit/project.xml
@@ -27,7 +27,6 @@
-