diff --git a/assets/images/editors/charter/event-icons/components/arrow-right.png b/assets/images/editors/charter/event-icons/components/arrow-right.png new file mode 100644 index 00000000..a5da3a70 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/arrow-right.png differ diff --git a/assets/images/editors/charter/event-icons/components/cross.png b/assets/images/editors/charter/event-icons/components/cross.png new file mode 100644 index 00000000..a3dee356 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/cross.png differ diff --git a/assets/images/editors/charter/event-icons/components/flash.png b/assets/images/editors/charter/event-icons/components/flash.png new file mode 100644 index 00000000..2ef5f949 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/flash.png differ diff --git a/assets/images/editors/charter/event-icons/components/plus.png b/assets/images/editors/charter/event-icons/components/plus.png new file mode 100644 index 00000000..939d4c46 Binary files /dev/null and b/assets/images/editors/charter/event-icons/components/plus.png differ diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index e0d17331..9380723d 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -76,11 +76,12 @@ class EventsData { hscriptParser.allowJSON = hscriptParser.allowMetadata = false; for (file in Paths.getFolderContent('data/events/', true, BOTH)) { - if (Path.extension(file) != "json" && Path.extension(file) != "pack") continue; - var eventName:String = Path.withoutExtension(Path.withoutDirectory(file)); + var ext = Path.extension(file); + if (ext != "json" && ext != "pack") continue; + var eventName:String = CoolUtil.getFilename(file); var fileTxt:String = Assets.getText(file); - if (Path.extension(file) == "pack") { + if (ext == "pack") { var arr = fileTxt.split("________PACKSEP________"); eventName = Path.withoutExtension(arr[0]); fileTxt = arr[2]; diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 16d0b4db..a16cd722 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,12 +1,15 @@ package funkin.editors.charter; import flixel.group.FlxSpriteGroup; -import funkin.editors.charter.Charter.ICharterSelectable; import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.Script; +import funkin.editors.charter.Charter.ICharterSelectable; +import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; import funkin.game.Character; import funkin.game.HealthIcon; -import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; -import funkin.backend.chart.ChartData.ChartEvent; +import openfl.display.BitmapData; class CharterEvent extends UISliceSprite implements ICharterSelectable { public var events:Array; @@ -45,50 +48,148 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { sprite.colorTransform = colorTransform; } + /** + * Pack data is a list of 4 strings separated by `________PACKSEP________` + * [0] Event Name + * [1] Event Script + * [2] Event JSON Info + * [3] Event Icon + * [4] Event UI Script / Icon Script + **/ + @:dox(hide) public static function getPackData(name:String):Array { + var packFile = Paths.pack('events/${name}'); + if (Assets.exists(packFile)) { + return Assets.getText(packFile).split('________PACKSEP________'); + } + return null; + } + + @:dox(hide) public static function getUIScript(event:ChartEvent, caller:String):Script { + var uiScript = Paths.script('data/events/${event.name}.ui'); + var script:Script = null; + if(Assets.exists(uiScript)) { + script = Script.create(uiScript); + } else { + var packData = getPackData(event.name); + if(packData != null) { + var scriptFile = packData[4]; + if(scriptFile != null) { + script = Script.fromString(scriptFile, uiScript); + } + } + } + + if(script != null && !(script is DummyScript)) { + // classes and functions + script.set("EventIconGroup", EventIconGroup); // automatically imported + script.set("EventNumber", EventNumber); // automatically imported + script.set("getIconFromStrumline", getIconFromStrumline); + script.set("getIconFromCharName", getIconFromCharName); + script.set("generateDefaultIcon", generateDefaultIcon); + script.set("getPackData", getPackData); + script.set("getEventComponent", getEventComponent); + // data + script.set("event", event); + script.set("caller", caller); + + script.load(); + } + + return script; + } + + /** + * Generates the default event icon for the wanted event + * @param name The name of the event + * @return The icon + **/ private static function generateDefaultIcon(name:String) { var isBase64:Bool = false; var path:String = Paths.image('editors/charter/event-icons/$name'); - if (!Assets.exists(path)) path = Paths.image('editors/charter/event-icons/Unknown'); - if (Assets.exists(Paths.pack('events/$name'))) { - var packimg = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; - if (isBase64 = (packimg != null)) - path = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; + if(!Assets.exists(path)) { + path = Paths.image('editors/charter/event-icons/Unknown'); + + var packData = getPackData(name); + if(packData != null && packData[3] != null) { + isBase64 = true; + path = packData[3]; + } } - var spr = new FlxSprite().loadGraphic(isBase64 ? openfl.display.BitmapData.fromBase64(path.trim(), 'UTF8') : path); + + var spr = new FlxSprite().loadGraphic(isBase64 ? BitmapData.fromBase64(path.trim(), 'UTF8') : path); return spr; } + /** + * Gets a component sprite from the editors/charter/event-icons/components folder + * If you wanna use a number, please use the EventNumber class instead + * @param type The type of component to get + * @param x The x position of the sprite (optional) + * @param y The y position of the sprite (optional) + * @return The component sprite + **/ + public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) { + var componentPath = Paths.image("editors/charter/event-icons/components/" + type); + if(Assets.exists(componentPath)) + return new FlxSprite(x, y).loadGraphic(componentPath); + + Logs.trace('Could not find component $type', WARNING); + return null; + } + + /** + * Expected to be called from inside of a ui script, + * calling this elsewhere might cause unexpected results or crashes + **/ + public static function getIconFromStrumline(index:Null) { + var state = cast(FlxG.state, Charter); + if (index != null && index >= 0 && index < state.strumLines.length) { + return getIconFromCharName(state.strumLines.members[index].strumLine.characters[0]); + } + return null; + } + + public static function getIconFromCharName(name:String) { + var icon = Character.getIconFromCharName(name); + var healthIcon = new HealthIcon(icon); + CoolUtil.setUnstretchedGraphicSize(healthIcon, 32, 32, false); + healthIcon.scrollFactor.set(1, 1); + healthIcon.active = false; + return healthIcon; + } + public static function generateEventIcon(event:ChartEvent):FlxSprite { + var script = getUIScript(event, "event-icon"); + if(script != null && !(script is DummyScript)) { + if(script.get("generateIcon") != null) { + var res:FlxSprite = script.call("generateIcon"); + if(res != null) + return res; + } + } + switch(event.name) { case "Time Signature Change": if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) { - var group = new FlxSpriteGroup(); + var group = new EventIconGroup(); group.add(generateDefaultIcon(event.name)); group.add({ // top var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER); - num.scrollFactor.set(1, 1); num.active = false; num; }); group.add({ // bottom var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER); - num.scrollFactor.set(1, 1); num.active = false; num; }); return group; } case "Camera Movement": - // custom icon for camera movement - var state = cast(FlxG.state, Charter); - if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) { - // camera movement, use health icon - var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]); - var healthIcon = new HealthIcon(icon); - healthIcon.setUnstretchedGraphicSize(32, 32, false); - healthIcon.scrollFactor.set(1, 1); - healthIcon.active = false; - return healthIcon; + // camera movement, use health icon + if(event.params != null) { + var icon = getIconFromStrumline(event.params[0]); + if(icon != null) return icon; } } return generateDefaultIcon(event.name); @@ -135,6 +236,52 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } } +class EventIconGroup extends FlxSpriteGroup { + public var forceWidth:Float = 32; + public var forceHeight:Float = 32; + public var dontTransformChildren:Bool = true; + + public function new() { + super(); + scrollFactor.set(1, 1); + } + + override function preAdd(sprite:FlxSprite):Void + { + super.preAdd(sprite); + sprite.scrollFactor.set(1, 1); + } + + public override function transformChildren(Function:FlxSprite->V->Void, Value:V):Void + { + if (dontTransformChildren) + return; + + super.transformChildren(Function, Value); + } + + override function set_x(Value:Float):Float + { + if (exists && x != Value) + transformChildren(xTransform, Value - x); // offset + return x = Value; + } + + override function set_y(Value:Float):Float + { + if (exists && y != Value) + transformChildren(yTransform, Value - y); // offset + return y = Value; + } + + override function get_width() { + return forceWidth; + } + override function get_height() { + return forceHeight; + } +} + class EventNumber extends FlxSprite { public static inline final ALIGN_NORMAL:Int = 0; public static inline final ALIGN_CENTER:Int = 1; diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 31d62580..c1633303 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -1,10 +1,13 @@ package funkin.editors.charter; -import funkin.backend.chart.ChartData.ChartEvent; -import funkin.backend.system.Conductor; import flixel.group.FlxGroup; -import funkin.backend.chart.EventsData; import flixel.util.FlxColor; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.chart.EventsData; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.Script; +import funkin.backend.system.Conductor; +import funkin.editors.charter.CharterEvent.EventNumber; using StringTools; @@ -109,83 +112,111 @@ class CharterEventScreen extends UISubstateWindow { // destroy old elements paramsFields = []; - for(e in paramsPanel) { - e.destroy(); - paramsPanel.remove(e); + while(paramsPanel.members.length > 0) { + var e = paramsPanel.members.pop(); + if(e != null) { + e.destroy(); + } } if (id >= 0 && id < events.length) { curEvent = id; var curEvent = events[curEvent]; eventName.text = curEvent.name; - // add new elements - var y:Float = eventName.y + eventName.height + 10; - for(k=>param in EventsData.getEventParams(curEvent.name)) { - function addLabel() { - var label:UIText = new UIText(eventName.x, y, 0, param.name); - y += label.height + 4; - paramsPanel.add(label); - }; - var value:Dynamic = CoolUtil.getDefault(curEvent.params[k], param.defValue); - var lastAdded = switch(param.type) { - case TString: - addLabel(); - var textBox:UITextBox = new UITextBox(eventName.x, y, cast value); - paramsPanel.add(textBox); paramsFields.push(textBox); - textBox; - case TBool: - var checkbox = new UICheckbox(eventName.x, y, param.name, cast value); - paramsPanel.add(checkbox); paramsFields.push(checkbox); - checkbox; - case TInt(min, max, step): - addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), 0, min, max); - paramsPanel.add(numericStepper); paramsFields.push(numericStepper); - numericStepper; - case TFloat(min, max, step, precision): - addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), precision, min, max); - paramsPanel.add(numericStepper); paramsFields.push(numericStepper); - numericStepper; - case TStrumLine: - addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value); - paramsPanel.add(dropdown); paramsFields.push(dropdown); - dropdown; - case TColorWheel: - addLabel(); - var colorWheel = new UIColorwheel(eventName.x, y, value is String ? FlxColor.fromString(value) : Std.int(value)); - paramsPanel.add(colorWheel); paramsFields.push(colorWheel); - colorWheel; - case TDropDown(options): - addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); - paramsPanel.add(dropdown); paramsFields.push(dropdown); - dropdown; - default: - paramsFields.push(null); - null; - } - if (lastAdded is UISliceSprite) - y += cast(lastAdded, UISliceSprite).bHeight + 4; - else if (lastAdded is FlxSprite) - y += cast(lastAdded, FlxSprite).height + 6; - } + generateEventUI(curEvent); } else { eventName.text = "No event"; curEvent = -1; } } + function generateEventUI(event:ChartEvent):Void { + var script = CharterEvent.getUIScript(event, "event-ui"); + if(script != null && !(script is DummyScript)) { + script.set("paramsPanel", paramsPanel); + script.set("paramsFields", paramsFields); + if(script.get("generateUI") != null) { + if(script.call("generateUI") == false) + return; + } + } + + // add new elements + var y:Float = eventName.y + eventName.height + 10; + var params = EventsData.getEventParams(event.name); + for(k=>param in params) { + function addLabel() { + var label:UIText = new UIText(eventName.x, y, 0, param.name); + y += label.height + 4; + paramsPanel.add(label); + }; + + var value:Dynamic = CoolUtil.getDefault(event.params[k], param.defValue); + var lastAdded = switch(param.type) { + case TString: + addLabel(); + var textBox:UITextBox = new UITextBox(eventName.x, y, cast value); + paramsPanel.add(textBox); paramsFields.push(textBox); + textBox; + case TBool: + var checkbox = new UICheckbox(eventName.x, y, param.name, cast value); + paramsPanel.add(checkbox); paramsFields.push(checkbox); + checkbox; + case TInt(min, max, step): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), 0, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TFloat(min, max, step, precision): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), precision, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TStrumLine: + addLabel(); + var dropdown = new UIDropDown(eventName.x, y, 320, 32, [ + for(k=>s in cast(FlxG.state, Charter).strumLines.members) + 'Strumline #${k+1} (${s.strumLine.characters[0]})' + ], cast value); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + case TColorWheel: + addLabel(); + var colorWheel = new UIColorwheel(eventName.x, y, CoolUtil.getColorFromDynamic(value)); + paramsPanel.add(colorWheel); paramsFields.push(colorWheel); + colorWheel; + case TDropDown(options): + addLabel(); + var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + default: + paramsFields.push(null); + null; + } + if (lastAdded is UISliceSprite) + y += cast(lastAdded, UISliceSprite).bHeight + 4; + else if (lastAdded is FlxSprite) + y += cast(lastAdded, FlxSprite).height + 6; + } + + if(script != null && !(script is DummyScript)) { + if(script.get("postGenerateUI") != null) { + script.set("params", params); + script.call("postGenerateUI"); + } + } + } + public function saveCurTab() { if (curEvent < 0) return; + var dataParams = EventsData.getEventParams(events[curEvent].name); events[curEvent].params = [ - for(p in paramsFields) { + for(i=>p in paramsFields) { if (p is UIDropDown) { - var dataParams = EventsData.getEventParams(events[curEvent].name); - if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index; + if (dataParams[i].type == TStrumLine) cast(p, UIDropDown).index; else cast(p, UIDropDown).label.text; } else if (p is UINumericStepper) {