mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
Rework Conversation data parsing
This commit is contained in:
parent
fa556dc1f2
commit
31cd5b3414
17 changed files with 849 additions and 113 deletions
|
@ -208,30 +208,29 @@ 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,
|
||||
// NOTE: Registries must be imported and not referenced with fully qualified names,
|
||||
// to ensure build macros work properly.
|
||||
trace('Parsing game data...');
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventRegistry.loadEventCache();
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
|
||||
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers.
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
ModuleHandler.callOnCreate();
|
||||
|
||||
var perf_gameDataParse_end = haxe.Timer.stamp();
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
|
||||
trace('Done parsing game data. Duration: ${perf_gameDataParse_end - perf_gameDataParse_start} seconds');
|
||||
trace('Parsing game data took ${Math.floor((perfEnd - perfStart) * 1000)}ms.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -46,6 +46,9 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
this.entries = new Map<String, T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Create a `loadEntriesAsync()` function.
|
||||
*/
|
||||
public function loadEntries():Void
|
||||
{
|
||||
clearEntries();
|
||||
|
@ -54,7 +57,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
// SCRIPTED ENTRIES
|
||||
//
|
||||
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
|
||||
log('Registering ${scriptedEntryClassNames.length} scripted entries...');
|
||||
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
|
||||
for (entryCls in scriptedEntryClassNames)
|
||||
{
|
||||
|
@ -78,7 +81,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
|
||||
return !entries.exists(entryId);
|
||||
});
|
||||
log('Fetching data for ${unscriptedEntryIds.length} unscripted entries...');
|
||||
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
|
||||
for (entryId in unscriptedEntryIds)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -120,19 +120,19 @@ class DataParse
|
|||
}
|
||||
}
|
||||
|
||||
public static function backdropData(json:Json, name:String):BackdropData
|
||||
public static function backdropData(json:Json, name:String):funkin.data.dialogue.ConversationData.BackdropData
|
||||
{
|
||||
switch (json.value)
|
||||
{
|
||||
case JObject(fields):
|
||||
var result:BackdropData = {};
|
||||
var result:Dynamic = {};
|
||||
var backdropType:String = '';
|
||||
|
||||
for (field in fields)
|
||||
{
|
||||
switch (field.name)
|
||||
{
|
||||
case 'backdropType':
|
||||
case 'type':
|
||||
backdropType = Tools.getValue(field.value);
|
||||
}
|
||||
Reflect.setField(result, field.name, Tools.getValue(field.value));
|
||||
|
@ -152,19 +152,19 @@ class DataParse
|
|||
}
|
||||
}
|
||||
|
||||
public static function outroData(json:Json, name:String):OutroData
|
||||
public static function outroData(json:Json, name:String):Null<funkin.data.dialogue.ConversationData.OutroData>
|
||||
{
|
||||
switch (json.value)
|
||||
{
|
||||
case JObject(fields):
|
||||
var result:OutroData = {};
|
||||
var result:Dynamic = {};
|
||||
var outroType:String = '';
|
||||
|
||||
for (field in fields)
|
||||
{
|
||||
switch (field.name)
|
||||
{
|
||||
case 'outroType':
|
||||
case 'type':
|
||||
outroType = Tools.getValue(field.value);
|
||||
}
|
||||
Reflect.setField(result, field.name, Tools.getValue(field.value));
|
||||
|
@ -179,6 +179,9 @@ class DataParse
|
|||
default:
|
||||
throw 'Expected Outro property $name to be specify a valid "type", but it was "${outroType}".';
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw 'Expected property $name to be an object, but it was ${json.value}.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
168
source/funkin/data/dialogue/ConversationData.hx
Normal file
168
source/funkin/data/dialogue/ConversationData.hx
Normal file
|
@ -0,0 +1,168 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
/**
|
||||
* A type definition for the data for a specific conversation.
|
||||
* It includes things like what dialogue boxes to use, what text to display, and what animations to play.
|
||||
* @see https://lib.haxe.org/p/json2object/
|
||||
*/
|
||||
typedef ConversationData =
|
||||
{
|
||||
/**
|
||||
* Semantic version for conversation data.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* Data on the backdrop for the conversation.
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.backdropData)
|
||||
public var backdrop:BackdropData;
|
||||
|
||||
/**
|
||||
* Data on the outro for the conversation.
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.outroData)
|
||||
@:optional
|
||||
public var outro:Null<OutroData>;
|
||||
|
||||
/**
|
||||
* Data on the music for the conversation.
|
||||
*/
|
||||
@:optional
|
||||
public var music:Null<MusicData>;
|
||||
|
||||
/**
|
||||
* Data for each line of dialogue in the conversation.
|
||||
*/
|
||||
public var dialogue:Array<DialogueEntryData>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data on the backdrop for the conversation, behind the dialogue box.
|
||||
* A custom parser distinguishes between backdrop types based on the `type` field.
|
||||
*/
|
||||
enum BackdropData
|
||||
{
|
||||
SOLID(data:BackdropData_Solid); // 'solid'
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a Solid color backdrop.
|
||||
*/
|
||||
typedef BackdropData_Solid =
|
||||
{
|
||||
/**
|
||||
* Used to distinguish between backdrop types. Should always be `solid` for this type.
|
||||
*/
|
||||
var type:String;
|
||||
|
||||
/**
|
||||
* The color of the backdrop.
|
||||
*/
|
||||
var color:String;
|
||||
|
||||
/**
|
||||
* Fade-in time for the backdrop.
|
||||
* @default No fade-in
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var fadeTime:Float;
|
||||
};
|
||||
|
||||
enum OutroData
|
||||
{
|
||||
NONE(data:OutroData_None); // 'none'
|
||||
FADE(data:OutroData_Fade); // 'fade'
|
||||
}
|
||||
|
||||
typedef OutroData_None =
|
||||
{
|
||||
/**
|
||||
* Used to distinguish between outro types. Should always be `none` for this type.
|
||||
*/
|
||||
var type:String;
|
||||
}
|
||||
|
||||
typedef OutroData_Fade =
|
||||
{
|
||||
/**
|
||||
* Used to distinguish between outro types. Should always be `fade` for this type.
|
||||
*/
|
||||
var type:String;
|
||||
|
||||
/**
|
||||
* The time to fade out the conversation.
|
||||
* @default 1 second
|
||||
*/
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
var fadeTime:Float;
|
||||
}
|
||||
|
||||
typedef MusicData =
|
||||
{
|
||||
/**
|
||||
* The asset to play for the music.
|
||||
*/
|
||||
var asset:String;
|
||||
|
||||
/**
|
||||
* The time to fade in the music.
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var fadeTime:Float;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
var looped:Bool;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data on a single line of dialogue in a conversation.
|
||||
*/
|
||||
typedef DialogueEntryData =
|
||||
{
|
||||
/**
|
||||
* Which speaker is speaking.
|
||||
* @see `SpeakerData.hx`
|
||||
*/
|
||||
public var speaker:String;
|
||||
|
||||
/**
|
||||
* The animation the speaker should play for this line of dialogue.
|
||||
*/
|
||||
public var speakerAnimation:String;
|
||||
|
||||
/**
|
||||
* Which dialogue box to use for this line of dialogue.
|
||||
* @see `DialogueBoxData.hx`
|
||||
*/
|
||||
public var box:String;
|
||||
|
||||
/**
|
||||
* Which animation to play for the dialogue box.
|
||||
*/
|
||||
public var boxAnimation:String;
|
||||
|
||||
/**
|
||||
* The text that will display for this line of dialogue.
|
||||
* Text will automatically wrap.
|
||||
* When the user advances the dialogue, the next entry in the array will concatenate on.
|
||||
* Advancing when the last entry is displayed will move to the next `DialogueEntryData`,
|
||||
* or end the conversation if there are no more.
|
||||
*/
|
||||
public var text:Array<String>;
|
||||
|
||||
/**
|
||||
* The relative speed at which text gets "typed out".
|
||||
* Setting `speed` to `1.5` would make it look like the character is speaking quickly,
|
||||
* and setting `speed` to `0.5` would make it look like the character is emphasizing each word.
|
||||
*/
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
public var speed:Float;
|
||||
};
|
81
source/funkin/data/dialogue/ConversationRegistry.hx
Normal file
81
source/funkin/data/dialogue/ConversationRegistry.hx
Normal file
|
@ -0,0 +1,81 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.data.dialogue.ConversationData;
|
||||
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
||||
|
||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the dialogue box data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateConversationData()` function.
|
||||
*/
|
||||
public static final CONVERSATION_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static final instance:ConversationRegistry = new ConversationRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
128
source/funkin/data/dialogue/DialogueBoxData.hx
Normal file
128
source/funkin/data/dialogue/DialogueBoxData.hx
Normal file
|
@ -0,0 +1,128 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
/**
|
||||
* A type definition for the data for a conversation text box.
|
||||
* It includes things like the sprite to use, and the font and color for the text.
|
||||
* The actual text is included in the ConversationData.
|
||||
* @see https://lib.haxe.org/p/json2object/
|
||||
*/
|
||||
typedef DialogueBoxData =
|
||||
{
|
||||
/**
|
||||
* Semantic version for dialogue box data.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* A human readable name for the dialogue box type.
|
||||
*/
|
||||
public var name:String;
|
||||
|
||||
/**
|
||||
* The asset path for the sprite to use for the dialogue box.
|
||||
* Takes a static sprite or a sprite sheet.
|
||||
*/
|
||||
public var assetPath:String;
|
||||
|
||||
/**
|
||||
* Whether to horizontally flip the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var flipX:Bool;
|
||||
|
||||
/**
|
||||
* Whether to vertically flip the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var flipY:Bool;
|
||||
|
||||
/**
|
||||
* Whether to disable anti-aliasing for the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* The relative horizontal and vertical offsets for the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
public var offsets:Array<Float>;
|
||||
|
||||
/**
|
||||
* Info about how to display text in the dialogue box.
|
||||
*/
|
||||
public var text:DialogueBoxTextData;
|
||||
|
||||
/**
|
||||
* Multiply the size of the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(1)
|
||||
public var scale:Float;
|
||||
|
||||
/**
|
||||
* If using a spritesheet for the dialogue box, the animations to use.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var animations:Array<AnimationData>;
|
||||
}
|
||||
|
||||
typedef DialogueBoxTextData =
|
||||
{
|
||||
/**
|
||||
* The position of the text in teh box.
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
/**
|
||||
* The width of the
|
||||
*/
|
||||
@:optional
|
||||
@:default(300)
|
||||
var width:Int;
|
||||
|
||||
/**
|
||||
* The font size to use for the text.
|
||||
*/
|
||||
@:optional
|
||||
@:default(32)
|
||||
var size:Int;
|
||||
|
||||
/**
|
||||
* The color to use for the text.
|
||||
* Use a string that can be translated to a color, like `#FF0000` for red.
|
||||
*/
|
||||
@:optional
|
||||
@:default("#000000")
|
||||
var color:String;
|
||||
|
||||
/**
|
||||
* The font to use for the text.
|
||||
* @since v1.1.0
|
||||
* @default `Arial`, make sure to switch this!
|
||||
*/
|
||||
@:optional
|
||||
@:default("Arial")
|
||||
var fontFamily:String;
|
||||
|
||||
/**
|
||||
* The color to use for the shadow of the text. Use transparent to disable.
|
||||
*/
|
||||
var shadowColor:String;
|
||||
|
||||
/**
|
||||
* The width of the shadow of the text.
|
||||
*/
|
||||
@:optional
|
||||
@:default(0)
|
||||
var shadowWidth:Int;
|
||||
};
|
81
source/funkin/data/dialogue/DialogueBoxRegistry.hx
Normal file
81
source/funkin/data/dialogue/DialogueBoxRegistry.hx
Normal file
|
@ -0,0 +1,81 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
||||
|
||||
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the dialogue box data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateDialogueBoxData()` function.
|
||||
*/
|
||||
public static final DIALOGUEBOX_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||
|
||||
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
|
||||
public static final instance:DialogueBoxRegistry = new DialogueBoxRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<DialogueBoxData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
||||
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<DialogueBoxData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
||||
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):DialogueBox
|
||||
{
|
||||
return ScriptedDialogueBox.init(clsName, "unknown");
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedDialogueBox.listScriptClasses();
|
||||
}
|
||||
}
|
68
source/funkin/data/dialogue/SpeakerData.hx
Normal file
68
source/funkin/data/dialogue/SpeakerData.hx
Normal file
|
@ -0,0 +1,68 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
/**
|
||||
* A type definition for a specific speaker in a conversation.
|
||||
* It includes things like what sprite to use and its available animations.
|
||||
* @see https://lib.haxe.org/p/json2object/
|
||||
*/
|
||||
typedef SpeakerData =
|
||||
{
|
||||
/**
|
||||
* Semantic version of the speaker data.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* A human-readable name for the speaker.
|
||||
*/
|
||||
public var name:String;
|
||||
|
||||
/**
|
||||
* The path to the asset to use for the speaker's sprite.
|
||||
*/
|
||||
public var assetPath:String;
|
||||
|
||||
/**
|
||||
* Whether the sprite should be flipped horizontally.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var flipX:Bool;
|
||||
|
||||
/**
|
||||
* Whether the sprite should be flipped vertically.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var flipY:Bool;
|
||||
|
||||
/**
|
||||
* Whether to disable anti-aliasing for the dialogue box sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* The offsets to apply to the sprite's position.
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
public var offsets:Array<Float>;
|
||||
|
||||
/**
|
||||
* The scale to apply to the sprite.
|
||||
*/
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
public var scale:Float;
|
||||
|
||||
/**
|
||||
* The available animations for the speaker.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var animations:Array<AnimationData>;
|
||||
}
|
81
source/funkin/data/dialogue/SpeakerRegistry.hx
Normal file
81
source/funkin/data/dialogue/SpeakerRegistry.hx
Normal file
|
@ -0,0 +1,81 @@
|
|||
package funkin.data.dialogue;
|
||||
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||
|
||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the speaker data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateSpeakerData()` function.
|
||||
*/
|
||||
public static final SPEAKER_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static final instance:SpeakerRegistry = new SpeakerRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<SpeakerData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<SpeakerData>();
|
||||
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<SpeakerData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<SpeakerData>();
|
||||
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):Speaker
|
||||
{
|
||||
return ScriptedSpeaker.init(clsName, "unknown");
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedSpeaker.listScriptClasses();
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
// SCRIPTED ENTRIES
|
||||
//
|
||||
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
|
||||
log('Registering ${scriptedEntryClassNames.length} scripted entries...');
|
||||
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
|
||||
for (entryCls in scriptedEntryClassNames)
|
||||
{
|
||||
|
@ -84,7 +84,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
|
||||
return !entries.exists(entryId);
|
||||
});
|
||||
log('Fetching data for ${unscriptedEntryIds.length} unscripted entries...');
|
||||
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
|
||||
for (entryId in unscriptedEntryIds)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -2,7 +2,6 @@ package funkin.modding;
|
|||
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.stage.StageData;
|
||||
import polymod.Polymod;
|
||||
|
@ -13,10 +12,11 @@ import funkin.data.stage.StageRegistry;
|
|||
import funkin.util.FileUtil;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.save.Save;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
import funkin.data.song.SongRegistry;
|
||||
|
||||
class PolymodHandler
|
||||
|
@ -208,8 +208,8 @@ class PolymodHandler
|
|||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
"default" => "preload", "shared" => "", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2", "week3" => "week3",
|
||||
"week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||
"default" => "preload", "shared" => "shared", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2",
|
||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||
],
|
||||
coreAssetRedirect: CORE_FOLDER,
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class CharacterDataParser
|
|||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearCharacterCache();
|
||||
trace('Loading character cache...');
|
||||
trace('[CHARACTER] Parsing all entries...');
|
||||
|
||||
//
|
||||
// UNSCRIPTED CHARACTERS
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package funkin.play.cutscene.dialogue;
|
||||
|
||||
import funkin.data.IRegistryEntry;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.sound.FlxSound;
|
||||
|
@ -13,27 +15,30 @@ import funkin.modding.IScriptedClass.IEventHandler;
|
|||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.data.dialogue.ConversationData.DialogueEntryData;
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import funkin.data.dialogue.ConversationData;
|
||||
import funkin.data.dialogue.ConversationData.DialogueEntryData;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
|
||||
/**
|
||||
* A high-level handler for dialogue.
|
||||
*
|
||||
* This shit is great for modders but it's pretty elaborate for how much it'll actually be used, lolol. -Eric
|
||||
*/
|
||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
||||
{
|
||||
static final CONVERSATION_SKIP_TIMER:Float = 1.5;
|
||||
|
||||
var skipHeldTimer:Float = 0.0;
|
||||
|
||||
/**
|
||||
* DATA
|
||||
* The ID of the conversation.
|
||||
*/
|
||||
/**
|
||||
* The ID of the associated dialogue.
|
||||
*/
|
||||
public final conversationId:String;
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* The current state of the conversation.
|
||||
|
@ -41,9 +46,9 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
var state:ConversationState = ConversationState.Start;
|
||||
|
||||
/**
|
||||
* The data for the associated dialogue.
|
||||
* Conversation data as parsed from the JSON file.
|
||||
*/
|
||||
var conversationData:ConversationData;
|
||||
public final _data:ConversationData;
|
||||
|
||||
/**
|
||||
* The current entry in the dialogue.
|
||||
|
@ -54,7 +59,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function get_currentDialogueEntryCount():Int
|
||||
{
|
||||
return conversationData.dialogue.length;
|
||||
return _data.dialogue.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,10 +78,10 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function get_currentDialogueEntryData():DialogueEntryData
|
||||
{
|
||||
if (conversationData == null || conversationData.dialogue == null) return null;
|
||||
if (currentDialogueEntry < 0 || currentDialogueEntry >= conversationData.dialogue.length) return null;
|
||||
if (_data == null || _data.dialogue == null) return null;
|
||||
if (currentDialogueEntry < 0 || currentDialogueEntry >= _data.dialogue.length) return null;
|
||||
|
||||
return conversationData.dialogue[currentDialogueEntry];
|
||||
return _data.dialogue[currentDialogueEntry];
|
||||
}
|
||||
|
||||
var currentDialogueLineString(get, never):String;
|
||||
|
@ -94,7 +99,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
/**
|
||||
* GRAPHICS
|
||||
*/
|
||||
var backdrop:FlxSprite;
|
||||
var backdrop:FunkinSprite;
|
||||
|
||||
var currentSpeaker:Speaker;
|
||||
|
||||
|
@ -102,14 +107,17 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
var skipTimer:FlxPieDial;
|
||||
|
||||
public function new(conversationId:String)
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.conversationId = conversationId;
|
||||
this.conversationData = ConversationDataParser.parseConversationData(this.conversationId);
|
||||
this.id = id;
|
||||
this._data = _fetchData(id);
|
||||
|
||||
if (conversationData == null) throw 'Could not load conversation data for conversation ID "$conversationId"';
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse conversation data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void
|
||||
|
@ -125,14 +133,14 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function setupMusic():Void
|
||||
{
|
||||
if (conversationData.music == null) return;
|
||||
if (_data.music == null) return;
|
||||
|
||||
music = new FlxSound().loadEmbedded(Paths.music(conversationData.music.asset), true, true);
|
||||
music = new FlxSound().loadEmbedded(Paths.music(_data.music.asset), true, true);
|
||||
music.volume = 0;
|
||||
|
||||
if (conversationData.music.fadeTime > 0.0)
|
||||
if (_data.music.fadeTime > 0.0)
|
||||
{
|
||||
FlxTween.tween(music, {volume: 1.0}, conversationData.music.fadeTime, {ease: FlxEase.linear});
|
||||
FlxTween.tween(music, {volume: 1.0}, _data.music.fadeTime, {ease: FlxEase.linear});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -145,19 +153,20 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function setupBackdrop():Void
|
||||
{
|
||||
backdrop = new FlxSprite(0, 0);
|
||||
backdrop = new FunkinSprite(0, 0);
|
||||
|
||||
if (conversationData.backdrop == null) return;
|
||||
if (_data.backdrop == null) return;
|
||||
|
||||
// Play intro
|
||||
switch (conversationData?.backdrop.type)
|
||||
switch (_data.backdrop)
|
||||
{
|
||||
case SOLID:
|
||||
backdrop.makeGraphic(Std.int(FlxG.width), Std.int(FlxG.height), FlxColor.fromString(conversationData.backdrop.data.color));
|
||||
if (conversationData.backdrop.data.fadeTime > 0.0)
|
||||
case SOLID(backdropData):
|
||||
var targetColor:FlxColor = FlxColor.fromString(backdropData.color);
|
||||
backdrop.makeSolidColor(Std.int(FlxG.width), Std.int(FlxG.height), targetColor);
|
||||
if (backdropData.fadeTime > 0.0)
|
||||
{
|
||||
backdrop.alpha = 0.0;
|
||||
FlxTween.tween(backdrop, {alpha: 1.0}, conversationData.backdrop.data.fadeTime, {ease: FlxEase.linear});
|
||||
FlxTween.tween(backdrop, {alpha: 1.0}, backdropData.fadeTime, {ease: FlxEase.linear});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -190,9 +199,9 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
var nextSpeakerId:String = currentDialogueEntryData.speaker;
|
||||
|
||||
// Skip the next steps if the current speaker is already displayed.
|
||||
if (currentSpeaker != null && nextSpeakerId == currentSpeaker.speakerId) return;
|
||||
if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return;
|
||||
|
||||
var nextSpeaker:Speaker = SpeakerDataParser.fetchSpeaker(nextSpeakerId);
|
||||
var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId);
|
||||
|
||||
if (currentSpeaker != null)
|
||||
{
|
||||
|
@ -241,7 +250,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
var nextDialogueBoxId:String = currentDialogueEntryData?.box;
|
||||
|
||||
// Skip the next steps if the current speaker is already displayed.
|
||||
if (currentDialogueBox != null && nextDialogueBoxId == currentDialogueBox.dialogueBoxId) return;
|
||||
if (currentDialogueBox != null && nextDialogueBoxId == currentDialogueBox.id) return;
|
||||
|
||||
if (currentDialogueBox != null)
|
||||
{
|
||||
|
@ -250,7 +259,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
currentDialogueBox = null;
|
||||
}
|
||||
|
||||
var nextDialogueBox:DialogueBox = DialogueBoxDataParser.fetchDialogueBox(nextDialogueBoxId);
|
||||
var nextDialogueBox:DialogueBox = DialogueBoxRegistry.instance.fetchEntry(nextDialogueBoxId);
|
||||
|
||||
if (nextDialogueBox == null)
|
||||
{
|
||||
|
@ -378,20 +387,18 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
public function startOutro():Void
|
||||
{
|
||||
switch (conversationData?.outro?.type)
|
||||
switch (_data?.outro)
|
||||
{
|
||||
case FADE:
|
||||
var fadeTime:Float = conversationData?.outro.data.fadeTime ?? 1.0;
|
||||
|
||||
outroTween = FlxTween.tween(this, {alpha: 0.0}, fadeTime,
|
||||
case FADE(outroData):
|
||||
outroTween = FlxTween.tween(this, {alpha: 0.0}, outroData.fadeTime,
|
||||
{
|
||||
type: ONESHOT, // holy shit like the game no way
|
||||
startDelay: 0,
|
||||
onComplete: (_) -> endOutro(),
|
||||
});
|
||||
|
||||
FlxTween.tween(this.music, {volume: 0.0}, fadeTime);
|
||||
case NONE:
|
||||
FlxTween.tween(this.music, {volume: 0.0}, outroData.fadeTime);
|
||||
case NONE(_):
|
||||
// Immediately clean up.
|
||||
endOutro();
|
||||
default:
|
||||
|
@ -400,7 +407,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
}
|
||||
}
|
||||
|
||||
public var completeCallback:Void->Void;
|
||||
public var completeCallback:() -> Void;
|
||||
|
||||
public function endOutro():Void
|
||||
{
|
||||
|
@ -596,7 +603,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'Conversation($conversationId)';
|
||||
return 'Conversation($id)';
|
||||
}
|
||||
|
||||
static function _fetchData(id:String):Null<ConversationData>
|
||||
{
|
||||
return ConversationRegistry.instance.parseEntryDataWithMigration(id, ConversationRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.play.cutscene.dialogue;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.text.FlxText;
|
||||
|
@ -9,18 +10,21 @@ import funkin.util.assets.FlxAnimationUtil;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
|
||||
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
||||
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
|
||||
{
|
||||
public final dialogueBoxId:String;
|
||||
public final id:String;
|
||||
|
||||
public var dialogueBoxName(get, never):String;
|
||||
|
||||
function get_dialogueBoxName():String
|
||||
{
|
||||
return boxData?.name ?? 'UNKNOWN';
|
||||
return _data.name ?? 'UNKNOWN';
|
||||
}
|
||||
|
||||
var boxData:DialogueBoxData;
|
||||
public final _data:DialogueBoxData;
|
||||
|
||||
/**
|
||||
* Offset the speaker's sprite by this much when playing each animation.
|
||||
|
@ -88,13 +92,16 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
return this.speed;
|
||||
}
|
||||
|
||||
public function new(dialogueBoxId:String)
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
this.dialogueBoxId = dialogueBoxId;
|
||||
this.boxData = DialogueBoxDataParser.parseDialogueBoxData(this.dialogueBoxId);
|
||||
this.id = id;
|
||||
this._data = _fetchData(id);
|
||||
|
||||
if (boxData == null) throw 'Could not load dialogue box data for box ID "$dialogueBoxId"';
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse dialogue box data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
public function onCreate(event:ScriptEvent):Void
|
||||
|
@ -115,18 +122,18 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
trace('[DIALOGUE BOX] Loading spritesheet ${boxData.assetPath} for ${dialogueBoxId}');
|
||||
trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(boxData.assetPath);
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Sparrow sprite: ${boxData.assetPath}');
|
||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||
return;
|
||||
}
|
||||
|
||||
this.boxSprite.frames = tex;
|
||||
|
||||
if (boxData.isPixel)
|
||||
if (_data.isPixel)
|
||||
{
|
||||
this.boxSprite.antialiasing = false;
|
||||
}
|
||||
|
@ -135,9 +142,10 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
this.boxSprite.antialiasing = true;
|
||||
}
|
||||
|
||||
this.flipX = boxData.flipX;
|
||||
this.globalOffsets = boxData.offsets;
|
||||
this.setScale(boxData.scale);
|
||||
this.flipX = _data.flipX;
|
||||
this.flipY = _data.flipY;
|
||||
this.globalOffsets = _data.offsets;
|
||||
this.setScale(_data.scale);
|
||||
}
|
||||
|
||||
public function setText(newText:String):Void
|
||||
|
@ -184,11 +192,11 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
function loadAnimations():Void
|
||||
{
|
||||
trace('[DIALOGUE BOX] Loading ${boxData.animations.length} animations for ${dialogueBoxId}');
|
||||
trace('[DIALOGUE BOX] Loading ${_data.animations.length} animations for ${id}');
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimations(this.boxSprite, boxData.animations);
|
||||
FlxAnimationUtil.addAtlasAnimations(this.boxSprite, _data.animations);
|
||||
|
||||
for (anim in boxData.animations)
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
if (anim.offsets == null)
|
||||
{
|
||||
|
@ -201,7 +209,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
}
|
||||
|
||||
var animNames:Array<String> = this.boxSprite?.animation?.getNameList() ?? [];
|
||||
trace('[DIALOGUE BOX] Successfully loaded ${animNames.length} animations for ${dialogueBoxId}');
|
||||
trace('[DIALOGUE BOX] Successfully loaded ${animNames.length} animations for ${id}');
|
||||
|
||||
boxSprite.animation.callback = this.onAnimationFrame;
|
||||
boxSprite.animation.finishCallback = this.onAnimationFinished;
|
||||
|
@ -234,16 +242,16 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
function loadText():Void
|
||||
{
|
||||
textDisplay = new FlxTypeText(0, 0, 300, '', 32);
|
||||
textDisplay.fieldWidth = boxData.text.width;
|
||||
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.fieldWidth = _data.text.width;
|
||||
textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW,
|
||||
FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false);
|
||||
textDisplay.borderSize = _data.text.shadowWidth ?? 2;
|
||||
textDisplay.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)];
|
||||
|
||||
textDisplay.completeCallback = onTypingComplete;
|
||||
|
||||
textDisplay.x += boxData.text.offsets[0];
|
||||
textDisplay.y += boxData.text.offsets[1];
|
||||
textDisplay.x += _data.text.offsets[0];
|
||||
textDisplay.y += _data.text.offsets[1];
|
||||
|
||||
add(textDisplay);
|
||||
}
|
||||
|
@ -374,4 +382,14 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'DialogueBox($id)';
|
||||
}
|
||||
|
||||
static function _fetchData(id:String):Null<DialogueBoxData>
|
||||
{
|
||||
return DialogueBoxRegistry.instance.parseEntryDataWithMigration(id, DialogueBoxRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,30 @@
|
|||
package funkin.play.cutscene.dialogue;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
|
||||
/**
|
||||
* The character sprite which displays during dialogue.
|
||||
*
|
||||
* Most conversations have two speakers, with one being flipped.
|
||||
*/
|
||||
class Speaker extends FlxSprite implements IDialogueScriptedClass
|
||||
class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRegistryEntry<SpeakerData>
|
||||
{
|
||||
/**
|
||||
* The internal ID for this speaker.
|
||||
*/
|
||||
public final speakerId:String;
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* The full data for a speaker.
|
||||
*/
|
||||
var speakerData:SpeakerData;
|
||||
public final _data:SpeakerData;
|
||||
|
||||
/**
|
||||
* A readable name for this speaker.
|
||||
|
@ -30,7 +33,7 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
|
||||
function get_speakerName():String
|
||||
{
|
||||
return speakerData.name;
|
||||
return _data.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,14 +78,17 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
return globalOffsets = value;
|
||||
}
|
||||
|
||||
public function new(speakerId:String)
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.speakerId = speakerId;
|
||||
this.speakerData = SpeakerDataParser.parseSpeakerData(this.speakerId);
|
||||
this.id = id;
|
||||
this._data = _fetchData(id);
|
||||
|
||||
if (speakerData == null) throw 'Could not load speaker data for speaker ID "$speakerId"';
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse speaker data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,18 +108,18 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
trace('[SPEAKER] Loading spritesheet ${speakerData.assetPath} for ${speakerId}');
|
||||
trace('[SPEAKER] Loading spritesheet ${_data.assetPath} for ${id}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(speakerData.assetPath);
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
{
|
||||
trace('Could not load Sparrow sprite: ${speakerData.assetPath}');
|
||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||
return;
|
||||
}
|
||||
|
||||
this.frames = tex;
|
||||
|
||||
if (speakerData.isPixel)
|
||||
if (_data.isPixel)
|
||||
{
|
||||
this.antialiasing = false;
|
||||
}
|
||||
|
@ -122,9 +128,10 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
this.antialiasing = true;
|
||||
}
|
||||
|
||||
this.flipX = speakerData.flipX;
|
||||
this.globalOffsets = speakerData.offsets;
|
||||
this.setScale(speakerData.scale);
|
||||
this.flipX = _data.flipX;
|
||||
this.flipY = _data.flipY;
|
||||
this.globalOffsets = _data.offsets;
|
||||
this.setScale(_data.scale);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,11 +148,11 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
|
||||
function loadAnimations():Void
|
||||
{
|
||||
trace('[SPEAKER] Loading ${speakerData.animations.length} animations for ${speakerId}');
|
||||
trace('[SPEAKER] Loading ${_data.animations.length} animations for ${id}');
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimations(this, speakerData.animations);
|
||||
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
||||
|
||||
for (anim in speakerData.animations)
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
if (anim.offsets == null)
|
||||
{
|
||||
|
@ -158,7 +165,7 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
}
|
||||
|
||||
var animNames:Array<String> = this.animation.getNameList();
|
||||
trace('[SPEAKER] Successfully loaded ${animNames.length} animations for ${speakerId}');
|
||||
trace('[SPEAKER] Successfully loaded ${animNames.length} animations for ${id}');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -271,4 +278,14 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass
|
|||
}
|
||||
|
||||
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'Speaker($id)';
|
||||
}
|
||||
|
||||
static function _fetchData(id:String):Null<SpeakerData>
|
||||
{
|
||||
return SpeakerRegistry.instance.parseEntryDataWithMigration(id, SpeakerRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
|
|
76
source/funkin/ui/debug/dialogue/ConversationDebugState.hx
Normal file
76
source/funkin/ui/debug/dialogue/ConversationDebugState.hx
Normal file
|
@ -0,0 +1,76 @@
|
|||
package funkin.ui.debug.dialogue;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.data.dialogue.ConversationData;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
|
||||
/**
|
||||
* 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 = ConversationRegistry.instance.fetchEntry(conversationId);
|
||||
conversation.completeCallback = onConversationComplete;
|
||||
add(conversation);
|
||||
|
||||
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
|
||||
}
|
||||
|
||||
function onConversationComplete():Void
|
||||
{
|
||||
remove(conversation);
|
||||
conversation = null;
|
||||
}
|
||||
|
||||
public override function dispatchEvent(event:ScriptEvent):Void
|
||||
{
|
||||
// Dispatch event to conversation script.
|
||||
ScriptEventDispatcher.callEvent(conversation, event);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,8 @@ class DataAssets
|
|||
|
||||
public static function listDataFilesInPath(path:String, ?suffix:String = '.json'):Array<String>
|
||||
{
|
||||
var textAssets = openfl.utils.Assets.list();
|
||||
var textAssets = openfl.utils.Assets.list(TEXT);
|
||||
|
||||
var queryPath = buildDataPath(path);
|
||||
|
||||
var results:Array<String> = [];
|
||||
|
|
Loading…
Reference in a new issue