diff --git a/assets/editors/layouts/stage/spriteEditScreen.xml b/assets/editors/layouts/stage/spriteEditScreen.xml
new file mode 100644
index 00000000..5b10371f
--- /dev/null
+++ b/assets/editors/layouts/stage/spriteEditScreen.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ function onSave() {
+ sprite.x = xStepper.value;
+ sprite.y = yStepper.value;
+ sprite.zoomFactor = zoomFactorStepper.value;
+ //sprite.antialiasing = antialiasingCheckbox.checked;
+ //sprite.color = colorWheel.color;
+ sprite.spriteAnimType = animType.index;
+
+ function saveXY(obj:Dynamic, name:String, x:UINumericStepper, y:UINumericStepper) {
+ data.xml.remove(name + "");
+ data.xml.remove(name + "x");
+ data.xml.remove(name + "y");
+
+ obj.x = x.value;
+ obj.y = y.value;
+
+ if(x.value == y.value)
+ data.xml.set(name, x.value);
+ else {
+ data.xml.set(name + "x", x.value);
+ data.xml.set(name + "y", y.value);
+ }
+ }
+
+ data.xml.set("x", xStepper.value);
+ data.xml.set("y", yStepper.value);
+ data.xml.set("zoom", zoomFactorStepper.value);
+ data.xml.set("type", ['NONE','BEAT','LOOP'][animType.index]);
+
+ saveXY(sprite.scrollFactor, "scroll", scrollXStepper, scrollYStepper);
+ saveXY(sprite.scale, "scale", scaleXStepper, scaleYStepper);
+ saveXY(sprite.skew, "skew", skewXStepper, skewYStepper);
+ }
+
+
\ No newline at end of file
diff --git a/source/funkin/editors/stage/StageEditor.hx b/source/funkin/editors/stage/StageEditor.hx
index cf7fe342..86cf70a1 100644
--- a/source/funkin/editors/stage/StageEditor.hx
+++ b/source/funkin/editors/stage/StageEditor.hx
@@ -28,8 +28,9 @@ class StageEditor extends UIState {
public var uiGroup:FlxTypedGroup = new FlxTypedGroup();
public var stageSpritesWindow:UIButtonList;
+ public var stageSprites:FlxTypedGroup = new FlxTypedGroup();
- public var xmlMap:Map = new Map();
+ public var xmlMap:Map = new Map();
public var chars:Array = [];
public var charMap:Map = [];
@@ -38,6 +39,8 @@ class StageEditor extends UIState {
public var guideCamera:FlxCamera;
public var uiCamera:FlxCamera;
+ public static var selection:Selection;
+
public var showOutlines:Bool = true;
public var showCharacters:Bool = true;
@@ -168,11 +171,15 @@ class StageEditor extends UIState {
}
}
stage.onNodeLoaded = function(node:Access, sprite:Dynamic):Dynamic {
+ var name = "";
if(sprite is FlxSprite) {
//sprite.forceIsOnScreen = true; // hack
}
if(sprite is FunkinSprite) {
+ var sprite:FunkinSprite = cast sprite;
+ name = sprite.name;
sprite.extra.set(exID("type"), node.name);
+ sprite.extra.set(exID("imageFile"), '${node.getAtt("sprite").getDefault(sprite.name)}');
//sprite.active = false;
}
if(sprite is StageCharPos) {
@@ -183,6 +190,7 @@ class StageEditor extends UIState {
case "girlfriend": "gf";
default: charPos.name;
}
+ name = charPos.name;
var char = new Character(0,0, charName, stage.isCharFlipped(charPos.name, charName == "bf"), true);
char.debugMode = true;
// Play first anim, and make it the last frame
@@ -204,6 +212,11 @@ class StageEditor extends UIState {
}
order.push(sprite);
xmlMap.set(sprite, node);
+
+ //var t = new StageSprite(name, sprite);
+ //t.node = node;
+ //stageSprites.add(t);
+
return sprite;
}
stage.loadXml(stage.stageXML, true);
@@ -217,6 +230,12 @@ class StageEditor extends UIState {
charMap[charPos.name] = char;
}
+ add(stageSprites);
+
+ for(spr in order) {
+
+ }
+
setZoom(stage.defaultZoom);
topMenuSpr = new UITopMenu(topMenu);
@@ -595,4 +614,129 @@ class StageEditor extends UIState {
enum StageChange {
CEditInfo(oldInfo:Xml, newInfo:Xml);
+}
+
+@:forward abstract Selection(Array) from Array to Array {
+ public inline function new(?array:Array)
+ this = array == null ? [] : array;
+
+ // too lazy to put this in every for loop so i made it a abstract
+ //public inline function loop(onNote:CharterNote->Void, ?onEvent:CharterEvent->Void, ?draggableOnly:Bool = true) {
+ // for (s in this) {
+ // if (s is CharterNote && onNote != null && (draggableOnly ? s.draggable: true))
+ // onNote(cast(s, CharterNote));
+ // else if (s is CharterEvent && onEvent != null && (draggableOnly ? s.draggable: true))
+ // onEvent(cast(s, CharterEvent));
+ // }
+ //}
+}
+
+class StageSprite extends UISprite {
+ public var tracker:FlxObject;
+ public var funkinTracker:FunkinSprite;
+ public var node:Access;
+
+ public var name:String;
+
+ public var extra:Map = new Map();
+
+ public var scrollX:Float;
+ public var scrollY:Float;
+ public var scaleX:Float;
+ public var scaleY:Float;
+ public var zoomFactor:Float = 1; // TODO: implement this
+ public var skewX:Float;
+ public var skewY:Float;
+ public var spriteAnimType:XMLAnimType;
+
+ public var skipNegativeBeats:Bool = false;
+ public var beatInterval:Int = 0;
+ public var beatOffset:Int = 0;
+
+ public var lowMemory:Bool;
+ public var highMemory:Bool;
+
+ public var selected:Bool = false;
+ public var draggable:Bool = false;
+
+ public function new(name:String, sprite:FlxObject) {
+ super(x, y);
+ this.name = name;
+ this.tracker = sprite;
+
+ scrollX = tracker.scrollFactor.x;
+ scrollY = tracker.scrollFactor.y;
+ if(tracker is FunkinSprite) {
+ var tracker:FunkinSprite = cast tracker;
+ scaleX = tracker.scale.x;
+ scaleY = tracker.scale.y;
+ funkinTracker = tracker;
+ }
+ else if (tracker is StageCharPos) {
+ var tracker:StageCharPos = cast tracker;
+ var char:Character = tracker.extra.get(StageEditor.exID("char"));
+ scaleX = tracker.scale.x;
+ scaleY = tracker.scale.y;
+ funkinTracker = char;
+ }
+
+ lowMemory = node.x.parent.nodeName == "low-memory";
+ highMemory = node.x.parent.nodeName == "high-memory";
+
+ if(funkinTracker != null) {
+ skewX = funkinTracker.skew.x;
+ skewY = funkinTracker.skew.y;
+ alpha = funkinTracker.alpha;
+ spriteAnimType = funkinTracker.spriteAnimType;
+ antialiasing = funkinTracker.antialiasing;
+ width = funkinTracker.width;
+ height = funkinTracker.height;
+ color = funkinTracker.color;
+
+ skipNegativeBeats = funkinTracker.skipNegativeBeats;
+ beatInterval = funkinTracker.beatInterval;
+ beatOffset = funkinTracker.beatOffset;
+ }
+
+ }
+
+ public override function update(elapsed:Float) {
+ super.update(elapsed);
+ if (tracker.exists) {
+ tracker.x = x;
+ tracker.y = y;
+ tracker.scrollFactor.set(scrollX, scrollY);
+ if(tracker is FlxSprite) {
+ var tracker:FlxSprite = cast tracker;
+ }
+ else if (tracker is StageCharPos) {
+ var tracker:StageCharPos = cast tracker;
+ tracker.skewX = skewX;
+ tracker.skewY = skewY;
+ }
+
+ if(funkinTracker != null) {
+ funkinTracker.scale.set(scaleX, scaleY);
+ funkinTracker.skew.set(skewX, skewY);
+ funkinTracker.alpha = alpha;
+ funkinTracker.spriteAnimType = spriteAnimType;
+ funkinTracker.antialiasing = antialiasing;
+ funkinTracker.width = width;
+ funkinTracker.height = height;
+
+ funkinTracker.skipNegativeBeats = skipNegativeBeats;
+ funkinTracker.beatInterval = beatInterval;
+ funkinTracker.beatOffset = beatOffset;
+
+ funkinTracker.color = color;
+ }
+ }
+ }
+
+ public function handleSelection(selectionBox:UISliceSprite):Bool {
+ return false;
+ };
+ public function handleDrag(change:FlxPoint):Void {
+
+ };
}
\ No newline at end of file
diff --git a/source/funkin/editors/stage/StageSpriteEditScreen.hx b/source/funkin/editors/stage/StageSpriteEditScreen.hx
new file mode 100644
index 00000000..3559044d
--- /dev/null
+++ b/source/funkin/editors/stage/StageSpriteEditScreen.hx
@@ -0,0 +1,286 @@
+package funkin.editors.stage;
+
+import funkin.backend.scripting.HScript;
+import hscript.Interp;
+import haxe.xml.Access;
+import flixel.math.FlxPoint;
+import funkin.backend.chart.ChartData.ChartMetaData;
+import funkin.editors.extra.PropertyButton;
+import funkin.editors.stage.elements.StageElementButton;
+import hscript.*;
+import hscript.Expr.Error;
+import hscript.Parser;
+
+using StringTools;
+
+class StageSpriteEditScreen extends UISubstateWindow {
+ public var data:StageElementButton;
+ public var sprite:FunkinSprite;
+
+ public var saveButton:UIButton;
+ public var closeButton:UIButton;
+
+ var elementMap:Map = [];
+
+
+ public function new(data:StageElementButton) {
+ super();
+ this.data = data;
+ }
+
+ var parser = HScript.initParser();
+ var interp = new Interp();
+
+ function set(name:String, value:Dynamic) {
+ interp.variables.set(name, value);
+ }
+
+ function get(name:String):Dynamic {
+ return interp.variables.get(name);
+ }
+
+ public override function create() {
+ var layout = new Access(Xml.parse(Assets.getText('assets/editors/layouts/stage/spriteEditScreen.xml')).firstElement());
+ winTitle = layout.getAtt("title").getDefault(winTitle);
+ winWidth = Std.parseInt(layout.getAtt("width")).getDefault(winWidth);
+ winHeight = Std.parseInt(layout.getAtt("height")).getDefault(winHeight);
+
+ sprite = data.getSprite();
+
+ set("stage", StageEditor.instance.stage);
+ set("sprite", sprite);
+ set("data", data);
+ set("getEx", function(name:String):Dynamic {
+ return sprite.extra.get(StageEditor.exID(name));
+ });
+ set("last", null);
+
+ function _errorHandler(error:Error) {
+ var fileName = error.origin;
+ var fn = '$fileName:${error.line}: ';
+ var err = error.toString();
+ if (err.startsWith(fn)) err = err.substr(fn.length);
+
+ Logs.traceColored([
+ Logs.logText(fn, GREEN),
+ Logs.logText(err, RED)
+ ], ERROR);
+ }
+
+ function exec(code:String):Dynamic {
+ try {
+ if (code != null && code.trim() != "")
+ return interp.execute(parser.parseString(code, "spriteEditScreen.xml"));
+ } catch(e:Error) {
+ _errorHandler(e);
+ } catch(e) {
+ _errorHandler(new Error(ECustom(e.toString()), 0, 0, "spriteEditScreen.xml", 0));
+ }
+ return null;
+ }
+
+ super.create();
+
+ function addLabelOn(ui:UISprite, text:String)
+ add(new UIText(ui.x, ui.y - 24, 0, text));
+
+ function execString(code:String):String {
+ if(code.indexOf("${") == -1) return code;
+ var text = "";
+ while(code.length > 0) {
+ var idx = code.indexOf("${");
+ if(idx == -1) {
+ text += code;
+ break;
+ }
+ text += code.substring(0, idx);
+ var ei = code.indexOf("}", idx);
+ if(ei == -1) {
+ text += code.substring(idx);
+ break;
+ }
+ var block = code.substring(idx+2, ei);
+ text += exec(block);
+ code = code.substring(ei + 1);
+ }
+ return text;
+ }
+
+ function execDefault(code:String, defaultValue:Dynamic):Dynamic {
+ return CoolUtil.getDefault(exec(code), defaultValue);
+ }
+
+ function execAtt(el:Access, key:String, defaultValue:Dynamic):Dynamic {
+ return execDefault(el.getAtt(key), defaultValue);
+ }
+
+ for(el in layout.elements) {
+ var type = el.name;
+ //var type:String = el.getAtt("type");
+ var name:String = el.getAtt("name");
+ var label:String = execString(el.getAtt("label"));
+ var text:String = execString(el.getAtt("text"));
+ var x:Float = execAtt(el, "x", 0);
+ var y:Float = execAtt(el, "y", 0);
+ var width:Null = exec(el.getAtt("width"));
+ var height:Null = exec(el.getAtt("height"));
+ var size:Int = execAtt(el, "size", 28);
+
+ trace(type, name, label, text, x, y, width, height, size);
+
+ var sprite:FlxSprite = switch(type) {
+ case "set":
+ set(el.getAtt("name"), exec(el.getAtt("value")));
+ continue;
+ case "image":
+ var spr = XMLUtil.createSpriteFromXML(el, "", LOOP);
+ cast add(spr);
+ case "exec":
+ exec(el.innerData);
+ continue;
+ case "textbox":
+ var textBox = new UITextBox(x, y, text, width, height, (el.getAtt("multiline").getDefault("false") == "true"));
+ add(textBox);
+ addLabelOn(textBox, label);
+ textBox;
+ case "title":
+ cast add(new UIText(x, y, 0, label, size));
+ case "stepper":
+ // x:Float, y:Float, value:Float = 0, step:Float = 1, precision:Int = 0, ?min:Float, ?max:Float, w:Int = 180, h:Int = 32
+ var stepper = new UINumericStepper(
+ x,
+ y,
+ execAtt(el, "value", 0),
+ execAtt(el, "step", 1),
+ execAtt(el, "precision", 0),
+ execAtt(el, "min", null),
+ execAtt(el, "max", null),
+ width,
+ height
+ );
+ add(stepper);
+ addLabelOn(stepper, label);
+ stepper;
+ case "checkbox":
+ var checkbox = new UICheckbox(x, y, label, execAtt(el, "value", false));
+ add(checkbox);
+ addLabelOn(checkbox, label);
+ checkbox;
+ case "slider":
+ var slider = new UISlider(x, y, width, execAtt(el, "value", 0), execAtt(el, "segments", []), execAtt(el, "centered", false));
+ add(slider);
+ addLabelOn(slider, label);
+ slider;
+ case "dropdown":
+ var options = execString(el.getAtt("options")).split(",");
+ var curValue = execAtt(el, "value", null);
+ var index = options.indexOf(curValue);
+ if (index == -1) index = 0;
+ var dropdown = new UIDropDown(x, y, width, height, options, index);
+ add(dropdown);
+ addLabelOn(dropdown, label);
+ dropdown;
+ // UIColorwheel
+ // UIAudioPlayer
+ // UIButtonList
+ // UIFileExplorer
+ // UIAutoCompleteTextbox
+ default: {
+ Logs.trace("Unknown element type: " + type, ERROR, RED);
+ Logs.trace("Skipping element: " + el.x.toString(), ERROR, RED);
+ continue;
+ }
+ }
+
+ set("last", sprite);
+ if(name != null) set(name, sprite);
+ if(sprite is UISprite) elementMap.set(name, cast sprite);
+ }
+
+ saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() {
+ saveData();
+ close();
+ }, 125);
+ saveButton.x -= saveButton.bWidth;
+ saveButton.y -= saveButton.bHeight;
+
+ closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
+ close();
+ }, 125);
+ closeButton.color = 0xFFFF0000;
+ closeButton.x -= closeButton.bWidth;
+ //closeButton.y -= closeButton.bHeight;
+ add(closeButton);
+ add(saveButton);
+ }
+
+ /*function updateIcon(icon:String) {
+ if (iconSprite == null) add(iconSprite = new FlxSprite());
+
+ if (iconSprite.animation.exists(icon)) return;
+ @:privateAccess iconSprite.animation.clearAnimations();
+
+ var path:String = Paths.image('icons/$icon');
+ if (!Assets.exists(path)) path = Paths.image('icons/face');
+
+ iconSprite.loadGraphic(path, true, 150, 150);
+ iconSprite.animation.add(icon, [0], 0, false);
+ iconSprite.antialiasing = true;
+ iconSprite.animation.play(icon);
+
+ iconSprite.scale.set(0.5, 0.5);
+ iconSprite.updateHitbox();
+ iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
+ }*/
+
+ function getElement(name:String):UISprite {
+ return elementMap.get(name);
+ }
+
+ function callFunc(name:String, args:Array) {
+ if (interp == null) return null;
+ if (!interp.variables.exists(name)) return null;
+
+ var func = interp.variables.get(name);
+ if (func != null && Reflect.isFunction(func))
+ return Reflect.callMethod(null, func, args);
+
+ return null;
+ }
+
+ public function saveData() {
+ for (el in elementMap) {
+ if(el is UINumericStepper) {
+ var el:UINumericStepper = cast el;
+ @:privateAccess el.__onChange(el.label.text);
+ }
+ }
+
+ var name = cast(getElement("nameTextBox"), UITextBox).label.text;
+ var imageFile = cast(getElement("spriteTextBox"), UITextBox).label.text;
+
+ // Save in sprite
+ sprite.name = name;
+ sprite.extra.set(StageEditor.exID("imageFile"), imageFile);
+
+ // Save in xml
+ data.xml.x.set("name", name);
+ data.xml.x.set("sprite", imageFile);
+
+ callFunc("onSave", []);
+
+ data.updateInfo(sprite);
+ }
+}
+
+typedef Element = {
+ var type:ElementType;
+ var label:String;
+ var ?name:String;
+ var ?text:String;
+}
+
+enum abstract ElementType(Int) {
+ var TITLE = 0;
+ var TEXTBOX = 1;
+}
\ No newline at end of file
diff --git a/source/funkin/editors/stage/elements/StageCharacterButton.hx b/source/funkin/editors/stage/elements/StageCharacterButton.hx
index 57dc4e9d..538ae0eb 100644
--- a/source/funkin/editors/stage/elements/StageCharacterButton.hx
+++ b/source/funkin/editors/stage/elements/StageCharacterButton.hx
@@ -36,6 +36,10 @@ class StageCharacterButton extends StageElementButton {
super.updateInfo(charPos);
}
+ public override function getSprite():FunkinSprite {
+ return char;
+ }
+
public override function onSelect() {
}
@@ -45,8 +49,12 @@ class StageCharacterButton extends StageElementButton {
updateInfo(this.charPos);
}
+ public override function getName():String {
+ return charPos.name;
+ }
+
public override function getInfoText():String {
- return '${charPos.name} (${charPos.x}, ${charPos.y})';
+ return '${getName()} (${charPos.x}, ${charPos.y})';
}
public override function updatePos() {
diff --git a/source/funkin/editors/stage/elements/StageElementButton.hx b/source/funkin/editors/stage/elements/StageElementButton.hx
index a834e653..dc21dcc5 100644
--- a/source/funkin/editors/stage/elements/StageElementButton.hx
+++ b/source/funkin/editors/stage/elements/StageElementButton.hx
@@ -90,6 +90,14 @@ class StageElementButton extends UIButton {
deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8;
}
+ public function getSprite():FunkinSprite {
+ return null;
+ }
+
+ public function getName():String {
+ return "UNKNOWN";
+ }
+
public function onSelect() {
// TODO: implement
}
diff --git a/source/funkin/editors/stage/elements/StageSolidButton.hx b/source/funkin/editors/stage/elements/StageSolidButton.hx
index 31eeba1d..698069e8 100644
--- a/source/funkin/editors/stage/elements/StageSolidButton.hx
+++ b/source/funkin/editors/stage/elements/StageSolidButton.hx
@@ -5,7 +5,7 @@ import flixel.util.FlxColor;
class StageSolidButton extends StageSpriteButton {
- public function new(x:Float,y:Float, sprite:FlxSprite, xml:Access) {
+ public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) {
super(x,y, sprite, xml);
color = 0xFFD9FF50;
}
diff --git a/source/funkin/editors/stage/elements/StageSpriteButton.hx b/source/funkin/editors/stage/elements/StageSpriteButton.hx
index 425ffd2d..0e509389 100644
--- a/source/funkin/editors/stage/elements/StageSpriteButton.hx
+++ b/source/funkin/editors/stage/elements/StageSpriteButton.hx
@@ -1,12 +1,13 @@
package funkin.editors.stage.elements;
import haxe.xml.Access;
+import funkin.editors.stage.StageSpriteEditScreen;
import flixel.util.FlxColor;
class StageSpriteButton extends StageElementButton {
- public var sprite:FlxSprite;
+ public var sprite:FunkinSprite;
- public function new(x:Float,y:Float, sprite:FlxSprite, xml:Access) {
+ public function new(x:Float,y:Float, sprite:FunkinSprite, xml:Access) {
this.sprite = sprite;
super(x,y, xml);
@@ -20,14 +21,18 @@ class StageSpriteButton extends StageElementButton {
}
public override function updateInfo(sprite:Dynamic) {
- if(sprite is FlxSprite) {
- var sprite:FlxSprite = cast sprite;
+ if(sprite is FunkinSprite) {
+ var sprite:FunkinSprite = cast sprite;
this.sprite = sprite;
sprite.visible = !isHidden;
}
super.updateInfo(sprite);
}
+ public override function getSprite():FunkinSprite {
+ return sprite;
+ }
+
public override function onGhostClick() {
isHidden = !isHidden;
updateInfo(this.sprite);
@@ -35,6 +40,7 @@ class StageSpriteButton extends StageElementButton {
public override function onEdit() {
// TODO: implement
+ FlxG.state.openSubState(new StageSpriteEditScreen(this));
}
public override function onDelete() {
@@ -44,8 +50,12 @@ class StageSpriteButton extends StageElementButton {
StageEditor.instance.stageSpritesWindow.remove(this);
}
+ public override function getName():String {
+ return xml.att.name;
+ }
+
public override function getInfoText():String {
- return '${xml.att.name} (${sprite.x}, ${sprite.y})';
+ return '${getName()} (${sprite.x}, ${sprite.y})';
}
public override function updatePos() {
diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx
index db547bfe..b04c8c13 100644
--- a/source/funkin/game/Stage.hx
+++ b/source/funkin/game/Stage.hx
@@ -32,7 +32,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
public var onNodeFinished:(Access, Dynamic)->Void = null;
public var onAddSprite:(FlxObject)->Void = null;
- private var spritesParentFolder = "";
+ public var spritesParentFolder = "";
public inline function getSprite(name:String) {
return stageSprites[name];