Merge remote-tracking branch 'dev/main'
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 556 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 193 B |
Binary file not shown.
|
After Width: | Height: | Size: 246 B |
Binary file not shown.
|
After Width: | Height: | Size: 287 B |
@@ -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<String, AnimData> = [];
|
||||
|
||||
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<String> {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<String, CharacterAnimButtons> = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<FlxBasic>
|
||||
{
|
||||
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 '<!DOCTYPE codename-engine-character>\n${Printer.print(xml, true)}';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
import haxe.xml.Access;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.game.Character;
|
||||
|
||||
class CharacterGhostsHandler extends FlxTypedGroup<Character> {
|
||||
public var character:Character;
|
||||
public var animGhosts:Map<String, Character> = [];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<CharacterData> = [];
|
||||
|
||||
public function new()
|
||||
{
|
||||
}
|
||||
|
||||
public function getCharactersFromSource(source:funkin.backend.assets.AssetsLibraryList.AssetSource)
|
||||
{
|
||||
var charactersFound:Array<String> = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UIButton> = new FlxTypedSpriteGroup<UIButton>(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();
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package funkin.editors.character;
|
||||
|
||||
class HelpControls extends UISubstateWindow
|
||||
{
|
||||
|
||||
var helpTextTitles:Array<String> = [
|
||||
"Camera:",
|
||||
"Offset:",
|
||||
"Animation:"
|
||||
];
|
||||
var helpTexts:Array<String> = [
|
||||
"\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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<UIButton> = new FlxTypedSpriteGroup<UIButton>(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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+97
-103
@@ -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<AnimData>;
|
||||
@: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<String> {
|
||||
return [
|
||||
for (path in Paths.getFolderContent('data/characters/', true, mods ? MODS : BOTH))
|
||||
if (Path.extension(path) == "xml") Path.withoutDirectory(Path.withoutExtension(path))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
typedef CharacterShadowFrame =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user