Finish up event editing (selecting an existing event now shows its data in the event data toolbox)

This commit is contained in:
EliteMasterEric 2024-01-03 21:10:14 -05:00
parent bbaa9aa4af
commit edc6f85e21
16 changed files with 210 additions and 146 deletions

View file

@ -19,7 +19,7 @@ import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData; import openfl.display.BitmapData;
import funkin.data.level.LevelRegistry; import funkin.data.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.event.SongEventData.SongEventParser; import funkin.data.event.SongEventRegistry;
import funkin.play.cutscene.dialogue.ConversationDataParser; import funkin.play.cutscene.dialogue.ConversationDataParser;
import funkin.play.cutscene.dialogue.DialogueBoxDataParser; import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
import funkin.play.cutscene.dialogue.SpeakerDataParser; import funkin.play.cutscene.dialogue.SpeakerDataParser;
@ -213,7 +213,7 @@ class InitState extends FlxState
SongRegistry.instance.loadEntries(); SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries();
SongEventParser.loadEventCache(); SongEventRegistry.loadEventCache();
ConversationDataParser.loadConversationCache(); ConversationDataParser.loadConversationCache();
DialogueBoxDataParser.loadDialogueBoxCache(); DialogueBoxDataParser.loadDialogueBoxCache();
SpeakerDataParser.loadSpeakerCache(); SpeakerDataParser.loadSpeakerCache();

View file

@ -1,7 +1,7 @@
package funkin.data.event; package funkin.data.event;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
import funkin.util.macro.ClassMacro; import funkin.util.macro.ClassMacro;
import funkin.play.event.ScriptedSongEvent; import funkin.play.event.ScriptedSongEvent;
@ -9,7 +9,7 @@ import funkin.play.event.ScriptedSongEvent;
/** /**
* This class statically handles the parsing of internal and scripted song event handlers. * This class statically handles the parsing of internal and scripted song event handlers.
*/ */
class SongEventParser class SongEventRegistry
{ {
/** /**
* Every built-in event class must be added to this list. * Every built-in event class must be added to this list.
@ -160,108 +160,3 @@ class SongEventParser
} }
} }
} }
enum abstract SongEventFieldType(String) from String to String
{
/**
* The STRING type will display as a text field.
*/
var STRING = "string";
/**
* The INTEGER type will display as a text field that only accepts numbers.
*/
var INTEGER = "integer";
/**
* The FLOAT type will display as a text field that only accepts numbers.
*/
var FLOAT = "float";
/**
* The BOOL type will display as a checkbox.
*/
var BOOL = "bool";
/**
* The ENUM type will display as a dropdown.
* Make sure to specify the `keys` field in the schema.
*/
var ENUM = "enum";
}
typedef SongEventSchemaField =
{
/**
* The name of the property as it should be saved in the event data.
*/
name:String,
/**
* The title of the field to display in the UI.
*/
title:String,
/**
* The type of the field.
*/
type:SongEventFieldType,
/**
* Used only for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,
/**
* Used for INTEGER and FLOAT values.
* The minimum value that can be entered.
* @default No minimum
*/
?min:Float,
/**
* Used for INTEGER and FLOAT values.
* The maximum value that can be entered.
* @default No maximum
*/
?max:Float,
/**
* Used for INTEGER and FLOAT values.
* The step value that will be used when incrementing/decrementing the value.
* @default `0.1`
*/
?step:Float,
/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}
@:forward
abstract SongEventSchema(SongEventSchemaRaw)
{
public function new(?fields:Array<SongEventSchemaField>)
{
this = fields;
}
public function getByName(name:String):SongEventSchemaField
{
for (field in this)
{
if (field.name == name) return field;
}
return null;
}
public function getFirstField():SongEventSchemaField
{
return this[0];
}
}
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;

View file

@ -0,0 +1,125 @@
package funkin.data.event;
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventSchema;
import funkin.data.song.SongData.SongEventData;
import funkin.util.macro.ClassMacro;
import funkin.play.event.ScriptedSongEvent;
@:forward(name, tittlte, type, keys, min, max, step, defaultValue, iterator)
abstract SongEventSchema(SongEventSchemaRaw)
{
public function new(?fields:Array<SongEventSchemaField>)
{
this = fields;
}
@:arrayAccess
public inline function getByName(name:String):SongEventSchemaField
{
for (field in this)
{
if (field.name == name) return field;
}
return null;
}
public function getFirstField():SongEventSchemaField
{
return this[0];
}
@:arrayAccess
public inline function get(key:Int)
{
return this[key];
}
@:arrayAccess
public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
{
return this[k] = v;
}
}
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
typedef SongEventSchemaField =
{
/**
* The name of the property as it should be saved in the event data.
*/
name:String,
/**
* The title of the field to display in the UI.
*/
title:String,
/**
* The type of the field.
*/
type:SongEventFieldType,
/**
* Used only for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,
/**
* Used for INTEGER and FLOAT values.
* The minimum value that can be entered.
* @default No minimum
*/
?min:Float,
/**
* Used for INTEGER and FLOAT values.
* The maximum value that can be entered.
* @default No maximum
*/
?max:Float,
/**
* Used for INTEGER and FLOAT values.
* The step value that will be used when incrementing/decrementing the value.
* @default `0.1`
*/
?step:Float,
/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}
enum abstract SongEventFieldType(String) from String to String
{
/**
* The STRING type will display as a text field.
*/
var STRING = "string";
/**
* The INTEGER type will display as a text field that only accepts numbers.
*/
var INTEGER = "integer";
/**
* The FLOAT type will display as a text field that only accepts numbers.
*/
var FLOAT = "float";
/**
* The BOOL type will display as a checkbox.
*/
var BOOL = "bool";
/**
* The ENUM type will display as a dropdown.
* Make sure to specify the `keys` field in the schema.
*/
var ENUM = "enum";
}

View file

@ -1,5 +1,7 @@
package funkin.data.song; package funkin.data.song;
import funkin.data.event.SongEventRegistry;
import funkin.data.event.SongEventSchema;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import thx.semver.Version; import thx.semver.Version;
@ -620,18 +622,28 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic
{ {
if (this.value == null) return {}; if (this.value == null) return {};
// TODO: How to check if it's a dynamic struct? if (Std.isOfType(this.value, Array))
if (Std.isOfType(this.value, Int) || Std.isOfType(this.value, String) || Std.isOfType(this.value, Float) || Std.isOfType(this.value, Bool)
|| Std.isOfType(this.value, Array))
{ {
var result:haxe.DynamicAccess<Dynamic> = {}; var result:haxe.DynamicAccess<Dynamic> = {};
result.set(defaultKey, this.value); result.set(defaultKey, this.value);
return cast result; return cast result;
} }
else else if (Reflect.isObject(this.value))
{ {
// We enter this case if the value is a struct.
return cast this.value; return cast this.value;
} }
else
{
var result:haxe.DynamicAccess<Dynamic> = {};
result.set(defaultKey, this.value);
return cast result;
}
}
public inline function getSchema():Null<SongEventSchema>
{
return SongEventRegistry.getEventSchema(this.event);
} }
public inline function getDynamic(key:String):Null<Dynamic> public inline function getDynamic(key:String):Null<Dynamic>

View file

@ -8,7 +8,7 @@ import funkin.play.stage.StageData;
import polymod.Polymod; import polymod.Polymod;
import polymod.backends.PolymodAssets.PolymodAssetType; import polymod.backends.PolymodAssets.PolymodAssetType;
import polymod.format.ParseRules.TextFileFormat; import polymod.format.ParseRules.TextFileFormat;
import funkin.data.event.SongEventData.SongEventParser; import funkin.data.event.SongEventRegistry;
import funkin.util.FileUtil; import funkin.util.FileUtil;
import funkin.data.level.LevelRegistry; import funkin.data.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
@ -271,7 +271,7 @@ class PolymodHandler
SongRegistry.instance.loadEntries(); SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries();
SongEventParser.loadEventCache(); SongEventRegistry.loadEventCache();
ConversationDataParser.loadConversationCache(); ConversationDataParser.loadConversationCache();
DialogueBoxDataParser.loadDialogueBoxCache(); DialogueBoxDataParser.loadDialogueBoxCache();
SpeakerDataParser.loadSpeakerCache(); SpeakerDataParser.loadSpeakerCache();

View file

@ -42,7 +42,7 @@ import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.ConversationDataParser; import funkin.play.cutscene.dialogue.ConversationDataParser;
import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene; import funkin.play.cutscene.VideoCutscene;
import funkin.data.event.SongEventData.SongEventParser; import funkin.data.event.SongEventRegistry;
import funkin.play.notes.NoteSprite; import funkin.play.notes.NoteSprite;
import funkin.play.notes.NoteDirection; import funkin.play.notes.NoteDirection;
import funkin.play.notes.Strumline; import funkin.play.notes.Strumline;
@ -942,7 +942,7 @@ class PlayState extends MusicBeatSubState
// TODO: Check that these work even when songPosition is less than 0. // TODO: Check that these work even when songPosition is less than 0.
if (songEvents != null && songEvents.length > 0) if (songEvents != null && songEvents.length > 0)
{ {
var songEventsToActivate:Array<SongEventData> = SongEventParser.queryEvents(songEvents, Conductor.songPosition); var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.songPosition);
if (songEventsToActivate.length > 0) if (songEventsToActivate.length > 0)
{ {
@ -961,7 +961,7 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips the event. Neat! // Calling event.cancelEvent() skips the event. Neat!
if (!eventEvent.eventCanceled) if (!eventEvent.eventCanceled)
{ {
SongEventParser.handleEvent(event); SongEventRegistry.handleEvent(event);
} }
} }
} }
@ -1607,7 +1607,7 @@ class PlayState extends MusicBeatSubState
// Reset song events. // Reset song events.
songEvents = currentChart.getEvents(); songEvents = currentChart.getEvents();
SongEventParser.resetEvents(songEvents); SongEventRegistry.resetEvents(songEvents);
// Reset the notes on each strumline. // Reset the notes on each strumline.
var playerNoteData:Array<SongNoteData> = []; var playerNoteData:Array<SongNoteData> = [];

View file

@ -5,8 +5,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
// Data from the event schema // Data from the event schema
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventData.SongEventFieldType; import funkin.data.event.SongEventSchema.SongEventFieldType;
/** /**
* This class represents a handler for a type of song event. * This class represents a handler for a type of song event.
@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent
*/ */
public override function getEventSchema():SongEventSchema public override function getEventSchema():SongEventSchema
{ {
return [ return new SongEventSchema([
{ {
name: "char", name: "char",
title: "Character", title: "Character",
@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent
step: 10.0, step: 10.0,
type: SongEventFieldType.FLOAT, type: SongEventFieldType.FLOAT,
} }
]; ]);
} }
} }

View file

@ -7,8 +7,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
// Data from the event schema // Data from the event schema
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventData.SongEventFieldType; import funkin.data.event.SongEventSchema.SongEventFieldType;
class PlayAnimationSongEvent extends SongEvent class PlayAnimationSongEvent extends SongEvent
{ {
@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent
*/ */
public override function getEventSchema():SongEventSchema public override function getEventSchema():SongEventSchema
{ {
return [ return new SongEventSchema([
{ {
name: 'target', name: 'target',
title: 'Target', title: 'Target',
@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent
type: SongEventFieldType.BOOL, type: SongEventFieldType.BOOL,
defaultValue: false defaultValue: false
} }
]; ]);
} }
} }

View file

@ -8,8 +8,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
// Data from the event schema // Data from the event schema
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventData.SongEventFieldType; import funkin.data.event.SongEventSchema.SongEventFieldType;
/** /**
* This class represents a handler for configuring camera bop intensity and rate. * This class represents a handler for configuring camera bop intensity and rate.
@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent
*/ */
public override function getEventSchema():SongEventSchema public override function getEventSchema():SongEventSchema
{ {
return [ return new SongEventSchema([
{ {
name: 'intensity', name: 'intensity',
title: 'Intensity', title: 'Intensity',
@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent
step: 1, step: 1,
type: SongEventFieldType.INTEGER, type: SongEventFieldType.INTEGER,
} }
]; ]);
} }
} }

View file

@ -1,7 +1,7 @@
package funkin.play.event; package funkin.play.event;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
/** /**
* This class represents a handler for a type of song event. * This class represents a handler for a type of song event.

View file

@ -8,8 +8,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
// Data from the event schema // Data from the event schema
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventFieldType; import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema.SongEventFieldType;
/** /**
* This class represents a handler for camera zoom events. * This class represents a handler for camera zoom events.
@ -99,7 +99,7 @@ class ZoomCameraSongEvent extends SongEvent
*/ */
public override function getEventSchema():SongEventSchema public override function getEventSchema():SongEventSchema
{ {
return [ return new SongEventSchema([
{ {
name: 'zoom', name: 'zoom',
title: 'Zoom Level', title: 'Zoom Level',
@ -145,6 +145,6 @@ class ZoomCameraSongEvent extends SongEvent
'Elastic In/Out' => 'elasticInOut', 'Elastic In/Out' => 'elasticInOut',
] ]
} }
]; ]);
} }
} }

View file

@ -37,9 +37,25 @@ class SelectItemsCommand implements ChartEditorCommand
if (this.notes.length == 0 && this.events.length >= 1) if (this.notes.length == 0 && this.events.length >= 1)
{ {
var eventSelected = this.events[0]; var eventSelected = this.events[0];
state.eventKindToPlace = eventSelected.event; state.eventKindToPlace = eventSelected.event;
var eventData = eventSelected.valueAsStruct();
// This code is here to parse event data that's not built as a struct for some reason.
// TODO: Clean this up or get rid of it.
var eventSchema = eventSelected.getSchema();
var defaultKey = null;
if (eventSchema == null)
{
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
}
else
{
defaultKey = eventSchema.getFirstField()?.name;
}
var eventData = eventSelected.valueAsStruct(defaultKey);
state.eventDataToPlace = eventData; state.eventDataToPlace = eventData;
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
} }

View file

@ -34,9 +34,25 @@ class SetItemSelectionCommand implements ChartEditorCommand
if (this.notes.length == 0 && this.events.length >= 1) if (this.notes.length == 0 && this.events.length >= 1)
{ {
var eventSelected = this.events[0]; var eventSelected = this.events[0];
state.eventKindToPlace = eventSelected.event; state.eventKindToPlace = eventSelected.event;
var eventData = eventSelected.valueAsStruct();
// This code is here to parse event data that's not built as a struct for some reason.
// TODO: Clean this up or get rid of it.
var eventSchema = eventSelected.getSchema();
var defaultKey = null;
if (eventSchema == null)
{
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
}
else
{
defaultKey = eventSchema.getFirstField()?.name;
}
var eventData = eventSelected.valueAsStruct(defaultKey);
state.eventDataToPlace = eventData; state.eventDataToPlace = eventData;
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
} }

View file

@ -1,6 +1,6 @@
package funkin.ui.debug.charting.components; package funkin.ui.debug.charting.components;
import funkin.data.event.SongEventData.SongEventParser; import funkin.data.event.SongEventRegistry;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import openfl.display.BitmapData; import openfl.display.BitmapData;
import openfl.utils.Assets; import openfl.utils.Assets;
@ -79,7 +79,7 @@ class ChartEditorEventSprite extends FlxSprite
} }
// Push all the other events as frames. // Push all the other events as frames.
for (eventName in SongEventParser.listEventIds()) for (eventName in SongEventRegistry.listEventIds())
{ {
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName')); var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
if (!exists) continue; // No graphic for this event. if (!exists) continue; // No graphic for this event.
@ -105,7 +105,7 @@ class ChartEditorEventSprite extends FlxSprite
function buildAnimations():Void function buildAnimations():Void
{ {
var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventParser.listEventIds()); var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventRegistry.listEventIds());
for (eventName in eventNames) for (eventName in eventNames)
{ {
this.animation.addByPrefix(eventName, '${eventName}0', 24, false); this.animation.addByPrefix(eventName, '${eventName}0', 24, false);

View file

@ -9,7 +9,7 @@ import haxe.ui.containers.TreeView;
import haxe.ui.containers.TreeViewNode; import haxe.ui.containers.TreeViewNode;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData; import funkin.data.event.SongEventSchema;
import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongData.SongTimeChange;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;

View file

@ -4,7 +4,7 @@ import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import funkin.play.stage.StageData; import funkin.play.stage.StageData;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema; import funkin.data.event.SongEventSchema;
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.debug.charting.util.ChartEditorDropdowns;
import haxe.ui.components.Button; import haxe.ui.components.Button;
@ -15,7 +15,7 @@ import haxe.ui.components.Label;
import haxe.ui.components.NumberStepper; import haxe.ui.components.NumberStepper;
import haxe.ui.components.Slider; import haxe.ui.components.Slider;
import haxe.ui.core.Component; import haxe.ui.core.Component;
import funkin.data.event.SongEventData.SongEventParser; import funkin.data.event.SongEventRegistry;
import haxe.ui.components.TextField; import haxe.ui.components.TextField;
import haxe.ui.containers.Box; import haxe.ui.containers.Box;
import haxe.ui.containers.Frame; import haxe.ui.containers.Frame;
@ -59,7 +59,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
{ {
toolboxEventsEventKind.dataSource = new ArrayDataSource(); toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = SongEventParser.listEvents(); var songEvents:Array<SongEvent> = SongEventRegistry.listEvents();
for (event in songEvents) for (event in songEvents)
{ {
@ -74,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
// Edit the event data to place. // Edit the event data to place.
chartEditorState.eventKindToPlace = eventType; chartEditorState.eventKindToPlace = eventType;
var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); var schema:SongEventSchema = SongEventRegistry.getEventSchema(eventType);
if (schema == null) if (schema == null)
{ {