diff --git a/assets/images/editors/character/WOAH.png b/assets/images/editors/character/WOAH.png new file mode 100644 index 00000000..13d95d50 Binary files /dev/null and b/assets/images/editors/character/WOAH.png differ diff --git a/assets/images/editors/character/delete-button.png b/assets/images/editors/character/delete-button.png new file mode 100644 index 00000000..0bf75235 Binary files /dev/null and b/assets/images/editors/character/delete-button.png differ diff --git a/assets/images/editors/character/edit-button.png b/assets/images/editors/character/edit-button.png new file mode 100644 index 00000000..849dae29 Binary files /dev/null and b/assets/images/editors/character/edit-button.png differ diff --git a/assets/images/editors/character/ghost-button.png b/assets/images/editors/character/ghost-button.png new file mode 100644 index 00000000..65e136bc Binary files /dev/null and b/assets/images/editors/character/ghost-button.png differ diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 25b89603..be2ff6fe 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -1,5 +1,6 @@ package funkin.backend; +import funkin.backend.utils.XMLUtil.AnimData; import funkin.backend.utils.XMLUtil.IXMLEvents; import flixel.system.FlxAssets.FlxGraphicAsset; import flixel.addons.effects.FlxSkewedSprite; @@ -37,6 +38,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public var name:String; public var zoomFactor:Float = 1; public var initialZoom:Float = 1; + public var debugMode:Bool = false; + public var animDatas:Map = []; public var animateAtlas:FlxAnimate; @:noCompletion public var atlasPlayingAnim:String; @@ -287,7 +290,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I } else { - if (!animation.exists(AnimName)) + if (!animation.exists(AnimName) && !debugMode) return; animation.play(AnimName, Force, Reversed, Frame); } @@ -325,6 +328,27 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I return name; } + public inline function removeAnimation(name:String) { + if (animateAtlas != null) + @:privateAccess animateAtlas.anim.animsMap.remove(name); + else + animation.remove(name); + } + + public inline function getNameList():Array { + if (animateAtlas != null) + return [for (name in @:privateAccess animateAtlas.anim.animsMap.keys()) name]; + else + return animation.getNameList(); + } + + public inline function stopAnimation() { + if (animateAtlas != null) + animateAtlas.anim.pause(); + else + animation.stop(); + } + public inline function isAnimFinished() { return animateAtlas != null ? (animateAtlas.anim.finished) : (animation.curAnim != null ? animation.curAnim.finished : true); diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index d6ed7091..01d7aa8d 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -1,5 +1,6 @@ package funkin.backend.utils; +import flixel.animation.FlxAnimation; import funkin.backend.FunkinSprite; import funkin.backend.system.ErrorCode; import funkin.backend.FunkinSprite.XMLAnimType; @@ -138,7 +139,7 @@ class XMLUtil { if (anim.has.name) animData.name = anim.att.name; if (anim.has.type) animData.animType = XMLAnimType.fromString(anim.att.type, animData.animType); if (anim.has.anim) animData.anim = anim.att.anim; - if (anim.has.fps) animData.fps = Std.parseInt(anim.att.fps); + if (anim.has.fps) animData.fps = Std.parseFloat(anim.att.fps); if (anim.has.x) animData.x = Std.parseFloat(anim.att.x); if (anim.has.y) animData.y = Std.parseFloat(anim.att.y); if (anim.has.loop) animData.loop = anim.att.loop == "true"; @@ -196,6 +197,7 @@ class XMLUtil { default: // nothing } + xmlSpr.animDatas.set(animData.name, animData); } return OK; } @@ -211,7 +213,7 @@ class XMLUtil { typedef AnimData = { var name:String; var anim:String; - var fps:Int; + var fps:Float; var loop:Bool; var x:Float; var y:Float; diff --git a/source/funkin/editors/character/AddAnimScreen.hx b/source/funkin/editors/character/AddAnimScreen.hx deleted file mode 100644 index c01ad927..00000000 --- a/source/funkin/editors/character/AddAnimScreen.hx +++ /dev/null @@ -1,113 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; - -class AddAnimScreen extends UISubstateWindow -{ - public var char:Character; - public var ghostChar:Character; - - public var addButton:UIButton; - public var cancelButton:UIButton; - public var editor:CharacterEditor; - - public var animName:UITextBox; - public var animPrefix:UITextBox; - public var fps:UITextBox; - public var loop:UICheckbox; - public var indices:UITextBox; - - public function new(char:Character, ghostChar, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - } - - public override function create() - { - winTitle = "Add new Animation"; - winWidth = 960; - - super.create(); - - // Animation Properties - var y_ofs = 60; - var y_text_ofs = 20; - - animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, ""); - add(animName); - add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: ")); - - animPrefix = new UITextBox(animName.x, animName.y + y_ofs, ""); - add(animPrefix); - add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: ")); - - fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, "24"); - add(fps); - add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: ")); - - loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", false); - add(loop); - - indices = new UITextBox(loop.x, loop.y + y_ofs, ""); - add(indices); - add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): ")); - - // Add / Cancel Buttons - addButton = new UIButton(windowSpr.x + 1, windowSpr.y + 31, "", function() - { - newAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text); - close(); - }); - addButton.bWidth = addButton.bHeight = 30; - add(addButton); - - var addButtonIcon = new FlxSprite(addButton.x, addButton.y, Paths.image('editors/charter/add-button')); - addButtonIcon.x += (30 - addButtonIcon.width) / 2; - addButtonIcon.y += (30 - addButtonIcon.height) / 2; - add(addButtonIcon); - - cancelButton = new UIButton(addButton.x - 10, addButton.y, "Cancel", function() - { - close(); - }); - cancelButton.x -= cancelButton.bWidth; - add(cancelButton); - } - - public function newAnimation(name:String, anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String) - { - var animType:XMLAnimType = NONE; - - var animData:AnimData = { - name: name, - anim: anim, - fps: fps, - loop: looped, - animType: animType, - indices: [], - x: 0, - y: 0 - }; - - if (indices != "" && indices != null) - { - var indicesSplit = indices.split(","); - - for (indice in indicesSplit) - { - var i = Std.parseInt(indice.trim()); - if (i != null) - animData.indices.push(i); - } - } - - XMLUtil.addAnimToSprite(char, animData); - XMLUtil.addAnimToSprite(ghostChar, animData); - char.characterData.animations.push(animData); - ghostChar.characterData.animations.push(animData); - } -} diff --git a/source/funkin/editors/character/CharacterAnimScreen.hx b/source/funkin/editors/character/CharacterAnimScreen.hx new file mode 100644 index 00000000..cbfa5e9f --- /dev/null +++ b/source/funkin/editors/character/CharacterAnimScreen.hx @@ -0,0 +1,122 @@ +package funkin.editors.character; + +import flixel.math.FlxPoint; +import flixel.animation.FlxAnimation; +import funkin.backend.utils.XMLUtil.AnimData; + +class CharacterAnimScreen extends UISubstateWindow { + public var animData:AnimData; + + public var nameTextBox:UITextBox; + public var animTextBox:UITextBox; + public var fpsStepper:UINumericStepper; + public var loopedCheckbox:UICheckbox; + public var indicesTextBox:UITextBox; + public var offsetXStepper:UINumericStepper; + public var offsetYStepper:UINumericStepper; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public var onSave:(animData:AnimData) -> Void = null; + + public function new(animData:AnimData, ?onSave:(animData:AnimData) -> Void) { + this.animData = animData; + this.onSave = onSave; + super(); + } + + public override function create() { + var creatingAnim:Bool = animData == null; + + if (creatingAnim) + animData = { + name: "name", + anim: "anim", + fps: 24, + loop: false, + animType: NONE, + x: 0, + y: 0, + indices: [] + }; + + winTitle = creatingAnim ? 'Creating Animation' : 'Animation ${animData.name} properties'; + winWidth = 690; winHeight = 314; + + super.create(); + + function addLabelOn(ui:UISprite, text:String) + add(new UIText(ui.x, ui.y - 24, 0, text)); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, creatingAnim ? "Create New Animation" : "Edit Animation Properties", 28)); + + nameTextBox = new UITextBox(title.x, title.y + title.height + 38, animData.name, 160); + add(nameTextBox); + addLabelOn(nameTextBox, "Name"); + + animTextBox = new UITextBox(nameTextBox.x + 160 + 26, nameTextBox.y, animData.anim, 200); + add(animTextBox); + addLabelOn(animTextBox, "Animation"); + + fpsStepper = new UINumericStepper(animTextBox.x + 200 + 26, animTextBox.y, animData.fps, 0.1, 2, 1, 100, 82); + add(fpsStepper); + addLabelOn(fpsStepper, "FPS"); + + loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, "Looped", animData.loop); + add(loopedCheckbox); + addLabelOn(loopedCheckbox, "Looping"); + loopedCheckbox.x += 8; loopedCheckbox.y += 6; + + indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y + 32 + 40, animData.indices.join(", "), 270); + add(indicesTextBox); + addLabelOn(indicesTextBox, "Indices"); + + offsetXStepper = new UINumericStepper(indicesTextBox.x + 270 + 26, indicesTextBox.y, animData.x, 0.001, 2, null, null, 84); + add(offsetXStepper); + addLabelOn(offsetXStepper, "Offset (X,Y)"); + + add(new UIText(offsetXStepper.x + 84 - 32 + 0, offsetXStepper.y + 9, 0, ",", 22)); + + offsetYStepper = new UINumericStepper(offsetXStepper.x + 84 - 32 + 26, offsetXStepper.y, animData.y, 0.001, 2, null, null, 84); + add(offsetYStepper); + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + saveAnimData(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, creatingAnim ? "Cancel" : "Close", function() { + if (creatingAnim && onSave != null) onSave(null); + close(); + }, 125); + add(closeButton); + closeButton.x -= closeButton.bWidth; + } + + public function saveAnimData() { + for (stepper in [offsetXStepper, offsetYStepper, fpsStepper]) + @:privateAccess stepper.__onChange(stepper.label.text); + + animData = { + name: nameTextBox.label.text, + anim: animTextBox.label.text, + fps: fpsStepper.value, + loop: loopedCheckbox.checked, + animType: NONE, + x: offsetXStepper.value, + y: offsetYStepper.value, + indices: [] + }; + var indicesSplit = indicesTextBox.label.text.split(","); + for(indice in indicesSplit) { + var i = Std.parseInt(indice.trim()); + if (i != null) + animData.indices.push(i); + } + + if (onSave != null) onSave(animData); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterAnimsWindow.hx b/source/funkin/editors/character/CharacterAnimsWindow.hx new file mode 100644 index 00000000..eafa2de1 --- /dev/null +++ b/source/funkin/editors/character/CharacterAnimsWindow.hx @@ -0,0 +1,176 @@ +package funkin.editors.character; + +import flixel.group.FlxGroup; +import flixel.util.FlxColor; +import flixel.math.FlxPoint; +import funkin.game.Character; + +class CharacterAnimsWindow extends UIWindow { + public var character:Character; + + public var buttons:FlxGroup = new FlxGroup(); + public var animButtons:Map = []; + + public var addButton:UIButton; + public var addIcon:FlxSprite; + + public function new(character:Character) { + super(800-23,140+23+46,450+23,511-23, "Character Animations"); + this.character = character; + + createButtons(); + buttons.cameras = [CharacterEditor.instance.animsCamera]; + members.push(buttons); + } + + public function createButtons() { + for (i=>anim in character.getNameList()) + createNewButton(anim, character.getAnimOffset(anim), false, -1, false); + + addButton = new UIButton(25, 16+((32+16)*(character.getNameList().length)), "", + function () { + CharacterEditor.instance.createAnimWithUI(); + } + , 426); + addButton.cameras = [CharacterEditor.instance.animsCamera]; + addButton.color = FlxColor.GREEN; + + addIcon = new FlxSprite(addButton.x + (440/2) - 8, addButton.y + (32/2) - 8).loadGraphic(Paths.image('editors/charter/add-button')); + addIcon.cameras = [CharacterEditor.instance.animsCamera]; + addIcon.antialiasing = false; + addButton.members.push(addIcon); + + members.push(addButton); + updateButtonsPos(); + } + + public function createNewButton(anim:String, offset:FlxPoint, ghost:Bool = false, id:Int = -1, ?updatePos:Bool = true) { + var button = new CharacterAnimButtons(0,0, anim, offset); + if (id == -1) buttons.add(button); + else buttons.members.insert(id, button); + animButtons.set(anim,button); + + if (updatePos) + updateButtonsPos(); + } + + public function removeButton(anim:String) { + var button:CharacterAnimButtons = animButtons.get(anim); + buttons.members.remove(button); + button.destroy(); + + updateButtonsPos(); + } + + public function updateButtonsPos() { + for (i => button in buttons.members) + cast(button, CharacterAnimButtons).updatePos(23,16+((32+16)*i)); + + addButton.y = 16+((32+16)*(buttons.members.length)); + addIcon.x = addButton.x + (440/2) - 8; addIcon.y = addButton.y + (32/2) - 8; + } + + public inline function changeCurAnim(anim:String) { + for (_anim => button in animButtons) + button.alpha = _anim == anim ? 1 : 0.25; + } + + var scrollY:Float = 0; + + public override function update(elapsed:Float) { + super.update(elapsed); + + __rect.x = x; __rect.y = y+23; + __rect.width = bWidth; __rect.height = bHeight-23; + if(UIState.state.isOverlapping(this, __rect)) { + var nextscrollY = scrollY - FlxG.mouse.wheel * 12; + if (nextscrollY >= 0 && nextscrollY + CharacterEditor.instance.animsCamera.height <= (addButton.y +32 +23)) + scrollY = nextscrollY; + hovered = true; + } else + hovered = false; + + for (button in buttons.members) + cast(button, UISprite).selectable = hovered; + addButton.selectable = hovered; + + CharacterEditor.instance.animsCamera.scroll.y = FlxMath.lerp(CharacterEditor.instance.animsCamera.scroll.y, scrollY, 1/3); + + //addButtonIndicator.y = CharacterEditor.instance.animsCamera.height - addButtonIndicator.width + CharacterEditor.instance.animsCamera.scroll.y; + } +} + +class CharacterAnimButtons extends UIButton { + public var anim:String = ""; + + public var editButton:UIButton; + public var editIcon:FlxSprite; + + public var ghostButton:UIButton; + public var ghostIcon:FlxSprite; + + public var deleteButton:UIButton; + public var deleteIcon:FlxSprite; + + public function new(x:Float,y:Float,anim:String, offset:FlxPoint) { + this.anim = anim; + super(x,y, '$anim (${offset.x}, ${offset.y})', function () { + CharacterEditor.instance.playAnimation(this.anim); + }, 282); + + ghostButton = new UIButton(x+282+17, y, "", function () { + CharacterEditor.instance.ghostAnim(this.anim); + }, 32); + members.push(ghostButton); + + ghostIcon = new FlxSprite(ghostButton.x + 8, ghostButton.y + 8).loadGraphic(Paths.image('editors/character/ghost-button'), true, 16, 16); + ghostIcon.animation.add("alive", [0]); + ghostIcon.animation.add("dead", [1]); + ghostIcon.animation.play("dead"); ghostIcon.alpha = 0.5; + ghostIcon.antialiasing = false; + ghostIcon.updateHitbox(); + members.push(ghostIcon); + + editButton = new UIButton(ghostButton.x+32+17, y, "", function () { + CharacterEditor.instance.editAnimWithUI(this.anim); + }, 32); + members.push(editButton); + + editIcon = new FlxSprite(editButton.x + 8, editButton.y + 8).loadGraphic(Paths.image('editors/character/edit-button')); + editIcon.antialiasing = false; + members.push(editIcon); + + deleteButton = new UIButton(editButton.x+32+17, y, "", function () { + CharacterEditor.instance.deleteAnim(this.anim); + }, 32); + deleteButton.color = FlxColor.RED; + members.push(deleteButton); + + deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/character/delete-button')); + deleteIcon.antialiasing = false; + members.push(deleteIcon); + } + + public override function update(elapsed:Float) { + editButton.selectable = ghostButton.selectable = deleteButton.selectable = selectable; + super.update(elapsed); + } + + public function updateInfo(anim:String, offset:FlxPoint, ghost:Bool) { + this.anim = anim; + + field.text = '$anim (${offset.x}, ${offset.y})'; + ghostIcon.animation.play(ghost ? "alive" : "dead"); + ghostIcon.alpha = ghost ? 1 : 0.5; + } + + public inline function updatePos(x:Float, y:Float) { + // buttons + this.x = x; this.y = ghostButton.y = editButton.y = deleteButton.y = y; + deleteButton.x = (editButton.x = (ghostButton.x = (x+282+17))+32+17)+32+17; + // icons + ghostIcon.x = ghostButton.x + 8; ghostIcon.y = ghostButton.y + 8; + editIcon.x = editButton.x + 8; editIcon.y = editButton.y + 8; + deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8; + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterConfig.hx b/source/funkin/editors/character/CharacterConfig.hx deleted file mode 100644 index 0c93959e..00000000 --- a/source/funkin/editors/character/CharacterConfig.hx +++ /dev/null @@ -1,169 +0,0 @@ -package funkin.editors.character; - -import haxe.xml.Printer; -import sys.io.File; -import sys.FileSystem; -import flixel.group.FlxGroup; -import haxe.xml.Access; -import haxe.Exception; -import flixel.util.FlxColor; -import funkin.game.Character.CharacterData; - -class CharacterConfig extends FlxTypedGroup -{ - public static var DEFAULT_CHARACTER = "bf"; - - public static function loadCharacterData(character:String):CharacterData - { - var xml:Access; - - var charData:CharacterData = { - offsetY: 0, - offsetX: 0, - camOffsetY: 0, - camOffsetX: 0, - quickDancer: false, - icon: character, - iconColor: FlxColor.fromRGB(94, 94, 94), - scale: 1, - isPlayer: false, - isGF: false, - holdTime: 4, - flipX: false, - antialiasing: true, - sprite: "bf", - name: character, - animations: [] - } - - var xmlPath = Paths.xml('characters/$character'); - - var plainXML = Assets.getText(xmlPath); - try - { - var charXML = Xml.parse(plainXML).firstElement(); - if (charXML == null) - throw new Exception("Missing \"character\" node in XML."); - xml = new Access(charXML); - } - - charData = xmlToData(charData, xml); - - return charData; - } - - public static function xmlToData(charData:CharacterData, xml:Access):CharacterData - { - if (xml.has.isPlayer) - charData.isPlayer = (xml.att.isPlayer == "true"); - if (xml.has.isGF) - charData.isGF = (xml.att.isGF == "true"); - if (xml.has.x) - charData.offsetX = Std.parseFloat(xml.att.x); - if (xml.has.y) - charData.offsetY = Std.parseFloat(xml.att.y); - if (xml.has.gameOverChar) - charData.gameOverChar = xml.att.gameOverChar; - if (xml.has.camx) - charData.camOffsetX = Std.parseFloat(xml.att.camx); - if (xml.has.camy) - charData.camOffsetY = Std.parseFloat(xml.att.camy); - if (xml.has.holdTime) - charData.holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4); - if (xml.has.flipX) - charData.flipX = (xml.att.flipX == "true"); - if (xml.has.icon) - charData.icon = xml.att.icon; - if (xml.has.iconColor) - charData.iconColor = stringToRGB(xml.att.iconColor); - if (xml.has.scale) - { - var scale = Std.parseFloat(xml.att.scale).getDefault(1); - charData.scale = scale; - } - if (xml.has.antialiasing) - charData.antialiasing = (xml.att.antialiasing == "true"); - if (xml.has.sprite) - charData.sprite = xml.att.sprite; - else - charData.sprite = charData.name; - - for (anim in xml.nodes.anim) - { - var animType:XMLAnimType = NONE; - var animloop = false; - if (anim.has.loop) - animloop = (anim.att.loop == "true"); - charData.animations.push(XMLUtil.extractAnimFromXML(anim, animType, animloop)); - } - return charData; - } - - public static function dataToXml(charData:CharacterData):Access - { - var root = Xml.createElement("character"); - root.set("x", Std.string(Math.floor(charData.offsetX))); - root.set("y", Std.string(Math.floor(charData.offsetY))); - root.set("gameOverChar", Std.string(charData.gameOverChar)); - root.set("camx", Std.string(Math.floor(charData.camOffsetX))); - root.set("camy", Std.string(Math.floor(charData.camOffsetY))); - root.set("holdTime", Std.string(charData.holdTime)); - root.set("flipX", Std.string(charData.flipX)); - root.set("icon", charData.icon); - root.set("iconColor", Std.string(charData.iconColor)); - root.set("scale", Std.string(charData.scale)); - root.set("antialiasing", Std.string(charData.antialiasing)); - root.set("sprite", Std.string(charData.sprite)); - root.set("isPlayer", Std.string(charData.isPlayer)); - root.set("isGF", Std.string(charData.isGF)); - - for (anim in charData.animations) - { - var child:Xml = Xml.createElement('anim'); - child.set("name", Std.string(anim.name)); - child.set("anim", Std.string(anim.anim)); - child.set("loop", Std.string(anim.loop)); - child.set("fps", Std.string(anim.fps)); - child.set("x", Std.string(anim.x)); - child.set("y", Std.string(anim.y)); - child.set("indices", Std.string(anim.indices)); - root.addChild(child); - } - var xml = new Access(Xml.parse('${root.toString()}')); - - return xml; - } - - public static function stringToRGB(rgbColor:String):FlxColor - { - var colorSplit = rgbColor.split(","); - if (colorSplit[0] != null && colorSplit[1] != null && colorSplit[2] != null) - return FlxColor.fromRGB(Std.parseInt(colorSplit[0]), Std.parseInt(colorSplit[1]), Std.parseInt(colorSplit[2])); - else - return FlxColor.fromRGB(90,90,90); - } - - public static function rgbToString(color:FlxColor):String - return '${color.red}, ${color.green}, ${color.blue}'; - /** - * @return Saves the character to the specific song folder path. - */ - public static function save(character:String, xml:String):String - { - var charPath = ''; - var data = '$xml'; - #if sys - var charPath = '${character}'; - - File.saveContent(charPath, data); - - // idk how null reacts to it so better be sure - #end - return xml; - } - - public static function generateCharXML(xml:Xml):String - { - return '\n${Printer.print(xml, true)}'; - } -} diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index 2fbaab43..b055224b 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -1,318 +1,64 @@ package funkin.editors.character; -import funkin.backend.system.framerate.Framerate; -import funkin.backend.FlxAnimate; -import flixel.group.FlxSpriteGroup; -import flixel.graphics.FlxGraphic; +import haxe.xml.Printer; +import haxe.xml.Access; +import funkin.editors.character.CharacterAnimsWindow.CharacterAnimButtons; import funkin.backend.utils.XMLUtil.AnimData; import flixel.math.FlxPoint; +import flixel.animation.FlxAnimation; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; +import flixel.input.keyboard.FlxKey; import funkin.game.Character; -import flixel.system.debug.interaction.tools.Pointer.GraphicCursorCross; -import flixel.util.FlxColor; -class CharacterEditor extends UIState -{ - public var blockedInputs = []; - public var blockedStuff = []; +class CharacterEditor extends UIState { + var __character:String; + public var character:Character; - public var curChar:String; + public var ghosts:CharacterGhostsHandler; - public var char:Character; - public var ghostChar:Character; - public var charLayer:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(0, 0); - public var animAtlas:FlxAnimate; + public static var instance(get, null):CharacterEditor; - public var cameraPointer:FlxSprite; - - public var camFollow:FlxObject; - public var editorCamera:FlxCamera; - public var uiCamera:FlxCamera; + private static inline function get_instance() + return FlxG.state is CharacterEditor ? cast FlxG.state : null; + /** + * CHARACTER UI STUFF + */ + public var characterBG:FunkinSprite; public var topMenu:Array; public var topMenuSpr:UITopMenu; public var uiGroup:FlxTypedGroup = new FlxTypedGroup(); - public var stage:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(0, 0); - public var curAnim:Int = 0; + // WINDOWS + public var characterPropertiresWindow:CharacterPropertiesWindow; + public var characterAnimsWindow:CharacterAnimsWindow; + + // camera for the character itself so that it can be unzoomed/zoomed in again + public var charCamera:FlxCamera; + // camera for the animations list + public var animsCamera:FlxCamera; + // camera for the ui + public var uiCamera:FlxCamera; - public var playingAnim:UIText; - public var currentChar:UIText; - public var movingCamText:UIText; + public var undoList:Array = []; + public var redoList:Array = []; - //Healthbar - public var healthBarBG:FlxSprite; - public var icon:FlxSprite; - - public var camZoom(default, set):Float = 1; - - public function new(charName:String) - { + public function new(character:String) { super(); - this.curChar = charName; + __character = character; } - override function create() - { + public override function create() { super.create(); - - // Camera setup - camFollow = new FlxObject(0, 0, 2, 2); - camFollow.screenCenter(); - add(camFollow); - editorCamera = FlxG.camera; - uiCamera = new FlxCamera(); - uiCamera.bgColor = 0; - FlxG.cameras.add(uiCamera, false); - FlxG.camera.follow(camFollow); - - // Add Stage and Centered Cross - add(stage); - add(ofs_cross); - loadCross(); - - // Indicators - var pointer:FlxGraphic = FlxGraphic.fromClass(GraphicCursorCross); - cameraPointer = new FlxSprite().loadGraphic(pointer); - cameraPointer.setGraphicSize(40, 40); - cameraPointer.updateHitbox(); - cameraPointer.color = FlxColor.WHITE; - - // Character - add(charLayer); - add(cameraPointer); - loadChar(!CharacterConfig.loadCharacterData(curChar).isPlayer); - - // Reload Stage - reloadStage(); - - // UI - loadUI(); - - playingAnim = new UIText(846.62, 64.23, 0, 'Animation: ${char.characterData.animations[curAnim].name}', 24); - playingAnim.alignment = LEFT; - playingAnim.cameras = [uiCamera]; - - currentChar = new UIText(846.62, 30.23, 0, 'Character: ${char.curCharacter}', 24); - currentChar.alignment = LEFT; - currentChar.cameras = [uiCamera]; - - movingCamText = new UIText((FlxG.width / 4) - 310, (FlxG.height / 2), -1, - "Press the left mouse button anywhere on this stage to set the character's camera position.", 24); - movingCamText.cameras = [uiCamera]; - movingCamText.visible = false; - - //Health Bar - healthBarBG = new FlxSprite(747, 102).loadGraphic(Paths.image('game/healthBar')); - healthBarBG.setGraphicSize(399,13); - healthBarBG.scrollFactor.set(); - healthBarBG.cameras = [uiCamera]; - - - var charData:CharacterData; - charData = char.characterData; - - var path = Paths.image('icons/${charData.icon}'); - if (!Assets.exists(path)) path = Paths.image('icons/face'); - - //Show both Losing Normal Icons - icon = new FlxSprite(754, 86).loadGraphic(path); - icon.scale.set(0.5,0.5); - icon.scrollFactor.set(); - icon.cameras = [uiCamera]; - - - - - - healthBarBG.color = charData.iconColor; - - // Top Menu - add(currentChar); - add(playingAnim); - add(icon); - add(healthBarBG); - add(movingCamText); - add(topMenuSpr); - add(uiGroup); - } - - override function update(elapsed:Float) - { - // Control - controlCamera(elapsed); - controlCharacter(); - changeAnim(); - moveCharPos(); - detectCamClick(); - mouseControl(); - - ghostChar.setPosition(char.x, char.y); - super.update(elapsed); - } - - public function reloadIcon() - { - var charData:CharacterData; - charData = char.characterData; - - var path = Paths.image('icons/${charData.icon}'); - if (!Assets.exists(path)) path = Paths.image('icons/face'); - icon.loadGraphic(path); - } - - public function searchAnim(name:String, ?array:Array):AnimData - { - if (array == null) - array = char.characterData.animations; - for (i in 0...array.length - 1) - if (array[i].name == name) - return array[i]; - return array[0]; - } - - public function getAnimIndex(name:String, ?array:Array):Int - { - if (array == null) - array = char.characterData.animations; - for (i in 0...array.length - 1) - if (array[i].name == name) - return i; - return 0; - } - - function resortAnims(animations:Array) - { - // TODO: add support for danceLeft danceRight - if (getAnimIndex("idle") != 0) - { - var oldanims = animations.copy(); - animations.clear(); - animations.push(searchAnim("idle", oldanims)); - for (anim in oldanims) - if (anim != searchAnim("idle", oldanims)) - animations.push(anim); - } - } - - public function reloadChar(isOpponent:Bool, charData:CharacterData) - { - var i:Int = charLayer.members.length - 1; - while (i >= 0) - { - var memb:Character = charLayer.members[i]; - if (memb != null) - { - memb.kill(); - charLayer.remove(memb); - memb.destroy(); - } - --i; - } - charLayer.clear(); - ghostChar = new Character(0, 0, curChar, !isOpponent); - ghostChar.characterData = charData; - ghostChar.debugMode = true; - ghostChar.alpha = 0.6; - - char = new Character(0, 0, curChar, !isOpponent); - char.characterData = charData; - - var charData = char.characterData; - resortAnims(charData.animations); - var availableAnim = searchAnim("idle"); - curAnim = getAnimIndex("idle"); - ghostChar.characterData.animations = char.characterData.animations; - char.playAnim(availableAnim.name, true); - ghostChar.playAnim(availableAnim.name, true); - - char.debugMode = true; - char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY); - - charLayer.add(ghostChar); - charLayer.add(char); - - animAtlas = (char.animateAtlas != null ? char.animateAtlas : null); - - currentChar.text = 'Character: ${char.curCharacter}'; - healthBarBG.color = charData.iconColor; - reloadIcon(); - - trace(charData.iconColor); - - updateCharPos(charData.offsetX, charData.offsetY); - updatePointerPos(); - mixDataToChar(char, charData); - mixDataToChar(ghostChar, charData); - } - - function mixDataToChar(target:Character, data:CharacterData) - { - var sprite = (target.xml.has.sprite ? target.xml.att.sprite : target.curCharacter); - if (data.sprite != sprite) - target.loadSprite(Paths.image('characters/${data.sprite}')); - target.icon = data.icon; - target.flipX = !!data.flipX; - target.antialiasing = data.antialiasing; - target.isPlayer = data.isPlayer; - target.playerOffsets = data.isPlayer; - target.isGF = data.isGF; - target.scale.set(data.scale, data.scale); - target.holdTime = data.holdTime; - target.fixChar(true); - target.updateHitbox(); - } - - function loadChar(isOpponent:Bool) - { - var i:Int = charLayer.members.length - 1; - while (i >= 0) - { - var memb:Character = charLayer.members[i]; - if (memb != null) - { - memb.kill(); - charLayer.remove(memb); - memb.destroy(); - } - --i; - } - charLayer.clear(); - ghostChar = new Character(0, 0, curChar, !isOpponent); - ghostChar.debugMode = true; - ghostChar.alpha = 0.6; - - char = new Character(0, 0, curChar, !isOpponent); - char.debugMode = true; - char.setPosition(char.characterData.offsetX + OFFSET_X + 100, char.characterData.offsetY); - - charLayer.add(ghostChar); - charLayer.add(char); - - var charData = char.characterData; - animAtlas = (char.animateAtlas != null ? char.animateAtlas : null); - updateCharPos(charData.offsetX, charData.offsetY); - resortAnims(char.characterData.animations); - curAnim = getAnimIndex("idle"); - char.playAnim(charData.animations[getAnimIndex("idle")].name, true); - ghostChar.characterData.animations = char.characterData.animations; - - char.updateHitbox(); - ghostChar.updateHitbox(); - updatePointerPos(); - } - - function loadUI() - { topMenu = [ { label: "File", childs: [ { label: "New", - onSelect: new_character + onSelect: _file_new, }, null, { @@ -328,7 +74,6 @@ class CharacterEditor extends UIState null, { label: "Exit", - keybind: [ESCAPE], onSelect: _file_exit } ] @@ -337,438 +82,533 @@ class CharacterEditor extends UIState label: "Edit", childs: [ { - label: "Camera Position", - onSelect: moveCameraPos + label: "Undo", + keybind: [CONTROL, Z], + onSelect: _edit_undo }, { - label: "Properties", - onSelect: edit_properties_window, + label: "Redo", + keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]], + onSelect: _edit_redo } ] }, { - label: "Animation", + label: "Character", childs: [ { label: "New Animation", keybind: [CONTROL, N], - onSelect: add_anim_window, + onSelect: _char_add_anim, + }, + { + label: "Edit Animation", + onSelect: _char_edit_anim, + }, + { + label: "Delete Animation", + keybind: [DELETE], + onSelect: _char_remove_anim, }, null, { - label: "Update Animation", - keybind: [CONTROL, U], - onSelect: update_anim_window, - }, - null, - { - label: "Remove Animation", - keybind: [CONTROL, D], - onSelect: remove_anim_window, - }, + label: "Edit Info", + onSelect: _char_edit_info, + } ] }, { - label: "Help", + label: "Playback", childs: [ { - label: "Controls", - onSelect: help_controls_window + label: "Play Animation", + keybind: [SPACE], + onSelect: _playback_play_anim, + }, + { + label: "Stop Animation", + onSelect: _playback_stop_anim } ] - } + }, + { + label: "Offsets", + childs: [ + { + label: "Move Left", + keybind: [LEFT], + onSelect: _offsets_left, + }, + { + label: "Move Up", + keybind: [UP], + onSelect: _offsets_up, + }, + { + label: "Move Down", + keybind: [DOWN], + onSelect: _offsets_down, + }, + { + label: "Move Right", + keybind: [RIGHT], + onSelect: _offsets_right, + }, + null, + { + label: "Move Extra Left", + keybind: [SHIFT, LEFT], + onSelect: _offsets_extra_left, + }, + { + label: "Move Extra Up", + keybind: [SHIFT, UP], + onSelect: _offsets_extra_up, + }, + { + label: "Move Extra Down", + keybind: [SHIFT, DOWN], + onSelect: _offsets_extra_down, + }, + { + label: "Move Extra Right", + keybind: [SHIFT, RIGHT], + onSelect: _offsets_extra_right, + }, + null, + { + label: "Clear Offsets", + keybind: [CONTROL, R], + onSelect: _offsets_clear, + } + ] + }, + { + label: "View", + childs: [ + { + label: "Zoom in", + keybind: [CONTROL, NUMPADPLUS], + onSelect: _view_zoomin + }, + { + label: "Zoom out", + keybind: [CONTROL, NUMPADMINUS], + onSelect: _view_zoomout + }, + { + label: "Reset zoom", + keybind: [CONTROL, NUMPADZERO], + onSelect: _view_zoomreset + }, + + ] + }, ]; + charCamera = FlxG.camera; + + uiCamera = new FlxCamera(); + uiCamera.bgColor = 0; + animsCamera = new FlxCamera(800-23,140+23+30+46,450+23,511-24-30); + animsCamera.bgColor = 0; + + characterBG = new FunkinSprite(0, 0, Paths.image('editors/character/WOAH')); + characterBG.cameras = [charCamera]; + characterBG.screenCenter(); + characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height); + characterBG.scrollFactor.set(); + add(characterBG); + + FlxG.cameras.add(uiCamera); + FlxG.cameras.add(animsCamera); + + character = new Character(0,0,__character); + character.debugMode = true; + character.cameras = [charCamera]; + + ghosts = new CharacterGhostsHandler(character); + ghosts.cameras = [charCamera]; + + add(ghosts); + add(character); + topMenuSpr = new UITopMenu(topMenu); topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; - Framerate.fpsCounter.alpha = 0.4; - Framerate.memoryCounter.alpha = 0.4; - Framerate.codenameBuildField.alpha = 0.4; + characterPropertiresWindow = new CharacterPropertiesWindow(); + characterAnimsWindow = new CharacterAnimsWindow(character); + uiGroup.add(characterPropertiresWindow); + uiGroup.add(characterAnimsWindow); + + playAnimation(character.getNameList()[0]); + + add(topMenuSpr); + add(uiGroup); } - var ofs_cross:FlxSpriteGroup = new FlxSpriteGroup(0, 0); + private var movingCam:Bool = false; + private var camDrag:FlxPoint = FlxPoint.get(0,0); + private var camDragSpeed:Float = 1.2; - public function loadCross() - { - var i:Int = ofs_cross.members.length - 1; - while (i >= 0) - { - var memb:FlxSprite = ofs_cross.members[i]; - if (memb != null) - { - memb.kill(); - ofs_cross.remove(memb); - memb.destroy(); + private var nextScroll:FlxPoint = FlxPoint.get(0,0); + + public override function update(elapsed:Float) { + super.update(elapsed); + + if (true) { + if(FlxG.keys.justPressed.ANY) + UIUtil.processShortcuts(topMenu); + } + + if (character != null) + characterPropertiresWindow.characterInfo.text = '(${character.getNameList().length}) Animations\nFlipped: ${character.flipX}\nSprite: ${character.sprite}\nAnim: ${character.getAnimName()}\nOffset: (${character.frameOffset.x}, ${character.frameOffset.y})'; + + if (!(characterPropertiresWindow.hovered || characterAnimsWindow.hovered)) { + if (FlxG.mouse.wheel != 0) { + zoom += 0.25 * FlxG.mouse.wheel; + __camZoom = Math.pow(2, zoom); } - --i; - } - ofs_cross.clear(); - - var x = -800; - - var horizontal_cross = new FlxSprite(x, -70).makeSolid(2000, 5); - var vertical_cross = new FlxSprite(x, 0).makeSolid(5, 2000); - horizontal_cross.y += (vertical_cross.height / 2); - vertical_cross.x += (horizontal_cross.width / 2); - ofs_cross.add(horizontal_cross); - ofs_cross.add(vertical_cross); - horizontal_cross.scrollFactor.set(1, 1); - vertical_cross.scrollFactor.set(1, 1); - // ofs_cross.screenCenter(); - ofs_cross.y = -200; - ofs_cross.scrollFactor.set(1, 1); - ofs_cross.alpha = 0.6; - } - - var OFFSET_X:Float = 150; - - public function reloadStage() - { - var i:Int = stage.members.length - 1; - while (i >= 0) - { - var memb:FunkinSprite = stage.members[i]; - if (memb != null) - { - memb.kill(); - stage.remove(memb); - memb.destroy(); - } - --i; - } - stage.clear(); - var playerXDifference = 0; - if (char.isPlayer) - playerXDifference = 670; - - var bg = new FunkinSprite(-600 + OFFSET_X - playerXDifference, -200, Paths.image("stages/default/stageback")); - stage.add(bg); - bg.scrollFactor.set(0.9, 0.9); - - var stageFront = new FunkinSprite(-600 + OFFSET_X - playerXDifference, 600, Paths.image("stages/default/stagefront")); - stageFront.setGraphicSize(Std.int(stageFront.width * 1.1)); - stageFront.updateHitbox(); - stage.add(stageFront); - stageFront.scrollFactor.set(0.9, 0.9); - - stage.setPosition(0, 0); - } - - function hasAtlas():Bool - return (animAtlas != null); - - var movingCam:Bool = false; - var movingCamDefaultPos:FlxPoint = null; - var movingCamDefaultPosCam:FlxPoint = null; - - public function moveCameraPos(_) - { - movingCam = true; - movingCamText.visible = true; - updatePointerPos(); - } - - public function detectCamClick() - { - var charData = char.characterData; - - if (movingCam) - { - if (FlxG.mouse.justPressed) - { - var midx = char.getMidpoint().x; - var midy = char.getMidpoint().y; - - var pos = FlxG.mouse.getWorldPosition(editorCamera); - var placedPos:FlxPoint = new FlxPoint((!char.isPlayer ? pos.x + -(midx) : pos.x + -(midx)), -(pos.y - midy)); - - movingCam = false; - movingCamText.visible = false; - charData.camOffsetX = placedPos.x; - charData.camOffsetY = placedPos.y; - updatePointerPos(); - } - } - } - - function updatePointerPos() - { - var x:Float = char.getMidpoint().x; - var y:Float = char.getMidpoint().y; - /*if (!char.isPlayer) - { */ - x += char.characterData.camOffsetX; - /*} - else - { - x -= char.characterData.camOffsetX; - }*/ - y -= char.characterData.camOffsetY; - - x -= cameraPointer.width / 2; - y -= cameraPointer.height / 2; - cameraPointer.setPosition(x, y); - } - - var movingOffset:Bool = false; - var dragX:Float = -1; - var dragY:Float = -1; - - public function moveCharPos() - { - var charSprite = (hasAtlas() ? char.animateAtlas : char); - var charData = char.characterData; - if (FlxG.mouse.justPressed && FlxG.mouse.overlaps(charSprite)) - { - var pos = FlxG.mouse.getWorldPosition(FlxG.camera); - dragX = charData.offsetX - pos.x; - dragY = charData.offsetY - pos.y; - movingOffset = true; - } - else if (!FlxG.mouse.overlaps(charSprite)) - movingOffset = false; - - if (movingOffset && FlxG.mouse.pressed) - { - var pos = FlxG.mouse.getWorldPosition(editorCamera); - updateCharPos((pos.x + dragX), (pos.y + dragY)); - } - } - - public function updateCharPos(x:Float, y:Float) - { - char.characterData.offsetX = x; - char.characterData.offsetY = y; - char.x = x; - char.y = y; - updatePointerPos(); - } - - public function reloadGhost() - { - ghostChar.loadSprite(char.characterData.sprite); - for (i in 0...char.characterData.animations.length - 1) - { - XMLUtil.addAnimToSprite(ghostChar, char.characterData.animations[i]); - } - - char.alpha = 0.85; - ghostChar.visible = true; - /*if (ghostDropDown.selectedLabel == '') - { - ghostChar.visible = false; - char.alpha = 1; - }*/ - ghostChar.color = 0xFF666688; - ghostChar.antialiasing = char.antialiasing; - ghostChar.playAnim(char.characterData.animations[curAnim].name, true); - } - - public function mouseControl() - { if (FlxG.mouse.justReleasedRight) { closeCurrentContextMenu(); - openContextMenu(topMenu[1].childs); - } - - if (FlxG.mouse.wheel != 0) { - camZoom += (FlxG.mouse.wheel / 10); + openContextMenu(topMenu[2].childs); } } - function controlCharacter() - { - var controlArray:Array = [ - FlxG.keys.justPressed.LEFT, - FlxG.keys.justPressed.RIGHT, - FlxG.keys.justPressed.UP, - FlxG.keys.justPressed.DOWN - ]; - var holdShift = FlxG.keys.pressed.SHIFT; - var holdAlt = FlxG.keys.pressed.ALT; - for (i in 0...controlArray.length) - { - if (controlArray[i]) - { - var multiplier:Float = 1; - if (holdShift) - multiplier = 10; - if (holdAlt) - multiplier = 0.1; - - var negaMult:Int = 1; - if (i % 2 == 1) - negaMult = -1; - - // For Player Offsets - if (i == 1 && char.playerOffsets) - negaMult = -1; - if (i == 0 && char.playerOffsets) - negaMult = 1; - - if (i > 1) - { - char.characterData.animations[curAnim].y += negaMult * multiplier; - ghostChar.characterData.animations[curAnim].y += negaMult * multiplier; - } - else - { - char.characterData.animations[curAnim].x += negaMult * multiplier; - ghostChar.characterData.animations[curAnim].x += negaMult * multiplier; - } - - char.animOffsets.set(char.characterData.animations[curAnim].name, - new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y)); - ghostChar.animOffsets.set(char.characterData.animations[curAnim].name, - new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y)); - - char.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x, - char.characterData.animations[curAnim].y); - ghostChar.addOffset(char.characterData.animations[curAnim].anim, char.characterData.animations[curAnim].x, - char.characterData.animations[curAnim].y); - /*ghostChar.animOffsets.set(char.animation.curAnim.name, - new FlxPoint(char.characterData.animations[curAnim].x, char.characterData.animations[curAnim].y)); */ - - char.playAnim(char.characterData.animations[curAnim].name, false); - if (char.getAnimName() != null && char.getAnimName() == ghostChar.getAnimName()) - { - ghostChar.playAnim(char.getAnimName(), false); - } - } + if (FlxG.mouse.justPressedMiddle) { + FlxG.mouse.getScreenPosition(charCamera, camDrag); + camDrag *= camDragSpeed; + camDrag += charCamera.scroll; } + + if (FlxG.mouse.pressedMiddle) { + var pos = FlxG.mouse.getScreenPosition(charCamera); + pos *= camDragSpeed; + nextScroll.set((camDrag.x - pos.x), (camDrag.y - pos.y)); + + currentCursor = HAND; + } else + currentCursor = ARROW; + + charCamera.scroll.set( + lerp(charCamera.scroll.x, nextScroll.x, 0.35), + lerp(charCamera.scroll.y, nextScroll.y, 0.35) + ); + + charCamera.zoom = lerp(charCamera.zoom, __camZoom, 0.125); + + characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height); + characterBG.scale.set(characterBG.scale.x / charCamera.zoom, characterBG.scale.y / charCamera.zoom); } - function controlCamera(elapsed:Float) - { - var ofs:Float = 500 * elapsed; - var shift:Bool = (FlxG.keys.pressed.SHIFT); - var cam_up:Bool = (FlxG.keys.pressed.W); - var cam_down:Bool = (FlxG.keys.pressed.S); - var cam_left:Bool = (FlxG.keys.pressed.A); - var cam_right:Bool = (FlxG.keys.pressed.D); - var cam_zoomers:Array = [FlxG.keys.pressed.I, FlxG.keys.pressed.O]; - - if (shift) - ofs *= 4; - - if (cam_up) - camFollow.y -= ofs; - if (cam_down) - camFollow.y += ofs; - if (cam_left) - camFollow.x -= ofs; - if (cam_right) - camFollow.x += ofs; - - var zoom_ofs = FlxG.camera.zoom * elapsed; - if (cam_zoomers[0] && camZoom < 3) - { - camZoom += zoom_ofs; - if (camZoom > 3) - camZoom = 3; - } - if (cam_zoomers[1] && camZoom > 0.1) - { - camZoom -= zoom_ofs; - if (camZoom < 0.1) - camZoom = 0.1; - } - - - - + // UNDO/REDO LOGIC + #if REGION + public inline function addToUndo(c:CharacterChange) { + redoList = []; + undoList.insert(0, c); + while(undoList.length > Options.maxUndos) + undoList.pop(); } + #end - function changeAnim() - { - var space = (FlxG.keys.justPressed.SPACE); - var forward = (FlxG.keys.justPressed.L); - var backwards = (FlxG.keys.justPressed.K); - - if (forward || backwards) - { - var changeLimit = char.characterData.animations.length; - curAnim = FlxMath.wrap(curAnim + (forward ? 1 : -1), 0, changeLimit-1); - - trace(curAnim); - trace(char.characterData.animations[curAnim]); - - char.playAnim(char.characterData.animations[curAnim].name, false); - playingAnim.text = 'Animation: ${char.characterData.animations[curAnim].name}'; - } - - if (space) - char.playAnim(char.characterData.animations[curAnim].name, true); - - char.updateHitbox(); - ghostChar.updateHitbox(); - } - - // Animation Windows - function add_anim_window(_) - openSubState(new AddAnimScreen(char, ghostChar, this)); - - function update_anim_window(_) - openSubState(new SelectAnimUpdate(char, ghostChar, this)); - - function remove_anim_window(_) - openSubState(new DeleteAnimScreen(char, ghostChar, this)); - - function help_controls_window(_) - openSubState(new HelpControls()); - - function _file_exit(_) - { + // TOP MENU OPTIONS + #if REGION + function _file_exit(_) { FlxG.switchState(new CharacterSelection()); } - override function destroy() { - Framerate.fpsCounter.alpha = 1; - Framerate.memoryCounter.alpha = 1; - Framerate.codenameBuildField.alpha = 1; - super.destroy(); + function _file_new(_) { } - function new_character(_) - FlxG.state.openSubState(new NewCharacter(char, ghostChar, this)); - - - function edit_properties_window(_) - FlxG.state.openSubState(new CharacterProperties(char, ghostChar, this)); - - function _file_save(_) - { + function _file_save(_) { #if sys - var assetPath = Paths.character(char.curCharacter); - var path = Assets.getPath(assetPath); - saveTo(path); + sys.io.File.saveContent( + Assets.getPath(Paths.xml('characters/${character.curCharacter}')), + buildCharacter() + ); return; #end _file_saveas(_); } - function _file_saveas(_) - { - var xml = CharacterConfig.dataToXml(char.characterData); - openSubState(new SaveSubstate(CharacterConfig.generateCharXML(xml.x), { - defaultSaveFile: '${char.curCharacter}.xml' + function _file_saveas(_) { + openSubState(new SaveSubstate(buildCharacter(), { + defaultSaveFile: '${character.curCharacter}.xml' })); } - #if sys - function saveTo(path:String) - { - var xml = CharacterConfig.dataToXml(char.characterData); - CharacterConfig.save(path, CharacterConfig.generateCharXML(xml.x)); + function buildCharacter():String { + return "\n" + Printer.print(character.buildXML(), true); + } + + function _edit_undo(_) { + var v = undoList.shift(); + switch (v) { + case null: + // do nothing + case CEditInfo(oldInfo, newInfo): + editInfo(oldInfo, false); + case CCreateAnim(animID, animData): + deleteAnim(animData.name, false); + case CEditAnim(name, oldData, animData): + editAnim(name, oldData, false); + case CDeleteAnim(animID, animData): + createAnim(animData, animID, false); + case CChangeOffset(name, change): + changeOffset(name, change * -1, false); + case CResetOffsets(oldOffsets): + for (anim => offsets in oldOffsets) { + character.animOffsets.set(anim, offsets.clone()); + ghosts.setOffsets(anim, offsets.clone()); + } + + for (anim in character.getNameList()) + characterAnimsWindow.animButtons[anim].updateInfo(anim, character.getAnimOffset(anim), ghosts.animGhosts[anim].visible); + + changeOffset(character.getAnimName(), FlxPoint.get(0, 0), false); // apply da new offsets + } + if (v != null) + redoList.insert(0, v); + } + + function _edit_redo(_) { + var v = redoList.shift(); + switch (v) { + case null: + // do nothing + case CEditInfo(oldInfo, newInfo): + editInfo(newInfo, false); + case CCreateAnim(animID, animData): + createAnim(animData, animID, false); + case CEditAnim(name, oldData, animData): + editAnim(oldData.name, animData, false); + case CDeleteAnim(animID, animData): + deleteAnim(animData.name, false); + case CChangeOffset(name, change): + changeOffset(name, change, false); + case CResetOffsets(oldOffsets): + clearOffsets(false); + } + if (v != null) + undoList.insert(0, v); + } + + function _char_add_anim(_) { + createAnimWithUI(); + } + + function _char_edit_anim(_) { + editAnimWithUI(character.getAnimName()); + } + + function _char_remove_anim(_) { + deleteAnim(character.getAnimName()); + } + + function _char_edit_info(_) { + editInfoWithUI(); + } + + public function createAnimWithUI() { + FlxG.state.openSubState(new CharacterAnimScreen(null, (_) -> { + if (_ != null) createAnim(_); + })); + } + + public function editAnimWithUI(name:String) { + FlxG.state.openSubState(new CharacterAnimScreen(character.animDatas.get(name), (_) -> { + if (_ != null) editAnim(name, _); + })); + } + + public function createAnim(animData:AnimData, animID:Int = -1, addtoUndo:Bool = true) { + XMLUtil.addAnimToSprite(character, animData); + ghosts.createGhost(animData.name); + characterAnimsWindow.createNewButton(animData.name, FlxPoint.get(animData.x,animData.y), false, animID); + + playAnimation(animData.name); + + if (addtoUndo) + addToUndo(CCreateAnim(character.getNameList().length, animData)); + } + + public function editAnim(name:String, animData:AnimData, addtoUndo:Bool = true) { + var oldAnimData:AnimData = character.animDatas.get(name); + var button:CharacterAnimButtons = characterAnimsWindow.animButtons[name]; + + ghosts.removeGhost(name); + XMLUtil.addAnimToSprite(character, animData); + ghosts.createGhost(animData.name); + button.updateInfo(animData.name, character.getAnimOffset(animData.name), ghosts.animGhosts[animData.name].visible); + + if (character.getAnimName() == animData.name) // update anim ifs its currently selected + playAnimation(animData.name); + + if (addtoUndo) + addToUndo(CEditAnim(animData.name, oldAnimData, animData)); + } + + public function deleteAnim(name:String, addtoUndo:Bool = true) { + if (character.getNameList().length-1 == 0) + @:privateAccess character.animation._curAnim = null; + else + playAnimation(character.getNameList()[Std.int(Math.abs(character.getNameList().indexOf(name)-1))]); + + // undo shit blah blah + var oldID:Int = character.getNameList().indexOf(name); + var oldAnimData:AnimData = character.animDatas.get(name); + + ghosts.removeGhost(name); + character.removeAnimation(name); + if (character.animOffsets.exists(name)) character.animOffsets.remove(name); + if (character.animDatas.exists(name)) character.animDatas.remove(name); + characterAnimsWindow.removeButton(name); + + if (addtoUndo) + addToUndo(CDeleteAnim(oldID, oldAnimData)); + } + + public function editInfoWithUI() { + FlxG.state.openSubState(new CharacterInfoScreen(character, (_) -> { + if (_ != null) editInfo(_); + })); + } + + public function editInfo(newInfo:Xml, addtoUndo:Bool = true) { + var lastAnim:String = character.getAnimName(); + + var oldInfo = character.buildXML(); + character.applyXML(new Access(newInfo)); + ghosts.updateInfos(newInfo); + + playAnimation(lastAnim); + + if (addtoUndo) + addToUndo(CEditInfo(oldInfo, newInfo)); + } + + public function ghostAnim(anim:String) { + var ghost:Character = ghosts.animGhosts.get(anim); + ghost.visible = !ghost.visible; + + var button:CharacterAnimButtons = characterAnimsWindow.animButtons[anim]; + button.updateInfo(anim, character.getAnimOffset(anim), ghost.visible); + } + + function _playback_play_anim(_) { + if (character.getNameList().length != 0) + playAnimation(character.getAnimName()); + } + + function _playback_stop_anim(_) { + if (character.getNameList().length != 0) + character.stopAnimation(); + } + + public function playAnimation(anim:String) { + character.playAnim(anim, true); + characterAnimsWindow.changeCurAnim(anim); + } + + function _offsets_left(_) { + changeOffset(character.getAnimName(), FlxPoint.get(1, 0)); + } + + function _offsets_up(_) { + changeOffset(character.getAnimName(), FlxPoint.get(0, 1)); + } + + function _offsets_down(_) { + changeOffset(character.getAnimName(), FlxPoint.get(0, -1)); + } + + function _offsets_right(_) { + changeOffset(character.getAnimName(), FlxPoint.get(-1, 0)); + } + + function _offsets_extra_left(_) { + changeOffset(character.getAnimName(), FlxPoint.get(5, 0)); + } + + function _offsets_extra_up(_) { + changeOffset(character.getAnimName(), FlxPoint.get(0, 5)); + } + + function _offsets_extra_down(_) { + changeOffset(character.getAnimName(), FlxPoint.get(0, -5)); + } + + function _offsets_extra_right(_) { + changeOffset(character.getAnimName(), FlxPoint.get(-5, 0)); + } + + function _offsets_clear(_) { + clearOffsets(); + } + + function changeOffset(anim:String, change:FlxPoint, addtoUndo:Bool = true) { + if (character.getNameList().length == 0) return; + + character.animOffsets.set(anim, character.getAnimOffset(anim) + change); + characterAnimsWindow.animButtons[anim].updateInfo(anim, character.getAnimOffset(anim), ghosts.animGhosts[anim].visible); + character.frameOffset.set(character.getAnimOffset(anim).x, character.getAnimOffset(anim).y); + + ghosts.updateOffsets(anim, change); + + if (addtoUndo) + addToUndo(CChangeOffset(anim, change)); + } + + function clearOffsets(addtoUndo:Bool = true) { + if (character.getNameList().length == 0) return; + + var oldOffsets:Map = [ + for (anim => offsets in character.animOffsets) + anim => offsets.clone() + ]; + + for (anim in character.getNameList()) { + character.animOffsets[anim].zero(); + characterAnimsWindow.animButtons[anim].updateInfo(anim, character.getAnimOffset(anim), ghosts.animGhosts[anim].visible); + } + + ghosts.clearOffsets(); + + if (addtoUndo) + addToUndo(CResetOffsets(oldOffsets)); + } + + var zoom(default, set):Float = 0; + var __camZoom(default, set):Float = 1; + function set_zoom(val:Float) { + return zoom = FlxMath.bound(val, -3.5, 1.75); // makes zooming not lag behind when continuing scrolling + } + function set___camZoom(val:Float) { + return __camZoom = FlxMath.bound(val, 0.1, 3); + } + + function _view_zoomin(_) { + zoom += 0.25; + __camZoom = Math.pow(2, zoom); + } + function _view_zoomout(_) { + zoom -= 0.25; + __camZoom = Math.pow(2, zoom); + } + function _view_zoomreset(_) { + zoom = 0; + __camZoom = Math.pow(2, zoom); } #end - - function set_camZoom(value:Float):Float - { - editorCamera.zoom = value; - return camZoom = value; - } } + +enum CharacterChange { + CEditInfo(oldInfo:Xml, newInfo:Xml); + CCreateAnim(animID:Int, animData:AnimData); + CEditAnim(name:String, oldData:AnimData, animData:AnimData); + CDeleteAnim(animID:Int, animData:AnimData); + CChangeOffset(name:String, change:FlxPoint); + CResetOffsets(oldOffsets:Map); +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterGhostsHandler.hx b/source/funkin/editors/character/CharacterGhostsHandler.hx new file mode 100644 index 00000000..8d72adfe --- /dev/null +++ b/source/funkin/editors/character/CharacterGhostsHandler.hx @@ -0,0 +1,73 @@ +package funkin.editors.character; + +import haxe.xml.Access; +import flixel.math.FlxPoint; +import funkin.game.Character; + +class CharacterGhostsHandler extends FlxTypedGroup { + public var character:Character; + public var animGhosts:Map = []; + + public function new(character:Character) { + this.character = character; + super(); + + for (anim in character.getNameList()) + createGhost(anim); + } + + public function createGhost(anim:String) { + var ghost:Character = new Character(0,0, character.curCharacter); + ghost.applyXML(character.xml); // apply cur character stuff + ghost.playAnim(anim); + ghost.stopAnimation(); + ghost.alpha = 0.5; + ghost.visible = false; + add(ghost); + + animGhosts.set(anim, ghost); + } + + public function removeGhost(anim:String) { + var ghost:Character = animGhosts.get(anim); + @:privateAccess ghost.animation._curAnim = null; + animGhosts.remove(anim); + ghost.destroy(); + } + + public function updateInfos(xml:Xml) { + for (anim => ghost in animGhosts) { + ghost.applyXML(new Access(xml)); + ghost.playAnim(anim); + ghost.stopAnimation(); + } + + rePositionGhosts(); + } + + public function updateOffsets(anim:String, change:FlxPoint) { + for (anim => ghost in animGhosts) + ghost.animOffsets.set(anim, ghost.getAnimOffset(anim) + change); + + rePositionGhosts(); + } + + public function setOffsets(anim:String, offset:FlxPoint) { + for (anim => ghost in animGhosts) + ghost.animOffsets.set(anim, offset); + + rePositionGhosts(); + } + + public function clearOffsets() { + for (anim => ghost in animGhosts) + ghost.animOffsets[anim].zero(); + + rePositionGhosts(); + } + + inline function rePositionGhosts() { + for (ghost in animGhosts) + ghost.frameOffset.set(ghost.getAnimOffset(ghost.getAnimName()).x, ghost.getAnimOffset(ghost.getAnimName()).y); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx new file mode 100644 index 00000000..ed3db829 --- /dev/null +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -0,0 +1,182 @@ +package funkin.editors.character; + +import flixel.math.FlxPoint; +import haxe.xml.Access; +import funkin.game.Character; + +class CharacterInfoScreen extends UISubstateWindow { + public var character:Character; + + public var spriteTextBox:UITextBox; + public var iconTextBox:UITextBox; + public var iconSprite:FlxSprite; + public var gameOverCharTextBox:UITextBox; + public var antialiasingCheckbox:UICheckbox; + public var flipXCheckbox:UICheckbox; + public var positionXStepper:UINumericStepper; + public var positionYStepper:UINumericStepper; + public var cameraXStepper:UINumericStepper; + public var cameraYStepper:UINumericStepper; + public var scaleStepper:UINumericStepper; + public var singTimeStepper:UINumericStepper; + public var isPlayerCheckbox:UICheckbox; + public var isGFCheckbox:UICheckbox; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public var onSave:(xml:Xml) -> Void = null; + + public function new(character:Character, ?onSave:(xml:Xml) -> Void) { + this.character = character; + this.onSave = onSave; + super(); + } + + public override function create() { + winTitle = "Editing Character"; + winWidth = 714; + winHeight = 500; + + super.create(); + + function addLabelOn(ui:UISprite, text:String) + add(new UIText(ui.x, ui.y - 24, 0, text)); + + var title:UIText; + add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Sprite Data", 28)); + + spriteTextBox = new UITextBox(title.x, title.y + title.height + 38, character.sprite, 200); + add(spriteTextBox); + addLabelOn(spriteTextBox, "Sprite"); + + iconTextBox = new UITextBox(spriteTextBox.x + 200 + 26, spriteTextBox.y, character.getIcon(), 150); + iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);} + add(iconTextBox); + addLabelOn(iconTextBox, "Icon"); + + updateIcon(character.getIcon()); + + gameOverCharTextBox = new UITextBox(iconTextBox.x + 150 + (75 + 12), iconTextBox.y, character.gameOverCharacter, 200); + add(gameOverCharTextBox); + addLabelOn(gameOverCharTextBox, "Game Over Character"); + + antialiasingCheckbox = new UICheckbox(spriteTextBox.x, spriteTextBox.y + 10 + 32 + 28, "Antialiasing", character.antialiasing); + add(antialiasingCheckbox); + addLabelOn(antialiasingCheckbox, "Antialiased"); + + flipXCheckbox = new UICheckbox(antialiasingCheckbox.x + 172, spriteTextBox.y + 10 + 32 + 28, "FlipX", character.flipX); + add(flipXCheckbox); + addLabelOn(flipXCheckbox, "Flipped"); + + add(title = new UIText(spriteTextBox.x, spriteTextBox.y + 10 + 46 + 64, 0, "Character Data", 28)); + + positionXStepper = new UINumericStepper(title.x, title.y + title.height + 36, character.globalOffset.x, 0.001, 2, null, null, 84); + add(positionXStepper); + addLabelOn(positionXStepper, "Position (X,Y)"); + + add(new UIText(positionXStepper.x + 84 - 32 + 0, positionXStepper.y + 9, 0, ",", 22)); + + positionYStepper = new UINumericStepper(positionXStepper.x + 84 - 32 + 26, positionXStepper.y, character.globalOffset.y, 0.001, 2, null, null, 84); + add(positionYStepper); + + cameraXStepper = new UINumericStepper(positionYStepper.x + 36 + 84 - 32, positionYStepper.y, character.cameraOffset.x, 0.001, 2, null, null, 84); + add(cameraXStepper); + addLabelOn(cameraXStepper, "Camera Position (X,Y)"); + + add(new UIText(cameraXStepper.x + 84 - 32 + 0, cameraXStepper.y + 9, 0, ",", 22)); + + cameraYStepper = new UINumericStepper(cameraXStepper.x + 84 - 32 + 26, cameraXStepper.y, character.cameraOffset.y, 0.001, 2, null, null, 84); + add(cameraYStepper); + + scaleStepper = new UINumericStepper(cameraYStepper.x + 84 - 32 + 90, cameraYStepper.y, character.scale.x, 0.001, 2, null, null, 74); + add(scaleStepper); + addLabelOn(scaleStepper, "Scale"); + + singTimeStepper = new UINumericStepper(scaleStepper.x + 74 - 32 + 36, scaleStepper.y, character.holdTime, 0.001, 2, null, null, 74); + add(singTimeStepper); + addLabelOn(singTimeStepper, "Sing Duration (Steps)"); + + isPlayerCheckbox = new UICheckbox(positionXStepper.x, positionXStepper.y + 10 + 32 + 28, "isPlayer", character.playerOffsets); + add(isPlayerCheckbox); + addLabelOn(isPlayerCheckbox, "Is Player"); + + isGFCheckbox = new UICheckbox(isPlayerCheckbox.x + 128, positionXStepper.y + 10 + 32 + 28, "isGF", character.isGF); + add(isGFCheckbox); + addLabelOn(isGFCheckbox, "Is GF"); + + for (checkbox in [isPlayerCheckbox, isGFCheckbox, antialiasingCheckbox, flipXCheckbox]) + {checkbox.y += 4; checkbox.x += 6;} + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight- 20, "Save & Close", function() { + saveCharacterInfo(); + close(); + }, 125); + saveButton.x -= saveButton.bWidth; + saveButton.y -= saveButton.bHeight; + + closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() { + if (onSave != null) onSave(null); + close(); + }, 125); + closeButton.x -= closeButton.bWidth; + 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 saveCharacterInfo() { + for (stepper in [positionXStepper, positionYStepper, cameraXStepper, cameraYStepper, singTimeStepper, scaleStepper]) + @:privateAccess stepper.__onChange(stepper.label.text); + + var xml = Xml.createElement("character"); + xml.set("isPlayer", isPlayerCheckbox.checked ? "true" : "false"); + xml.set("isGF", isGFCheckbox.checked ? "true" : "false"); + xml.set("x", Std.string(positionXStepper.value)); + xml.set("y", Std.string(positionYStepper.value)); + xml.set("gameOverChar", gameOverCharTextBox.label.text); + xml.set("camx", Std.string(cameraXStepper.value)); + xml.set("camy", Std.string(cameraYStepper.value)); + xml.set("holdTime", Std.string(singTimeStepper.value)); + xml.set("flipX", Std.string(flipXCheckbox.checked)); + xml.set("icon", iconTextBox.label.text); + xml.set("scale", Std.string(scaleStepper.value)); + xml.set("antialiasing", antialiasingCheckbox.checked ? "true" : "false"); + xml.set("sprite", spriteTextBox.label.text); + + for (anim in character.animDatas) + { + var animXml:Xml = Xml.createElement('anim'); + animXml.set("name", anim.name); + animXml.set("anim", anim.anim); + animXml.set("loop", Std.string(anim.loop)); + animXml.set("fps", Std.string(anim.fps)); + var offset:FlxPoint = character.getAnimOffset(anim.name); + animXml.set("x", Std.string(offset.x)); + animXml.set("y", Std.string(offset.y)); + if (anim.indices.length > 0) + animXml.set("indices", anim.indices.join(",")); + xml.addChild(animXml); + } + + if (onSave != null) onSave(xml); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterProperties.hx b/source/funkin/editors/character/CharacterProperties.hx deleted file mode 100644 index 2c167c9c..00000000 --- a/source/funkin/editors/character/CharacterProperties.hx +++ /dev/null @@ -1,123 +0,0 @@ -package funkin.editors.character; - -import flixel.util.FlxColor; -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; - -class CharacterProperties extends UISubstateWindow -{ - public var char:Character; - public var ghostChar:Character; - public var charData:CharacterData; - - public var applyButton:UIButton; - public var cancelButton:UIButton; - public var editor:CharacterEditor; - - public var curAnimData:AnimData; - - public var spriteFile:UITextBox; - public var gameOverChar:UITextBox; - public var healthIcon:UITextBox; - public var iconColor:UITextBox; - public var flipX:UICheckbox; - public var isPlayer:UICheckbox; - public var isGF:UICheckbox; - public var antialiasing:UICheckbox; - public var scale:UITextBox; - public var holdTime:UITextBox; - - public function new(char:Character, ghostChar, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - this.charData = char.characterData; - this.editor = editor; - } - - public override function create() - { - winTitle = 'Character Properties'; - winWidth = 960; - - super.create(); - - // Offsets - var y_ofs = 60; - var x_ofs = 60; - var y_text_ofs = 20; - - // Character Properties - spriteFile = new UITextBox(windowSpr.x + 50, windowSpr.y + 10 + y_ofs, charData.sprite); - add(spriteFile); - add(new UIText(spriteFile.x, spriteFile.y - y_text_ofs, -1, "Image/Atlas file: ")); - - gameOverChar = new UITextBox(spriteFile.x + spriteFile.bWidth + x_ofs, spriteFile.y, charData.gameOverChar); - add(gameOverChar); - add(new UIText(gameOverChar.x, gameOverChar.y - y_text_ofs, -1, "Game Over character: ")); - - healthIcon = new UITextBox(spriteFile.x, spriteFile.y + y_ofs, charData.icon); - add(healthIcon); - add(new UIText(healthIcon.x, healthIcon.y - y_text_ofs, -1, "Health Icon: ")); - - iconColor = new UITextBox(healthIcon.x + healthIcon.bWidth + x_ofs, healthIcon.y, CharacterConfig.rgbToString(charData.iconColor)); - add(iconColor); - add(new UIText(iconColor.x, iconColor.y - y_text_ofs, -1, "Icon color (RGB): ")); - - flipX = new UICheckbox(healthIcon.x, healthIcon.y + y_ofs, "Flip X", charData.flipX); - add(flipX); - - antialiasing = new UICheckbox(flipX.x + flipX.field.width + x_ofs, flipX.y, "Antialiasing", charData.antialiasing); - add(antialiasing); - - isPlayer = new UICheckbox(flipX.x, flipX.y + y_ofs, "Playable Character", charData.isPlayer); - add(isPlayer); - - isGF = new UICheckbox(isPlayer.x + isPlayer.field.width + x_ofs, isPlayer.y, "GF Character", charData.isGF); - add(isGF); - - scale = new UITextBox(isPlayer.x, isPlayer.y + y_ofs, Std.string(charData.scale)); - add(scale); - add(new UIText(scale.x, scale.y - y_text_ofs, -1, "Scale: ")); - - holdTime = new UITextBox(scale.x + scale.bWidth + y_ofs, scale.y, Std.string(charData.holdTime)); - add(holdTime); - add(new UIText(holdTime.x, holdTime.y - y_text_ofs, -1, "Hold time: ")); - - // Apply / Cancel buttons - applyButton = new UIButton(windowSpr.x - x_ofs - 70, windowSpr.y, "Apply", function() - { - updateProperties(spriteFile.label.text, healthIcon.label.text, CharacterConfig.stringToRGB(healthIcon.label.text), flipX.checked, - antialiasing.checked, isPlayer.checked, isGF.checked, Std.parseFloat(scale.label.text), Std.parseFloat(holdTime.label.text), gameOverChar.label.text); - }); - add(applyButton); - - cancelButton = new UIButton(applyButton.x, applyButton.y + y_ofs, "Cancel", function() - { - close(); - }); - add(cancelButton); - } - - public function updateProperties(sprite:String, healthIcon:String, iconColor:FlxColor, flipX:Bool, antialiasing:Bool, isPlayer:Bool, isGF:Bool, - scale:Float, holdTime:Float, gameOverChar:String) - { - // Character Data - charData.sprite = sprite; - charData.icon = healthIcon; - charData.iconColor = iconColor; - charData.flipX = flipX; - charData.antialiasing = antialiasing; - charData.isPlayer = isPlayer; - charData.isGF = isGF; - charData.scale = scale; - charData.holdTime = holdTime; - charData.gameOverChar = gameOverChar; - - editor.reloadChar(!char.isPlayer, charData); - editor.reloadStage(); - editor.loadCross(); - } -} diff --git a/source/funkin/editors/character/CharacterPropertiesWindow.hx b/source/funkin/editors/character/CharacterPropertiesWindow.hx new file mode 100644 index 00000000..06795eb1 --- /dev/null +++ b/source/funkin/editors/character/CharacterPropertiesWindow.hx @@ -0,0 +1,35 @@ +package funkin.editors.character; + +import flixel.util.FlxColor; + +class CharacterPropertiesWindow extends UIWindow { + public var newButton:UIButton; + public var editButton:UIButton; + + public var characterInfo:UIText; + + public function new() { + super(800-23,23 + 23, 450 + 23, 140, "Character Properties"); + + newButton = new UIButton( x +(450 + 23)- 25 - 3 - 200, y + 12 + 31, "New Character", null, 200); + members.push(newButton); + newButton.color = FlxColor.GREEN; + + editButton = new UIButton(newButton.x, newButton.y + 12 + 30 + 4, "Edit Character Info", function () { + CharacterEditor.instance.editInfoWithUI(); + }, 200); + members.push(editButton); + + characterInfo = new UIText(x + 450 + 20 - 42 - 400, y + 36 + 10, 400, "() Animations:\nFlipped:\nSprite:\nAnim:\nOffset: (,)"); + characterInfo.alignment = LEFT; + members.push(characterInfo); + } + + public override function update(elapsed:Float) { + super.update(elapsed); + + __rect.x = x; __rect.y = y+23; + __rect.width = bWidth; __rect.height = bHeight-23; + hovered = UIState.state.isOverlapping(this, __rect); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index 1d26f6a6..066e52e6 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -3,7 +3,6 @@ package funkin.editors.character; import flixel.util.FlxColor; import funkin.game.Character; import funkin.backend.chart.Chart; -import funkin.game.Character.CharacterData; import funkin.options.type.TextOption; import funkin.options.type.IconOption; import funkin.options.OptionsScreen; @@ -11,77 +10,18 @@ import sys.FileSystem; class CharacterSelection extends EditorTreeMenu { - public var characterList:CharactersList; - public override function create() { bgType = "charter"; super.create(); - characterList = CharactersList.get(); - main = new OptionsScreen("Character Editor", "Select a character to edit", [ - for (s in characterList.characters) - new IconOption(s.name, "Press ACCEPT to edit this character.", s.icon, function() - { - FlxG.switchState(new CharacterEditor(s.name)); + for (char in Character.getList(true)) + new IconOption(char, "Press ACCEPT to edit this character.", Character.getIconFromCharName(char), + function() { + FlxG.switchState(new CharacterEditor(char)); }) ]); } - - public override function update(elapsed:Float) - { - super.update(elapsed); - } - - public override function onMenuChange() { - super.onMenuChange(); - if (optionsTree.members.length > 1) { // selected a song - // small flashbang - var color:FlxColor = characterList.characters[main.curSelected].iconColor.getDefault(0xFFFFFFFF); - - bg.colorTransform.redOffset = 0.25 * color.red; - bg.colorTransform.greenOffset = 0.25 * color.green; - bg.colorTransform.blueOffset = 0.25 * color.blue; - bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25); - bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25); - bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25); - } - } -} - -class CharactersList -{ - public var characters:Array = []; - - public function new() - { - } - - public function getCharactersFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource) - { - var charactersFound:Array = []; - - charactersFound = Paths.getFolderContent('data/characters', false, source); - - if (charactersFound.length > 0) - { - for (s in charactersFound) - if (s.endsWith(".xml")) - characters.push(CharacterConfig.loadCharacterData(s.replace(".xml", ""))); - return true; - } - return false; - } - - public static function get() - { - var characterList = new CharactersList(); - - if (!characterList.getCharactersFromSource(MODS)) - characterList.getCharactersFromSource(SOURCE); - - return characterList; - } -} +} \ No newline at end of file diff --git a/source/funkin/editors/character/DeleteAnimScreen.hx b/source/funkin/editors/character/DeleteAnimScreen.hx deleted file mode 100644 index ec5d6299..00000000 --- a/source/funkin/editors/character/DeleteAnimScreen.hx +++ /dev/null @@ -1,73 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxRandom; -import cpp.Random; -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; - -class DeleteAnimScreen extends UISubstateWindow -{ - public var char:Character; - public var ghostChar:Character; - - public var editor:CharacterEditor; - - public var animButtons:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(365, -10); - - public var closeButton:UIButton; - - public function new(char:Character, ghostChar:Character, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - this.editor = editor; - } - - public override function create() - { - winTitle = "Delete Animation"; - winWidth = 480; - winHeight = 700; - - super.create(); - - add(animButtons); - - var y_adder = 60; - for (anim in char.characterData.animations) - { - var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_adder, anim.name, function() - { - removeAnimation(anim); - close(); - }); - animButtons.add(newbutton); - y_adder += 45; - } - - closeButton = new UIButton(windowSpr.x - 120, windowSpr.y + 20, "Close", function() { - close(); - }); - add(closeButton); - } - - public function removeAnimation(anim:AnimData) - { - var refreshAnim:Bool = false; - if (char.animation.curAnim != null && anim.name == char.animation.curAnim.name) - refreshAnim = true; - - if (char.animation.getByName(anim.name) != null) - char.animation.remove(anim.name); - if (char.animOffsets.exists(anim.name)) - char.animOffsets.remove(anim.name); - char.characterData.animations.remove(anim); - - if (refreshAnim && char.characterData.animations.length > 0) - char.playAnim(char.characterData.animations[0].name, true); - - editor.reloadGhost(); - } -} diff --git a/source/funkin/editors/character/HelpControls.hx b/source/funkin/editors/character/HelpControls.hx deleted file mode 100644 index 66fe3e78..00000000 --- a/source/funkin/editors/character/HelpControls.hx +++ /dev/null @@ -1,71 +0,0 @@ -package funkin.editors.character; - -class HelpControls extends UISubstateWindow -{ - - var helpTextTitles:Array = [ - "Camera:", - "Offset:", - "Animation:" - ]; - var helpTexts:Array = [ - "\n\n- WASD: Move camera\n\n- I & O: Zoom In & Out.", - "\n\n- Arrows keys: Move the offset of the current\nanimation.\n\n- Left Click: Move the offset of the character", - "\n\n- K & L: Switch to previous & next animation\n\n- SPACE: Play current animation.\n\n" - ]; - - var curPage:Int = 0; - public function new() - { - super(); - } - - public override function create() - { - winTitle = 'Help: Controls'; - winWidth = 960; - - super.create(); - - var helpTextTitle = new UIText(windowSpr.x + 25, windowSpr.y + 45, -1, helpTextTitles[0], 36); - add(helpTextTitle); - - var helpText = new UIText(windowSpr.x + 25, windowSpr.y + 50, -1, helpTexts[0], 24); - add(helpText); - - var pageText = new UIText(855, windowSpr.y + 460, -1, '${curPage+1}/${helpTexts.length}', 24); - add(pageText); - - var rightButton = new UIButton(885, windowSpr.y + 500, ">", function() - { - if(curPage == helpTexts.length-1) - curPage = 0; - else - curPage++; - - pageText.text = '${curPage+1}/${helpTexts.length}'; - helpText.text = helpTexts[curPage]; - helpTextTitle.text = helpTextTitles[curPage]; - }, 32); - - var leftButton = new UIButton(845, windowSpr.y + 500, "<", function() - { - if(curPage == 0) - curPage = helpTexts.length-1; - else - curPage--; - - pageText.text = '${curPage+1}/${helpTexts.length}'; - helpText.text = helpTexts[curPage]; - helpTextTitle.text = helpTextTitles[curPage]; - }, 32); - - var closeButton = new UIButton(helpText.x, windowSpr.y + 500, "Close", function() - { - close(); - }); - add(closeButton); - add(leftButton); - add(rightButton); - } -} diff --git a/source/funkin/editors/character/NewCharacter.hx b/source/funkin/editors/character/NewCharacter.hx deleted file mode 100644 index 1a729a12..00000000 --- a/source/funkin/editors/character/NewCharacter.hx +++ /dev/null @@ -1,172 +0,0 @@ -package funkin.editors.character; - -import flixel.util.FlxColor; -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; -import funkin.editors.character.DeleteAnimScreen; - -class NewCharacter extends UISubstateWindow -{ - public var char:Character; - public var ghostChar:Character; - public var charData:CharacterData; - - public var applyButton:UIButton; - public var nextButton:UIButton; - public var cancelButton:UIButton; - public var editor:CharacterEditor; - - public var curAnimData:AnimData; - - public var spriteFile:UITextBox; - public var gameOverChar:UITextBox; - public var healthIcon:UITextBox; - public var iconColor:UITextBox; - public var flipX:UICheckbox; - public var isPlayer:UICheckbox; - public var isGF:UICheckbox; - public var antialiasing:UICheckbox; - public var scale:UITextBox; - public var holdTime:UITextBox; - - public function new(char:Character, ghostChar, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - this.charData = char.characterData; - this.editor = editor; - - //TO DO: Generate and Save - //---- - - - //Delete old Animaitons - } - - public override function create() - { - winTitle = 'Create New Character'; - winWidth = 960; - - super.create(); - - // Offsets - var y_ofs = 60; - var x_ofs = 60; - var y_text_ofs = 20; - - // Character Properties - spriteFile = new UITextBox(windowSpr.x + 50, windowSpr.y + 10 + y_ofs, charData.sprite); - add(spriteFile); - add(new UIText(spriteFile.x, spriteFile.y - y_text_ofs, -1, "Image/Atlas file: ")); - - gameOverChar = new UITextBox(spriteFile.x + spriteFile.bWidth + x_ofs, spriteFile.y, charData.gameOverChar); - add(gameOverChar); - add(new UIText(gameOverChar.x, gameOverChar.y - y_text_ofs, -1, "Game Over character: ")); - - healthIcon = new UITextBox(spriteFile.x, spriteFile.y + y_ofs, charData.icon); - add(healthIcon); - add(new UIText(healthIcon.x, healthIcon.y - y_text_ofs, -1, "Health Icon: ")); - - iconColor = new UITextBox(healthIcon.x + healthIcon.bWidth + x_ofs, healthIcon.y, CharacterConfig.rgbToString(charData.iconColor)); - add(iconColor); - add(new UIText(iconColor.x, iconColor.y - y_text_ofs, -1, "Icon color (RGB): ")); - - flipX = new UICheckbox(healthIcon.x, healthIcon.y + y_ofs, "Flip X", charData.flipX); - add(flipX); - - antialiasing = new UICheckbox(flipX.x + flipX.field.width + x_ofs, flipX.y, "Antialiasing", charData.antialiasing); - add(antialiasing); - - isPlayer = new UICheckbox(flipX.x, flipX.y + y_ofs, "Playable Character", charData.isPlayer); - add(isPlayer); - - isGF = new UICheckbox(isPlayer.x + isPlayer.field.width + x_ofs, isPlayer.y, "GF Character", charData.isGF); - add(isGF); - - scale = new UITextBox(isPlayer.x, isPlayer.y + y_ofs, Std.string(charData.scale)); - add(scale); - add(new UIText(scale.x, scale.y - y_text_ofs, -1, "Scale: ")); - - holdTime = new UITextBox(scale.x + scale.bWidth + y_ofs, scale.y, Std.string(charData.holdTime)); - add(holdTime); - add(new UIText(holdTime.x, holdTime.y - y_text_ofs, -1, "Hold time: ")); - - // Apply / Cancel buttons - - - applyButton = new UIButton(windowSpr.x - x_ofs - 70, windowSpr.y, "Apply", function() - { - - updateProperties(spriteFile.label.text, healthIcon.label.text, CharacterConfig.stringToRGB(healthIcon.label.text), flipX.checked, - antialiasing.checked, isPlayer.checked, isGF.checked, Std.parseFloat(scale.label.text), Std.parseFloat(holdTime.label.text), gameOverChar.label.text); - - //TO DO: Add a Animation - }); - add(applyButton); - - cancelButton = new UIButton(applyButton.x, applyButton.y + y_ofs, "Cancel", function() - { - close(); - }); - add(cancelButton); - } - - public function updateProperties(sprite:String, healthIcon:String, iconColor:FlxColor, flipX:Bool, antialiasing:Bool, isPlayer:Bool, isGF:Bool, - scale:Float, holdTime:Float, gameOverChar:String) - { - // Character Data - charData.sprite = sprite; - charData.icon = healthIcon; - charData.iconColor = iconColor; - charData.flipX = flipX; - charData.antialiasing = antialiasing; - charData.isPlayer = isPlayer; - charData.isGF = isGF; - charData.scale = scale; - charData.holdTime = holdTime; - charData.gameOverChar = gameOverChar; - - //Bring Player Char to Default Position for User - if(isPlayer) - { - charData.offsetX = 770; - charData.offsetY = 410; - } - else if(isGF) - { - charData.offsetX = 400; - charData.offsetY = 330; - } - else - { - charData.offsetX = 100; - charData.offsetY = 410; - } - - - - - editor.reloadChar(!char.isPlayer, charData); - editor.reloadStage(); - editor.loadCross(); - - - - } - - public function removeAnimation(anim:AnimData) - { - var refreshAnim:Bool = false; - if (char.animation.curAnim != null && anim.name == char.animation.curAnim.name) - refreshAnim = true; - - if (char.animation.getByName(anim.name) != null) - char.animation.remove(anim.name); - if (char.animOffsets.exists(anim.name)) - char.animOffsets.remove(anim.name); - char.characterData.animations.remove(anim); - } -} diff --git a/source/funkin/editors/character/SelectAnimUpdate.hx b/source/funkin/editors/character/SelectAnimUpdate.hx deleted file mode 100644 index 576f0189..00000000 --- a/source/funkin/editors/character/SelectAnimUpdate.hx +++ /dev/null @@ -1,62 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxRandom; -import cpp.Random; -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; - -class SelectAnimUpdate extends UISubstateWindow -{ - public var cancelButton:UIButton; - - public var char:Character; - public var ghostChar:Character; - - public var editor:CharacterEditor; - - public var animButtons:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(365, -10); - - public function new(char:Character, ghostChar:Character, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - this.editor = editor; - } - - public override function create() - { - winTitle = "Select an animation to update:"; - winWidth = 480; - winHeight = 700; - - super.create(); - - add(animButtons); - - var y_ofs = 60; - - for (anim in char.characterData.animations) - { - var newbutton = new UIButton(animButtons.x - 550, animButtons.y + y_ofs, anim.name, function() - { - openUpdateWindow(anim); - }); - animButtons.add(newbutton); - y_ofs += 45; - } - - cancelButton = new UIButton(windowSpr.x - 10, windowSpr.y + 20, "Cancel", function() - { - close(); - }); - cancelButton.x -= cancelButton.bWidth; - add(cancelButton); - } - - public function openUpdateWindow(anim:AnimData) - { - openSubState(new UpdateAnimScreen(this.char, this.ghostChar, anim, this.editor)); - } -} diff --git a/source/funkin/editors/character/UpdateAnimScreen.hx b/source/funkin/editors/character/UpdateAnimScreen.hx deleted file mode 100644 index e04ccd87..00000000 --- a/source/funkin/editors/character/UpdateAnimScreen.hx +++ /dev/null @@ -1,125 +0,0 @@ -package funkin.editors.character; - -import flixel.math.FlxPoint; -import funkin.backend.utils.XMLUtil.AnimData; -import funkin.game.Character; - -class UpdateAnimScreen extends UISubstateWindow -{ - public var char:Character; - public var ghostChar:Character; - - public var updateButton:UIButton; - public var cancelButton:UIButton; - public var editor:CharacterEditor; - - public var curAnimData:AnimData; - - public var animName:UITextBox; - public var animPrefix:UITextBox; - public var fps:UITextBox; - public var loop:UICheckbox; - public var indices:UITextBox; - - public function new(char:Character, ghostChar, animData:AnimData, editor:CharacterEditor) - { - super(); - this.char = char; - this.ghostChar = ghostChar; - this.curAnimData = animData; - this.editor = editor; - } - - public override function create() - { - winTitle = 'Update Animation: ${curAnimData.name}'; - winWidth = 960; - - super.create(); - - // Animation Properties - var y_ofs = 60; - var y_text_ofs = 20; - - animName = new UITextBox(windowSpr.x + 50, windowSpr.y + y_ofs, curAnimData.name); - add(animName); - add(new UIText(animName.x, animName.y - y_text_ofs, -1, "Animation Name: ")); - - animPrefix = new UITextBox(animName.x, animName.y + y_ofs, curAnimData.anim); - add(animPrefix); - add(new UIText(animPrefix.x, animPrefix.y - y_text_ofs, -1, "Animation on XML/Atlas file: ")); - - fps = new UITextBox(animPrefix.x, animPrefix.y + y_ofs, Std.string(curAnimData.fps)); - add(fps); - add(new UIText(fps.x, fps.y - y_text_ofs, -1, "Framerate: ")); - - loop = new UICheckbox(fps.x, fps.y + y_ofs, "Looped", curAnimData.loop); - add(loop); - - indices = new UITextBox(loop.x, loop.y + y_ofs, Std.string(curAnimData.indices).replace("[", "").replace("]", "")); - add(indices); - add(new UIText(indices.x, indices.y - y_text_ofs, -1, "Indices (0, 1, 2, 3): ")); - - // Add / Cancel Buttons - updateButton = new UIButton(windowSpr.x - 10, windowSpr.y + 31, "Update", function() - { - updateAnimation(animName.label.text, animPrefix.label.text, Std.parseInt(fps.label.text), loop.checked, indices.label.text); - close(); - }); - updateButton.x -= updateButton.bWidth; - add(updateButton); - - cancelButton = new UIButton(updateButton.x, updateButton.y + y_ofs, "Cancel", function() - { - close(); - }); - add(cancelButton); - } - - public function updateAnimation(name:String, ?anim:String, ?fps:Int = 24, ?looped:Bool = false, ?indices:String) - { - var animType:XMLAnimType = NONE; - - var selectedAnim:AnimData = null; - var selectedAnimIndex = 0; - - for (i in 0...char.characterData.animations.length) - { - if (char.characterData.animations[i].name == name) - { - selectedAnim = char.characterData.animations[i]; - selectedAnimIndex = i; - } - } - - if (selectedAnim == null) - return; - - var animData:AnimData = { - name: name, - anim: anim, - fps: fps, - loop: looped, - animType: animType, - indices: [], - x: selectedAnim.x, - y: selectedAnim.y - }; - - if (indices != "" && indices != null) - { - var indicesSplit = indices.split(","); - - for (indice in indicesSplit) - { - var i = Std.parseInt(indice.trim()); - if (i != null) - animData.indices.push(i); - } - } - - XMLUtil.addAnimToSprite(char, animData); - char.characterData.animations[selectedAnimIndex] = animData; - char.playAnim(name, true); - } -} diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 43d2cb9b..5abdfa08 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -317,7 +317,6 @@ class Charter extends UIState { } ]; - hitsound = FlxG.sound.load(Paths.sound('editors/charter/hitsound')); metronome = FlxG.sound.load(Paths.sound('editors/charter/metronome')); @@ -357,7 +356,6 @@ class Charter extends UIState { sep.cameras = [charterCamera]; } - selectionBox = new UISliceSprite(0, 0, 2, 2, 'editors/ui/selection'); selectionBox.visible = false; selectionBox.scrollFactor.set(1, 1); diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index 91167d5c..c3c978f1 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -135,7 +135,7 @@ class CharterStrumlineScreen extends UISubstateWindow { add(saveButton); closeButton = new UIButton(saveButton.x - 20, saveButton.y, creatingStrumLine ? "Cancel" : "Close", function() { - if (creatingStrumLine) onSave(null); + if (creatingStrumLine && onSave != null) onSave(null); close(); }, 125); add(closeButton); diff --git a/source/funkin/editors/ui/UIButton.hx b/source/funkin/editors/ui/UIButton.hx index 5b53f194..ad722970 100644 --- a/source/funkin/editors/ui/UIButton.hx +++ b/source/funkin/editors/ui/UIButton.hx @@ -4,7 +4,6 @@ import flixel.graphics.frames.FlxFrame; class UIButton extends UISliceSprite { public var callback:Void->Void = null; - public var field:UIText; public override function new(x:Float, y:Float, text:String, callback:Void->Void, w:Int = 120, h:Int = 32) { diff --git a/source/funkin/editors/ui/UISprite.hx b/source/funkin/editors/ui/UISprite.hx index af05c73d..9db06b6f 100644 --- a/source/funkin/editors/ui/UISprite.hx +++ b/source/funkin/editors/ui/UISprite.hx @@ -24,6 +24,7 @@ class UISprite extends FlxSprite { public var cursor:MouseCursor = ARROW; public var focused(get, set):Bool; + public var selectable:Bool = true; private inline function get_focused():Bool return UIState.state.currentFocus == cast this; @@ -45,7 +46,8 @@ class UISprite extends FlxSprite { hoveredByChild = false; super.update(elapsed); - updateButton(); + if (selectable) + updateButton(); @:privateAccess { __oldDefCams = FlxCamera._defaultCameras; diff --git a/source/funkin/editors/ui/UIState.hx b/source/funkin/editors/ui/UIState.hx index d287e630..5283447f 100644 --- a/source/funkin/editors/ui/UIState.hx +++ b/source/funkin/editors/ui/UIState.hx @@ -8,6 +8,7 @@ import funkin.editors.ui.UIContextMenu.UIContextMenuCallback; import openfl.ui.Mouse; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import flixel.math.FlxPoint; +import openfl.ui.MouseCursor; import flixel.math.FlxRect; class UIState extends MusicBeatState { @@ -20,6 +21,8 @@ class UIState extends MusicBeatState { public var hoveredSprite:UISprite = null; public var currentFocus:IUIFocusable = null; + public var currentCursor:MouseCursor = ARROW; + private var __rect:FlxRect; private var __mousePos:FlxPoint; @@ -105,7 +108,7 @@ class UIState extends MusicBeatState { Mouse.cursor = hoveredSprite.cursor; hoveredSprite = null; } else { - Mouse.cursor = ARROW; + Mouse.cursor = currentCursor; } } diff --git a/source/funkin/editors/ui/UIWindow.hx b/source/funkin/editors/ui/UIWindow.hx new file mode 100644 index 00000000..c1320599 --- /dev/null +++ b/source/funkin/editors/ui/UIWindow.hx @@ -0,0 +1,12 @@ +package funkin.editors.ui; + +class UIWindow extends UISliceSprite { + public var titleSpr:UIText; + + public override function new(x:Float, y:Float, w:Int, h:Int, title:String) { + super(x, y, w, h, "editors/ui/warning-popup"); + + members.push(titleSpr = new UIText(x + 25, y, bWidth - 50, title, 15, -1)); + titleSpr.y = y + ((30 - titleSpr.height) / 2); + } +} \ No newline at end of file diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index fb5c939a..a06a42c3 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -1,6 +1,6 @@ package funkin.game; -import funkin.editors.character.CharacterConfig; +import haxe.xml.Printer; import flixel.util.FlxColor; import funkin.backend.FunkinSprite; import flixel.graphics.frames.FlxFrame; @@ -23,27 +23,6 @@ import funkin.backend.scripting.events.PlayAnimEvent.PlayAnimContext; using StringTools; -typedef CharacterData = -{ - var offsetX:Float; - var offsetY:Float; - var camOffsetX:Float; - var camOffsetY:Float; - var quickDancer:Bool; - var holdTime:Float; - var isPlayer:Bool; - var flipX:Bool; - var antialiasing:Bool; - var sprite:String; - var name:String; - var animations:Array; - @:optional var isGF:Bool; - @:optional var icon:String; - @:optional var iconColor:FlxColor; - @:optional var scale:Float; - @:optional var gameOverChar:String; -} - @:allow(funkin.desktop.editors.CharacterEditor) @:allow(funkin.game.StrumLine) @:allow(funkin.game.PlayState) @@ -62,11 +41,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset return stunned = b; } - public var debugMode:Bool = false; - public var isPlayer:Bool = false; public var isGF:Bool = false; public var curCharacter:String = 'bf'; + public var sprite:String = 'bf'; public var lastHit:Float = Math.NEGATIVE_INFINITY; public var holdTime:Float = 4; @@ -82,21 +60,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public var script:Script; public var xml:Access; - public var characterData:CharacterData; - public var shadowFrame:CharacterShadowFrame; public inline function getCameraPosition() { var midpoint = getMidpoint(); - var event = EventManager.get(PointEvent) - .recycle(midpoint.x - + (isPlayer ? -100 : 150) - + globalOffset.x - + cameraOffset.x, midpoint.y - - 100 - + globalOffset.y - + cameraOffset.y); + var event = EventManager.get(PointEvent).recycle( + midpoint.x + (isPlayer ? -100 : 150) + globalOffset.x + cameraOffset.x, + midpoint.y - 100 + globalOffset.y + cameraOffset.y); script.call("onGetCamPos", [event]); midpoint.put(); // this event cannot be cancelled @@ -141,62 +112,18 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset try { var charXML = Xml.parse(plainXML).firstElement(); - if (charXML == null) - throw new Exception("Missing \"character\" node in XML."); + if (charXML == null) throw new Exception("Missing \"character\" node in XML."); xml = new Access(charXML); - } - catch (e) - { + } catch (e) { Logs.trace('Error while loading character ${curCharacter}: ${e}', ERROR); curCharacter = "bf"; continue; } - var sprite:String = curCharacter; - - if (xml.has.isPlayer) - playerOffsets = (xml.att.isPlayer == "true"); - if (xml.has.isGF) - isGF = (xml.att.isGF == "true"); - if (xml.has.x) - globalOffset.x = Std.parseFloat(xml.att.x); - if (xml.has.y) - globalOffset.y = Std.parseFloat(xml.att.y); - if (xml.has.gameOverChar) - gameOverCharacter = xml.att.gameOverChar; - if (xml.has.camx) - cameraOffset.x = Std.parseFloat(xml.att.camx); - if (xml.has.camy) - cameraOffset.y = Std.parseFloat(xml.att.camy); - if (xml.has.holdTime) - holdTime = CoolUtil.getDefault(Std.parseFloat(xml.att.holdTime), 4); - if (xml.has.flipX) - flipX = (xml.att.flipX == "true"); - if (xml.has.icon) - icon = xml.att.icon; - if (xml.has.scale) - { - var scale = Std.parseFloat(xml.att.scale).getDefault(1); - this.scale.set(scale, scale); - updateHitbox(); - } - if (xml.has.antialiasing) - antialiasing = (xml.att.antialiasing == "true"); - if (xml.has.sprite) - sprite = xml.att.sprite; - - loadSprite(Paths.image('characters/$sprite')); - - for (anim in xml.nodes.anim) - { - XMLUtil.addXMLAnimation(this, anim); - } - - characterData = CharacterConfig.loadCharacterData(curCharacter); - - // Loads the script and calls it's "create" function + // Loads the script script = Script.create(Paths.script(Path.withoutExtension(xmlPath), null, true)); script.setParent(this); script.load(); + applyXML(xml); script.call("create"); } break; @@ -225,9 +152,6 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset // antialiasing = false; // flipX = true; - fixChar(true); - - dance(); script.call("postCreate"); } @@ -242,6 +166,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset // [ true ][ false ][ true ] // [ false ][ true ][ true ] // [ true ][ true ][ false ] + // bros provided evedince :skull: if (isPlayer != playerOffsets && switchAnims) { // character is flipped @@ -251,6 +176,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset switchOffset('singLEFT', 'singRIGHT'); switchOffset('singLEFTmiss', 'singRIGHTmiss'); } + frameOffset.set(getAnimOffset(getAnimName()).x, getAnimOffset(getAnimName()).y); if (isPlayer) flipX = !flipX; __baseFlipped = flipX; @@ -302,8 +228,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public function tryDance() { - switch (lastAnimContext) - { + switch (lastAnimContext) { case SING | MISS: if (lastHit + (Conductor.stepCrochet * holdTime) < Conductor.songPosition) dance(); @@ -328,8 +253,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset */ public var danceInterval:Int = 1; - public override function beatHit(curBeat:Int) - { + public override function beatHit(curBeat:Int) { script.call("beatHit", [curBeat]); if (danceInterval < 1) danceInterval = 1; @@ -340,16 +264,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset } } - public override function stepHit(curStep:Int) - { + public override function stepHit(curStep:Int) { script.call("stepHit", [curStep]); // nothing } var __reverseDrawProcedure:Bool = false; - public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect - { + public override function getScreenBounds(?newRect:FlxRect, ?camera:FlxCamera):FlxRect { if (__reverseDrawProcedure) { scale.x *= -1; @@ -363,10 +285,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset var __drawingShadowFrame = false; var __oldColorTransform = new ColorTransform(); - public override function draw() - { - if (!__drawingShadowFrame && shadowFrame != null) - { + public override function draw() { + if (!__drawingShadowFrame && shadowFrame != null) { __drawingShadowFrame = true; var oldFrame = _frame; @@ -436,17 +356,84 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset globalOffset.put(); } - public inline function getIcon() - { - return (icon != null) ? icon : curCharacter; + public function applyXML(xml:Access) { + this.xml = xml; // modders wassup + sprite = curCharacter; + + if (xml.x.exists("isPlayer")) playerOffsets = (xml.x.get("isPlayer") == "true"); + if (xml.x.exists("isGF")) isGF = (xml.x.get("isGF") == "true"); + if (xml.x.exists("x")) globalOffset.x = Std.parseFloat(xml.x.get("x")); + if (xml.x.exists("y")) globalOffset.y = Std.parseFloat(xml.x.get("y")); + if (xml.x.exists("gameOverChar")) gameOverCharacter = xml.x.get("gameOverChar"); + if (xml.x.exists("camx")) cameraOffset.x = Std.parseFloat(xml.x.get("camx")); + if (xml.x.exists("camy")) cameraOffset.y = Std.parseFloat(xml.x.get("camy")); + if (xml.x.exists("holdTime")) holdTime = CoolUtil.getDefault(Std.parseFloat(xml.x.get("holdTime")), 4); + if (xml.x.exists("flipX")) flipX = (xml.x.get("flipX") == "true"); + if (xml.x.exists("icon")) icon = xml.x.get("icon"); // idk why this is broke it just is + if (xml.x.exists("scale")) { + var scale = Std.parseFloat(xml.x.get("scale")).getDefault(1); + this.scale.set(scale, scale); + updateHitbox(); + } + if (xml.x.exists("antialiasing")) antialiasing = (xml.x.get("antialiasing") == "true"); + if (xml.x.exists("sprite")) sprite = xml.x.get("sprite"); + + loadSprite(Paths.image('characters/$sprite')); + + animation.destroyAnimations(); + animDatas.clear(); + for (anim in xml.nodes.anim) + { + XMLUtil.addXMLAnimation(this, anim); + } + + fixChar(true); + dance(); } + public function buildXML():Xml { + var xml = Xml.createElement("character"); + xml.set("isPlayer", playerOffsets == true ? "true" : "false"); + xml.set("isGF", isGF == true ? "true" : "false"); + xml.set("x", Std.string(globalOffset.x)); + xml.set("y", Std.string(globalOffset.y)); + xml.set("gameOverChar", gameOverCharacter); + xml.set("camx", Std.string(cameraOffset.x)); + xml.set("camy", Std.string(cameraOffset.y)); + xml.set("holdTime", Std.string(holdTime)); + xml.set("flipX", Std.string(flipX)); + xml.set("icon", icon); + xml.set("scale", Std.string(scale.x)); + xml.set("antialiasing", antialiasing == true ? "true" : "false"); + xml.set("sprite", sprite); + + for (anim in animDatas) + { + var animXml:Xml = Xml.createElement('anim'); + animXml.set("name", anim.name); + animXml.set("anim", anim.anim); + animXml.set("loop", Std.string(anim.loop)); + animXml.set("fps", Std.string(anim.fps)); + var offset:FlxPoint = getAnimOffset(anim.name); + animXml.set("x", Std.string(offset.x)); + animXml.set("y", Std.string(offset.y)); + if (anim.indices.length > 0) + animXml.set("indices", anim.indices.join(",")); + xml.addChild(animXml); + } + + this.xml = new Access(xml); + return xml; + } + + public inline function getIcon() + return (icon != null) ? icon : curCharacter; + public static function getIconFromCharName(?curCharacter:String) { if(curCharacter == null) return "face"; var icon = curCharacter; while(true) { - switch (curCharacter) - { + switch (curCharacter) { // case 'your-char': // To hardcode characters icons default: // load xml @@ -468,12 +455,19 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset continue; } - if (xml.has.icon) icon = xml.att.icon; + if (xml.x.exists("icon")) icon = xml.x.get("icon"); } break; } return icon; } + + public static function getList(?mods:Bool = false):Array { + return [ + for (path in Paths.getFolderContent('data/characters/', true, mods ? MODS : BOTH)) + if (Path.extension(path) == "xml") Path.withoutDirectory(Path.withoutExtension(path)) + ]; + } } typedef CharacterShadowFrame = diff --git a/source/funkin/game/GameOverSubstate.hx b/source/funkin/game/GameOverSubstate.hx index 35b1218c..603e2b69 100644 --- a/source/funkin/game/GameOverSubstate.hx +++ b/source/funkin/game/GameOverSubstate.hx @@ -79,7 +79,7 @@ class GameOverSubstate extends MusicBeatSubstate FlxG.switchState(new FreeplayState()); } - if (!isEnding && ((!lossSFX.playing) || (character.animation.curAnim != null && character.animation.curAnim.name == "firstDeath" && character.animation.curAnim.finished)) && (FlxG.sound.music == null || !FlxG.sound.music.playing)) { + if (!isEnding && ((!lossSFX.playing) || (character.getAnimName() == "firstDeath" && character.isAnimFinished())) && (FlxG.sound.music == null || !FlxG.sound.music.playing)) { CoolUtil.playMusic(Paths.music(gameOverSong), false, 1, true, 100); beatHit(0); }