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 funkin.data.level.LevelRegistry;
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.DialogueBoxDataParser;
import funkin.play.cutscene.dialogue.SpeakerDataParser;
@ -213,7 +213,7 @@ class InitState extends FlxState
SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries();
NoteStyleRegistry.instance.loadEntries();
SongEventParser.loadEventCache();
SongEventRegistry.loadEventCache();
ConversationDataParser.loadConversationCache();
DialogueBoxDataParser.loadDialogueBoxCache();
SpeakerDataParser.loadSpeakerCache();

View file

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

View file

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

View file

@ -42,7 +42,7 @@ import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.dialogue.ConversationDataParser;
import funkin.play.cutscene.VanillaCutscenes;
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.NoteDirection;
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.
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)
{
@ -961,7 +961,7 @@ class PlayState extends MusicBeatSubState
// Calling event.cancelEvent() skips the event. Neat!
if (!eventEvent.eventCanceled)
{
SongEventParser.handleEvent(event);
SongEventRegistry.handleEvent(event);
}
}
}
@ -1607,7 +1607,7 @@ class PlayState extends MusicBeatSubState
// Reset song events.
songEvents = currentChart.getEvents();
SongEventParser.resetEvents(songEvents);
SongEventRegistry.resetEvents(songEvents);
// Reset the notes on each strumline.
var playerNoteData:Array<SongNoteData> = [];

View file

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

View file

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

View file

@ -8,8 +8,8 @@ import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
// Data from the event schema
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData.SongEventSchema;
import funkin.data.event.SongEventData.SongEventFieldType;
import funkin.data.event.SongEventSchema;
import funkin.data.event.SongEventSchema.SongEventFieldType;
/**
* 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
{
return [
return new SongEventSchema([
{
name: 'intensity',
title: 'Intensity',
@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent
step: 1,
type: SongEventFieldType.INTEGER,
}
];
]);
}
}

View file

@ -1,7 +1,7 @@
package funkin.play.event;
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.

View file

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

View file

@ -37,9 +37,25 @@ class SelectItemsCommand implements ChartEditorCommand
if (this.notes.length == 0 && this.events.length >= 1)
{
var eventSelected = this.events[0];
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.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)
{
var eventSelected = this.events[0];
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.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
}

View file

@ -1,6 +1,6 @@
package funkin.ui.debug.charting.components;
import funkin.data.event.SongEventData.SongEventParser;
import funkin.data.event.SongEventRegistry;
import flixel.graphics.frames.FlxAtlasFrames;
import openfl.display.BitmapData;
import openfl.utils.Assets;
@ -79,7 +79,7 @@ class ChartEditorEventSprite extends FlxSprite
}
// 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'));
if (!exists) continue; // No graphic for this event.
@ -105,7 +105,7 @@ class ChartEditorEventSprite extends FlxSprite
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)
{
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 funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData;
import funkin.data.event.SongEventSchema;
import funkin.data.song.SongData.SongTimeChange;
import funkin.play.character.BaseCharacter.CharacterType;
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.stage.StageData;
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.util.ChartEditorDropdowns;
import haxe.ui.components.Button;
@ -15,7 +15,7 @@ import haxe.ui.components.Label;
import haxe.ui.components.NumberStepper;
import haxe.ui.components.Slider;
import haxe.ui.core.Component;
import funkin.data.event.SongEventData.SongEventParser;
import funkin.data.event.SongEventRegistry;
import haxe.ui.components.TextField;
import haxe.ui.containers.Box;
import haxe.ui.containers.Frame;
@ -59,7 +59,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
{
toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = SongEventParser.listEvents();
var songEvents:Array<SongEvent> = SongEventRegistry.listEvents();
for (event in songEvents)
{
@ -74,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
// Edit the event data to place.
chartEditorState.eventKindToPlace = eventType;
var schema:SongEventSchema = SongEventParser.getEventSchema(eventType);
var schema:SongEventSchema = SongEventRegistry.getEventSchema(eventType);
if (schema == null)
{