package funkin.editors.character; import funkin.backend.system.framerate.Framerate; import funkin.backend.FlxAnimate; import flixel.group.FlxSpriteGroup; import flixel.graphics.FlxGraphic; import funkin.backend.utils.XMLUtil.AnimData; import flixel.math.FlxPoint; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; 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 = []; public var curChar:String; public var char:Character; public var ghostChar:Character; public var charLayer:FlxTypedSpriteGroup = new FlxTypedSpriteGroup(0, 0); public var animAtlas:FlxAnimate; public var cameraPointer:FlxSprite; public var camFollow:FlxObject; public var editorCamera:FlxCamera; public var uiCamera:FlxCamera; 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; public var playingAnim:UIText; public var movingCamText:UIText; public var camZoom(default, set):Float = 1; public function new(charName:String) { super(); this.curChar = charName; } 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((FlxG.width / 2), 30, -1, 'Animation:\n${char.characterData.animations[curAnim].name}', 24); playingAnim.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; // Top Menu add(playingAnim); add(movingCamText); add(topMenuSpr); add(uiGroup); } override function update(elapsed:Float) { // Control controlCamera(elapsed); controlCharacter(); changeAnim(); moveCharPos(); detectCamClick(); ghostChar.setPosition(char.x, char.y); super.update(elapsed); } 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); 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" }, null, { label: "Save", keybind: [CONTROL, S], onSelect: _file_save, }, { label: "Save As...", keybind: [CONTROL, SHIFT, S], onSelect: _file_saveas, }, null, { label: "Exit", keybind: [ESCAPE], onSelect: _file_exit } ] }, { label: "Edit", childs: [ { label: "Camera Position", onSelect: moveCameraPos }, { label: "Properties", onSelect: edit_properties_window, } ] }, { label: "Animation", childs: [ { label: "New Animation", keybind: [CONTROL, N], onSelect: add_anim_window, }, null, { label: "Update Animation", keybind: [CONTROL, U], onSelect: update_anim_window, }, null, { label: "Remove Animation", keybind: [CONTROL, D], onSelect: remove_anim_window, }, ] }, { label: "Help", childs: [ { label: "Controls", onSelect: help_controls_window } ] } ]; topMenuSpr = new UITopMenu(topMenu); topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; Framerate.fpsCounter.alpha = 0.4; Framerate.memoryCounter.alpha = 0.4; Framerate.codenameBuildField.alpha = 0.4; } var ofs_cross:FlxSpriteGroup = new FlxSpriteGroup(0, 0); 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(); } --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); } 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); } } } } 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; } } 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:\n${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(_) { FlxG.switchState(new CharacterSelection()); } override function destroy() { Framerate.fpsCounter.alpha = 1; Framerate.memoryCounter.alpha = 1; Framerate.codenameBuildField.alpha = 1; super.destroy(); } function edit_properties_window(_) FlxG.state.openSubState(new CharacterProperties(char, ghostChar, this)); function _file_save(_) { #if sys var assetPath = Paths.character(char.curCharacter); var path = Assets.getPath(assetPath); saveTo(path); return; #end _file_saveas(_); } function _file_saveas(_) { var xml = CharacterConfig.dataToXml(char.characterData); openSubState(new SaveSubstate(CharacterConfig.generateCharXML(xml.x), { defaultSaveFile: '${char.curCharacter}.xml' })); } #if sys function saveTo(path:String) { var xml = CharacterConfig.dataToXml(char.characterData); CharacterConfig.save(path, CharacterConfig.generateCharXML(xml.x)); } #end function set_camZoom(value:Float):Float { editorCamera.zoom = value; return camZoom = value; } }