Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENHANCEMENT] Additional changes to Note Kind Scripts #2635

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
squashed commits
  • Loading branch information
lemz1 committed Feb 18, 2025
commit bd2447df13dcc527445ee7653be04b876f008d64
2 changes: 1 addition & 1 deletion assets
151 changes: 151 additions & 0 deletions source/funkin/data/notes/SongNoteSchema.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package funkin.data.notes;

@:forward(name, title, type, min, max, step, precision, keys, defaultValue, iterator)
abstract SongNoteSchema(SongNoteSchemaRaw)
{
public function new(?fields:Array<SongNoteSchemaField>)
{
this = fields;
}

@:arrayAccess
public function getByName(name:String):SongNoteSchemaField
{
for (field in this)
{
if (field.name == name) return field;
}

return null;
}

public function getFirstField():SongNoteSchemaField
{
return this[0];
}

@:arrayAccess
public inline function get(index:Int):SongNoteSchemaField
{
return this[index];
}

@:arrayAccess
public inline function set(index:Int, value:SongNoteSchemaField):SongNoteSchemaField
{
return this[index] = value;
}

public function stringifyFieldValue(name:String, value:Dynamic):String
{
var field:SongNoteSchemaField = getByName(name);
if (field == null) return 'Unknown';

switch (field.type)
{
case SongNoteFieldType.STRING:
return Std.string(value);
case SongNoteFieldType.INTEGER | SongNoteFieldType.FLOAT | SongNoteFieldType.BOOL:
var returnValue:String = Std.string(value);
return returnValue;
case SongNoteFieldType.ENUM:
var valueString:String = Std.string(value);
for (key in field.keys.keys())
{
// Comparing these values as strings because comparing Dynamic variables is jank.
if (Std.string(field.keys.get(key)) == valueString) return key;
}
return valueString;
default:
return 'Unknown';
}
}
}

typedef SongNoteSchemaRaw = Array<SongNoteSchemaField>;

typedef SongNoteSchemaField =
{
/**
* 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:SongNoteFieldType,

/**
* 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,

/**
* Used for INTEGER and FLOAT values.
* The amount of decimal places.
* @default `0`
*/
?precision:Int,

/**
* Used only for ENUM values.
* The key is the display name and the value is the actual value.
*/
?keys:Map<String, Dynamic>,

/**
* An optional default value for the field.
*/
?defaultValue:Dynamic,
}

enum abstract SongNoteFieldType(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";
}
119 changes: 103 additions & 16 deletions source/funkin/data/song/SongData.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package funkin.data.song;
import funkin.data.event.SongEventRegistry;
import funkin.play.event.SongEvent;
import funkin.data.event.SongEventSchema;
import funkin.data.notes.SongNoteSchema;
import funkin.data.song.SongRegistry;
import funkin.play.notes.notekind.NoteKindManager;
import thx.semver.Version;
import funkin.util.tools.ICloneable;

Expand Down Expand Up @@ -984,17 +986,18 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
}

@:alias("p")
@:default([])
@:optional
public var params:Array<NoteParamData>;
@:jcustomparse(funkin.data.DataParse.dynamicValue)
@:jcustomwrite(funkin.data.DataWrite.dynamicValue)
public var params:Dynamic = null;

public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Dynamic)
{
this.time = time;
this.data = data;
this.length = length;
this.kind = kind;
this.params = params ?? [];
this.params = params;
}

/**
Expand All @@ -1003,7 +1006,7 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
*
* 0 = left, 1 = down, 2 = up, 3 = right
*/
public inline function getDirection(strumlineSize:Int = 4):Int
public function getDirection(strumlineSize:Int = 4):Int
{
return this.data % strumlineSize;
}
Expand Down Expand Up @@ -1089,25 +1092,109 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
_stepLength = null;
}

public function cloneParams():Array<NoteParamData>
public function clone():SongNoteDataRaw
{
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, this.params);
}

public function toString():String
{
var params:Array<NoteParamData> = [];
for (param in this.params)
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
}

public function paramsAsStruct(?defaultKey:String = "key"):Dynamic
{
if (this.params == null) return {};

if (Reflect.isObject(this.params))
{
params.push(param.clone());
// We enter this case if the params are a struct.
return cast this.params;
}
else
{
var result:haxe.DynamicAccess<Dynamic> = {};
result.set(defaultKey, this.params);
return cast result;
}
return params;
}

public function clone():SongNoteDataRaw
public function getSchema():Null<SongNoteSchema>
{
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
return NoteKindManager.getSchema(this.kind);
}

public function toString():String
public function getDynamic(key:String):Null<Dynamic>
{
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
return this.params == null ? null : Reflect.field(this.params, key);
}

public function getBool(key:String):Null<Bool>
{
return this.params == null ? null : cast Reflect.field(this.params, key);
}

public function getInt(key:String):Null<Int>
{
if (this.params == null) return null;
var result = Reflect.field(this.params, key);
if (result == null) return null;
if (Std.isOfType(result, Int)) return result;
if (Std.isOfType(result, String)) return Std.parseInt(cast result);
return cast result;
}

public function getFloat(key:String):Null<Float>
{
if (this.params == null) return null;
var result = Reflect.field(this.params, key);
if (result == null) return null;
if (Std.isOfType(result, Float)) return result;
if (Std.isOfType(result, String)) return Std.parseFloat(cast result);
return cast result;
}

public function getString(key:String):String
{
return this.params == null ? null : cast Reflect.field(this.params, key);
}

public function getArray(key:String):Array<Dynamic>
{
return this.params == null ? null : cast Reflect.field(this.params, key);
}

public function getBoolArray(key:String):Array<Bool>
{
return this.params == null ? null : cast Reflect.field(this.params, key);
}

public function buildTooltip():Null<String>
{
var noteSchema = getSchema();

if (noteSchema == null) return null;

var result = '${this.kind}';

var defaultKey = noteSchema.getFirstField()?.name;
var paramsStruct:haxe.DynamicAccess<Dynamic> = paramsAsStruct(defaultKey);

for (pair in paramsStruct.keyValueIterator())
{
var key = pair.key;
var value = pair.value;

var title = noteSchema.getByName(key)?.title ?? 'UnknownField';

// if (noteSchema.stringifyFieldValue(key, value) != null) trace(noteSchema.stringifyFieldValue(key, value));
var valueStr = noteSchema.stringifyFieldValue(key, value) ?? 'UnknownValue';

result += '\n- ${title}: ${valueStr}';
}

return result;
}
}

Expand All @@ -1117,7 +1204,7 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
@:forward
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
{
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Dynamic)
{
this = new SongNoteDataRaw(time, data, length, kind, params);
}
Expand Down
17 changes: 5 additions & 12 deletions source/funkin/play/notes/NoteSprite.hx
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ class NoteSprite extends FunkinSprite
}

/**
* An array of custom parameters for this note
* Custom parameters for this note
*/
public var params(get, set):Array<NoteParamData>;
public var params(get, set):Dynamic;

function get_params():Array<NoteParamData>
function get_params():Dynamic
{
return this.noteData?.params ?? [];
}

function set_params(value:Array<NoteParamData>):Array<NoteParamData>
function set_params(value:Dynamic):Dynamic
{
if (this.noteData == null) return value;
return this.noteData.params = value;
Expand Down Expand Up @@ -175,14 +175,7 @@ class NoteSprite extends FunkinSprite
*/
public function getParam(name:String):Null<Dynamic>
{
for (param in params)
{
if (param.name == name)
{
return param.value;
}
}
return null;
return this.noteData?.getDynamic(name);
}

#if FLX_DEBUG
Expand Down
Loading