Initial Stage Editor Commit
Co-Authored-By: ⍚~Nex <87421482+NexIsDumb@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package funkin.backend.system;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.graphics.tile.FlxDrawTrianglesItem;
|
||||
import flixel.math.FlxMatrix;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.geom.ColorTransform;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
class NothingCamera extends FlxCamera {
|
||||
public static final instance = new NothingCamera();
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
|
||||
visible = false;
|
||||
}
|
||||
|
||||
override function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader) { return null;}
|
||||
override function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader) { return null;}
|
||||
override function clearDrawStack() {}
|
||||
override function render() {}
|
||||
|
||||
public override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
|
||||
public override function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
|
||||
public override function drawTriangles(graphic:FlxGraphic, vertices:DrawData<Float>, indices:DrawData<Int>, uvtData:DrawData<Float>, ?colors:DrawData<Int>, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void {}
|
||||
|
||||
public override function update(elapsed:Float) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package funkin.backend.utils;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
|
||||
class MatrixUtil {
|
||||
public static function getMatrixPosition(sprite:FlxSprite, points:OneOfTwo<FlxPoint, Array<FlxPoint>>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array<FlxPoint>
|
||||
{
|
||||
//if(_width == -1) _width = sprite.width;
|
||||
//if(_height == -1) _height = sprite.height;
|
||||
if(camera == null) camera = sprite.camera;
|
||||
if(points is FlxBasePoint) points = [points];
|
||||
var nc = funkin.backend.system.NothingCamera.instance;
|
||||
nc.scroll.set(camera.scroll.x, camera.scroll.y);
|
||||
//nc.zoom = camera.zoom; // might not be needed
|
||||
nc.pixelPerfectRender = camera.pixelPerfectRender;
|
||||
@:privateAccess sprite.drawComplex(nc);
|
||||
|
||||
var points:Array<FlxPoint> = cast points;
|
||||
@:privateAccess for(point in points) {
|
||||
var x = sprite._matrix.__transformX(point.x * _width, point.y * _height);
|
||||
var y = sprite._matrix.__transformY(point.x * _width, point.y * _height);
|
||||
|
||||
// reset to ingame coords
|
||||
x += camera.scroll.x;
|
||||
y += camera.scroll.y;
|
||||
|
||||
point.set(x, y);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class EditorPicker extends MusicBeatSubstate {
|
||||
{
|
||||
name: "Stage Editor",
|
||||
iconID: 2,
|
||||
state: null
|
||||
state: funkin.editors.stager.StageSelection
|
||||
},
|
||||
{
|
||||
name: "UI Debug State",
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
package funkin.editors.stager;
|
||||
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.system.framerate.Framerate;
|
||||
import funkin.backend.utils.XMLUtil.AnimData;
|
||||
import funkin.editors.stager.elements.*;
|
||||
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
|
||||
import funkin.game.Character;
|
||||
import funkin.game.Stage;
|
||||
import haxe.xml.Access;
|
||||
import haxe.xml.Printer;
|
||||
|
||||
using funkin.backend.utils.MatrixUtil;
|
||||
|
||||
class StageEditor extends UIState {
|
||||
static var __stage:String;
|
||||
public var stage:Stage;
|
||||
|
||||
public static var instance(get, null):StageEditor;
|
||||
|
||||
private static inline function get_instance()
|
||||
return FlxG.state is StageEditor ? cast FlxG.state : null;
|
||||
|
||||
public var topMenu:Array<UIContextMenuOption>;
|
||||
public var topMenuSpr:UITopMenu;
|
||||
|
||||
public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
|
||||
public var stageSpritesWindow:UIButtonList<StageElementButton>;
|
||||
|
||||
public var xmlMap:Map<FlxBasic, Access> = new Map<FlxBasic, Access>();
|
||||
|
||||
public var chars:Array<Character> = [];
|
||||
|
||||
public var stageCamera:FlxCamera;
|
||||
public var guideCamera:FlxCamera;
|
||||
public var uiCamera:FlxCamera;
|
||||
|
||||
public var undos:UndoList<StageChange> = new UndoList<StageChange>();
|
||||
|
||||
public inline static function exID(id:String) {
|
||||
return "stageEditor." + id;
|
||||
}
|
||||
|
||||
public function new(stage:String) {
|
||||
super();
|
||||
if (stage != null) __stage = stage;
|
||||
}
|
||||
|
||||
public override function create() {
|
||||
super.create();
|
||||
|
||||
WindowUtils.suffix = " (Stage Editor)";
|
||||
SaveWarning.selectionClass = StageSelection;
|
||||
SaveWarning.saveFunc = () -> {_file_save(null);};
|
||||
|
||||
topMenu = [
|
||||
{
|
||||
label: "File",
|
||||
childs: [
|
||||
{
|
||||
label: "New",
|
||||
onSelect: _file_new,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Save",
|
||||
keybind: [CONTROL, S],
|
||||
onSelect: _file_save,
|
||||
},
|
||||
{
|
||||
label: "Save As...",
|
||||
keybind: [CONTROL, SHIFT, S],
|
||||
onSelect: _file_saveas,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Exit",
|
||||
onSelect: _file_exit
|
||||
}
|
||||
]
|
||||
},
|
||||
/*{
|
||||
label: "Edit",
|
||||
childs: [
|
||||
{
|
||||
label: "Undo",
|
||||
keybind: [CONTROL, Z],
|
||||
onSelect: _edit_undo
|
||||
},
|
||||
{
|
||||
label: "Redo",
|
||||
keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]],
|
||||
onSelect: _edit_redo
|
||||
}
|
||||
]
|
||||
},*/
|
||||
/*{
|
||||
label: "Character",
|
||||
childs: [
|
||||
{
|
||||
label: "New Animation",
|
||||
keybind: [CONTROL, N],
|
||||
onSelect: _char_add_anim,
|
||||
},
|
||||
{
|
||||
label: "Edit Animation",
|
||||
onSelect: _char_edit_anim,
|
||||
},
|
||||
{
|
||||
label: "Delete Animation",
|
||||
keybind: [DELETE],
|
||||
onSelect: _char_remove_anim,
|
||||
},
|
||||
null,
|
||||
{
|
||||
label: "Edit Info",
|
||||
onSelect: _char_edit_info,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Playback",
|
||||
childs: [
|
||||
{
|
||||
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
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
stageCamera = FlxG.camera;
|
||||
|
||||
guideCamera = new FlxCamera();
|
||||
guideCamera.bgColor = 0;
|
||||
|
||||
uiCamera = new FlxCamera();
|
||||
uiCamera.bgColor = 0;
|
||||
|
||||
FlxG.cameras.add(guideCamera, false);
|
||||
FlxG.cameras.add(uiCamera, false);
|
||||
|
||||
// Load from xml
|
||||
var order:Array<Dynamic> = [];
|
||||
stage = new Stage(__stage, this, false);
|
||||
stage.onXMLLoaded = function(xml:Access, elems:Array<Access>) {
|
||||
return elems;
|
||||
}
|
||||
stage.onNodeLoaded = function(node:Access, sprite:Dynamic):Dynamic {
|
||||
if(sprite is FunkinSprite) {
|
||||
sprite.forceIsOnScreen = true; // hack
|
||||
sprite.extra.set(exID("type"), node.name);
|
||||
//sprite.active = false;
|
||||
}
|
||||
if(sprite is StageCharPos) {
|
||||
var charPos:StageCharPos = cast sprite;
|
||||
var charName = switch(charPos.name) {
|
||||
case "boyfriend": "bf";
|
||||
case "girlfriend": "gf";
|
||||
default: charPos.name;
|
||||
}
|
||||
var char = new Character(0,0, charName, stage.isCharFlipped(charPos.name, charName == "bf"));
|
||||
char.debugMode = true;
|
||||
// Play first anim, and make it the last frame
|
||||
var animToPlay = char.getAnimOrder()[0];
|
||||
char.playAnim(animToPlay, true, NONE);
|
||||
var lastIndx = (char.animateAtlas != null) ?
|
||||
char.animateAtlas.anim.length - 1 :
|
||||
char.animation.curAnim.numFrames - 1;
|
||||
char.playAnim(animToPlay, true, NONE, false, lastIndx);
|
||||
char.stopAnimation();
|
||||
|
||||
// Add it to the stage
|
||||
char.visible = true;
|
||||
char.alpha = 0.5;
|
||||
char.extra.set(exID("node"), node);
|
||||
char.extra.set(exID("pos"), charPos);
|
||||
charPos.extra.set(exID("char"), char);
|
||||
chars.push(char);
|
||||
}
|
||||
order.push(sprite);
|
||||
xmlMap.set(sprite, node);
|
||||
return sprite;
|
||||
}
|
||||
stage.loadXml(stage.stageXML, true);
|
||||
add(stage);
|
||||
|
||||
for(char in chars) {
|
||||
var charPos = char.extra.get(exID("pos"));
|
||||
var node = char.extra.get(exID("node"));
|
||||
|
||||
stage.applyCharStuff(char, charPos.name, 0);
|
||||
}
|
||||
|
||||
zoom = calculateZoom(stage.defaultZoom);
|
||||
|
||||
topMenuSpr = new UITopMenu(topMenu);
|
||||
topMenuSpr.cameras = uiGroup.cameras = [uiCamera];
|
||||
|
||||
final margin = 22;
|
||||
final width = 470;
|
||||
final TOP_MENU_HEIGHT = 25;
|
||||
var buttonSize:FlxPoint = FlxPoint.get(width-margin*2, 32);
|
||||
stageSpritesWindow = new UIButtonList<StageElementButton>(Std.int(FlxG.width - width), TOP_MENU_HEIGHT, width, Std.int(FlxG.height - TOP_MENU_HEIGHT), "Stage Sprites", buttonSize);
|
||||
stageSpritesWindow.collapsable = true;
|
||||
stageSpritesWindow.addButton.callback = () -> {
|
||||
// TODO: implement this
|
||||
}
|
||||
for (i=>sprite in order) {
|
||||
trace(sprite, Type.getClassName(Type.getClass(sprite)));
|
||||
var xml = xmlMap.get(sprite);
|
||||
if(xml != null) {
|
||||
if(sprite is FunkinSprite) {
|
||||
var type = sprite.extra.get(exID("type"));
|
||||
stageSpritesWindow.add((type == "box" || type == "solid") ? new StageSolidButton(0,0, sprite, xml) : new StageSpriteButton(0,0, sprite, xml));
|
||||
}
|
||||
else if(sprite is StageCharPos) {
|
||||
stageSpritesWindow.add(new StageCharacterButton(0,0, sprite, xml));
|
||||
}
|
||||
}
|
||||
}
|
||||
uiGroup.add(stageSpritesWindow);
|
||||
|
||||
add(topMenuSpr);
|
||||
add(uiGroup);
|
||||
|
||||
if(Framerate.isLoaded) {
|
||||
Framerate.fpsCounter.alpha = 0.4;
|
||||
Framerate.memoryCounter.alpha = 0.4;
|
||||
Framerate.codenameBuildField.alpha = 0.4;
|
||||
}
|
||||
|
||||
DiscordUtil.call("onEditorLoaded", ["Stage Editor", __stage]);
|
||||
}
|
||||
|
||||
override function destroy() {
|
||||
super.destroy();
|
||||
nextScroll = FlxDestroyUtil.destroy(nextScroll);
|
||||
if(Framerate.isLoaded) {
|
||||
Framerate.fpsCounter.alpha = 1;
|
||||
Framerate.memoryCounter.alpha = 1;
|
||||
Framerate.codenameBuildField.alpha = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//private var movingCam:Bool = false;
|
||||
//private var camDragSpeed:Float = 1.2;
|
||||
|
||||
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) && !characterAnimsWindow.dragging) {
|
||||
if (FlxG.mouse.wheel != 0) {
|
||||
zoom += 0.25 * FlxG.mouse.wheel;
|
||||
__camZoom = Math.pow(2, zoom);
|
||||
}
|
||||
|
||||
if (FlxG.mouse.justReleasedRight) {
|
||||
closeCurrentContextMenu();
|
||||
openContextMenu(topMenu[2].childs);
|
||||
}
|
||||
if (FlxG.mouse.pressed) {
|
||||
nextScroll.set(nextScroll.x - FlxG.mouse.deltaScreenX, nextScroll.y - FlxG.mouse.deltaScreenY);
|
||||
currentCursor = HAND;
|
||||
} else
|
||||
currentCursor = ARROW;
|
||||
} else if (!FlxG.mouse.pressed)
|
||||
currentCursor = ARROW;
|
||||
|
||||
characterBG.scale.set(FlxG.width/characterBG.width, FlxG.height/characterBG.height);
|
||||
characterBG.scale.set(characterBG.scale.x / charCamera.zoom, characterBG.scale.y / charCamera.zoom);*/
|
||||
|
||||
if (true/*!(characterPropertiresWindow.hovered || characterAnimsWindow.hovered) && !characterAnimsWindow.dragging*/) {
|
||||
if (FlxG.mouse.wheel != 0) {
|
||||
zoom += 0.25 * FlxG.mouse.wheel;
|
||||
updateZoom();
|
||||
}
|
||||
|
||||
//if (FlxG.mouse.justReleasedRight) {
|
||||
// closeCurrentContextMenu();
|
||||
// openContextMenu(topMenu[2].childs);
|
||||
//}
|
||||
if (FlxG.mouse.pressed) {
|
||||
nextScroll.set(nextScroll.x - FlxG.mouse.deltaScreenX, nextScroll.y - FlxG.mouse.deltaScreenY);
|
||||
currentCursor = HAND;
|
||||
} else
|
||||
currentCursor = ARROW;
|
||||
} else if (!FlxG.mouse.pressed)
|
||||
currentCursor = ARROW;
|
||||
|
||||
stageCamera.scroll.set(
|
||||
lerp(stageCamera.scroll.x, nextScroll.x, 0.35),
|
||||
lerp(stageCamera.scroll.y, nextScroll.y, 0.35)
|
||||
);
|
||||
|
||||
stageCamera.zoom = lerp(stageCamera.zoom, __camZoom, 0.125);
|
||||
|
||||
WindowUtils.prefix = undos.unsaved ? "* " : "";
|
||||
SaveWarning.showWarning = undos.unsaved;
|
||||
}
|
||||
|
||||
// TOP MENU OPTIONS
|
||||
#if REGION
|
||||
function _file_exit(_) {
|
||||
if (undos.unsaved) SaveWarning.triggerWarning();
|
||||
else FlxG.switchState(new StageSelection());
|
||||
}
|
||||
|
||||
function _file_new(_) {
|
||||
}
|
||||
|
||||
function _file_save(_) {
|
||||
#if sys
|
||||
CoolUtil.safeSaveFile(
|
||||
'${Paths.getAssetsRoot()}/data/stages/${stage.stageName}.xml',
|
||||
buildStage()
|
||||
);
|
||||
undos.save();
|
||||
#else
|
||||
_file_saveas(_);
|
||||
#end
|
||||
}
|
||||
|
||||
function _file_saveas(_) {
|
||||
openSubState(new SaveSubstate(buildStage(), {
|
||||
defaultSaveFile: '${stage.stageName}.xml'
|
||||
}));
|
||||
undos.save();
|
||||
}
|
||||
|
||||
function buildStage():String {
|
||||
/*if (character.isPlayer != character.playerOffsets) {
|
||||
character.switchOffset('singLEFT', 'singRIGHT');
|
||||
character.switchOffset('singLEFTmiss', 'singRIGHTmiss');
|
||||
}
|
||||
var charXML:Xml = character.buildXML([
|
||||
for (button in characterAnimsWindow.buttons.members)
|
||||
button.anim
|
||||
]);
|
||||
|
||||
// clean
|
||||
if (charXML.exists("gameOverChar") && character.gameOverCharacter == "bf-dead") charXML.remove("gameOverChar");
|
||||
if (charXML.exists("camx") && character.cameraOffset.x == 0) charXML.remove("camx");
|
||||
if (charXML.exists("camy") && character.cameraOffset.y == 0) charXML.remove("camy");
|
||||
if (charXML.exists("holdTime") && character.holdTime == 4) charXML.remove("holdTime");
|
||||
if (charXML.exists("flipX") && !character.flipX) charXML.remove("flipX");
|
||||
if (charXML.exists("scale") && character.scale.x == 1) charXML.remove("scale");
|
||||
if (charXML.exists("antialiasing") && character.antialiasing) charXML.remove("antialiasing");*/
|
||||
|
||||
return "<!DOCTYPE codename-engine-character>\n";// + Printer.print(charXML, true);
|
||||
}
|
||||
|
||||
/*function _edit_undo(_) {
|
||||
var undo = undos.undo();
|
||||
switch(undo) {
|
||||
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 (charButton in characterAnimsWindow.buttons.members)
|
||||
charButton.updateInfo(charButton.anim, character.getAnimOffset(charButton.anim), ghosts.animGhosts[charButton.anim].visible);
|
||||
|
||||
changeOffset(character.getAnimName(), FlxPoint.get(0, 0), false); // apply da new offsets
|
||||
}
|
||||
}
|
||||
|
||||
function _edit_redo(_) {
|
||||
var redo = undos.redo();
|
||||
switch(redo) {
|
||||
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);
|
||||
}
|
||||
}*/
|
||||
|
||||
public function editInfoWithUI() {
|
||||
FlxG.state.openSubState(new StageInfoScreen(stage, (_) -> {
|
||||
if (_ != null) editInfo(_);
|
||||
}));
|
||||
}
|
||||
|
||||
public function editInfo(newInfo:Xml, addtoUndo:Bool = true) {
|
||||
/*var oldInfo = stage.buildXML();
|
||||
stage.applyXML(new Access(newInfo));
|
||||
|
||||
if (addtoUndo)
|
||||
undos.addToUndo(CEditInfo(oldInfo, newInfo));*/
|
||||
}
|
||||
|
||||
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;
|
||||
updateZoom();
|
||||
}
|
||||
function _view_zoomout(_) {
|
||||
zoom -= 0.25;
|
||||
updateZoom();
|
||||
}
|
||||
function _view_zoomreset(_) {
|
||||
zoom = 0;
|
||||
updateZoom();
|
||||
}
|
||||
|
||||
inline function updateZoom() {
|
||||
__camZoom = Math.pow(2, zoom);
|
||||
}
|
||||
|
||||
inline function calculateZoom(zoom:Float) {
|
||||
return Math.log(zoom) / Math.log(2);
|
||||
}
|
||||
#end
|
||||
|
||||
override function draw() {
|
||||
super.draw();
|
||||
|
||||
if(dot == null) {
|
||||
dot = new FlxSprite().makeGraphic(10, 10, FlxColor.RED);
|
||||
dot.camera = stageCamera;
|
||||
dot.forceIsOnScreen = true;
|
||||
}
|
||||
//guideCamera.zoom = stageCamera.zoom;
|
||||
//dot.scrollFactor.set(0, 0);
|
||||
|
||||
/*for(member in members) {
|
||||
if(member is FlxSprite) {
|
||||
if(camera == null || camera == stageCamera) {
|
||||
var sprite:FlxSprite = cast member;
|
||||
@:privateAccess if(sprite._frame == null) continue;
|
||||
if(sprite.visible) {
|
||||
//trace("Drawing Guides for " + sprite);
|
||||
drawGuides(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
for(sprite in stage.stageSprites) {
|
||||
if(sprite.visible) {
|
||||
@:privateAccess if(sprite._frame == null) continue;
|
||||
drawGuides(sprite);
|
||||
}
|
||||
}
|
||||
for(char in chars) {
|
||||
if(char.visible) {
|
||||
@:privateAccess if(char._frame == null) continue;
|
||||
drawGuides(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawGuides(sprite:FlxSprite) {
|
||||
var oldWidth = sprite.width;
|
||||
var oldHeight = sprite.height;
|
||||
var oldOffset = sprite.offset.clone(FlxPoint.weak());
|
||||
var oldOrigin = sprite.origin.clone(FlxPoint.weak());
|
||||
sprite.updateHitbox();
|
||||
sprite.offset.copyFrom(oldOffset);
|
||||
sprite.origin.copyFrom(oldOrigin);
|
||||
|
||||
var corners = sprite.getMatrixPosition([
|
||||
// corners
|
||||
FlxPoint.get(0, 0),
|
||||
FlxPoint.get(1, 0),
|
||||
FlxPoint.get(0, 1),
|
||||
FlxPoint.get(1, 1),
|
||||
// edges
|
||||
FlxPoint.get(0, 0.5),
|
||||
FlxPoint.get(1, 0.5),
|
||||
FlxPoint.get(0.5, 1),
|
||||
FlxPoint.get(0.5, 0),
|
||||
// center
|
||||
//FlxPoint.get(0.5, 0.5)
|
||||
], sprite.camera, sprite.width, sprite.height);
|
||||
|
||||
//Logs.trace("Guide at " + corners[0].x + ", " + corners[0].y + " sprite at " + sprite.x + ", " + sprite.y);
|
||||
|
||||
for(corner in corners) {
|
||||
drawDot(corner.x, corner.y);
|
||||
corner.put();
|
||||
}
|
||||
|
||||
drawLine(corners[0], corners[1]); // tl - tr
|
||||
drawLine(corners[0], corners[2]); // tl - bl
|
||||
drawLine(corners[1], corners[3]); // tr - br
|
||||
drawLine(corners[2], corners[3]); // bl - br
|
||||
|
||||
// reset hitbox to old values
|
||||
sprite.width = oldWidth;
|
||||
sprite.height = oldHeight;
|
||||
}
|
||||
|
||||
inline function drawDot(x:Float, y:Float) {
|
||||
dot.setPosition(x, y);
|
||||
dot.scale.set(0.7/stageCamera.zoom, 0.7/stageCamera.zoom);
|
||||
dot.x -= dot.width / 2;
|
||||
dot.y -= dot.height / 2;
|
||||
dot.draw();
|
||||
}
|
||||
|
||||
function drawLine(point1:FlxPoint, point2:FlxPoint) {
|
||||
var dx:Float = point2.x - point1.x;
|
||||
var dy:Float = point2.y - point1.y;
|
||||
|
||||
var angle:Float = Math.atan2(dy, dx);
|
||||
var distance:Float = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
dot.setPosition(point1.x, point1.y);
|
||||
dot.angle = angle * FlxAngle.TO_DEG;
|
||||
dot.origin.set(0, dot.frameHeight / 2);
|
||||
dot.scale.x = distance / dot.frameWidth;
|
||||
dot.scale.y = 0.20/stageCamera.zoom;
|
||||
//dot.x -= dot.width / 2;
|
||||
dot.y -= dot.height / 2;
|
||||
dot.draw();
|
||||
// Reset the angle and scale
|
||||
dot.angle = 0;
|
||||
dot.scale.x = dot.scale.y = 1;
|
||||
dot.updateHitbox();
|
||||
}
|
||||
|
||||
var dot:FlxSprite = null;
|
||||
}
|
||||
|
||||
enum StageChange {
|
||||
CEditInfo(oldInfo:Xml, newInfo:Xml);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package funkin.editors.stager;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.editors.extra.PropertyButton;
|
||||
import funkin.game.Stage;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class StageInfoScreen extends UISubstateWindow {
|
||||
public var stage:Stage;
|
||||
|
||||
public var saveButton:UIButton;
|
||||
public var closeButton:UIButton;
|
||||
|
||||
public var onSave:(xml:Xml) -> Void = null;
|
||||
|
||||
public function new(stage:Stage, ?onSave:(xml:Xml) -> Void) {
|
||||
this.stage = stage;
|
||||
this.onSave = onSave;
|
||||
super();
|
||||
}
|
||||
|
||||
public override function create() {
|
||||
winTitle = "Editing Stage";
|
||||
winWidth = 1014;
|
||||
winHeight = 600;
|
||||
|
||||
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");
|
||||
|
||||
iconColorWheel = new UIColorwheel(gameOverCharTextBox.x + 200 + 20, gameOverCharTextBox.y, character.iconColor);
|
||||
add(iconColorWheel);
|
||||
addLabelOn(iconColorWheel, "Icon Color");
|
||||
|
||||
add(title = new UIText(spriteTextBox.x, spriteTextBox.y + 10 + 46 + 84, 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)");
|
||||
|
||||
customPropertiesButtonList = new UIButtonList<PropertyButton>(singTimeStepper.x + singTimeStepper.width + 200, singTimeStepper.y, 290, 200, '', FlxPoint.get(280, 35), null, 5);
|
||||
customPropertiesButtonList.frames = Paths.getFrames('editors/ui/inputbox');
|
||||
customPropertiesButtonList.cameraSpacing = 0;
|
||||
customPropertiesButtonList.addButton.callback = function() {
|
||||
customPropertiesButtonList.add(new PropertyButton("newProperty", "valueHere", customPropertiesButtonList));
|
||||
}
|
||||
for (prop=>val in character.extra)
|
||||
customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList));
|
||||
add(customPropertiesButtonList);
|
||||
addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)");
|
||||
|
||||
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;
|
||||
closeButton.color = 0xFFFF0000;
|
||||
add(closeButton);
|
||||
add(saveButton);
|
||||
}
|
||||
|
||||
/*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);
|
||||
if (iconColorWheel.colorChanged)
|
||||
xml.set("color", iconColorWheel.curColor.toWebString());
|
||||
for (val in customPropertiesButtonList.buttons.members)
|
||||
xml.set(val.propertyText.label.text, val.valueText.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);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package funkin.editors.stager;
|
||||
|
||||
import funkin.options.OptionsScreen;
|
||||
import funkin.game.Stage;
|
||||
import funkin.options.type.IconOption;
|
||||
import funkin.options.type.NewOption;
|
||||
import funkin.options.type.OptionType;
|
||||
import funkin.options.type.TextOption;
|
||||
|
||||
class StageSelection extends EditorTreeMenu
|
||||
{
|
||||
public override function create()
|
||||
{
|
||||
bgType = "charter";
|
||||
super.create();
|
||||
|
||||
var modsList:Array<String> = Stage.getList(true, true);
|
||||
|
||||
var list:Array<OptionType> = [
|
||||
for (char in (modsList.length == 0 ? Stage.getList(false, true) : modsList))
|
||||
new TextOption(char, "Press ACCEPT to edit this stage.",
|
||||
function() {
|
||||
FlxG.switchState(new StageEditor(char));
|
||||
})
|
||||
];
|
||||
|
||||
list.insert(0, new NewOption("New Stage", "New Stage", function() {
|
||||
openSubState(new UIWarningSubstate("New Stage: Feature Not Implemented!", "This feature isnt implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [
|
||||
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
|
||||
]));
|
||||
}));
|
||||
|
||||
main = new OptionsScreen("Stage Editor", "Select a stage to edit. HSCRIPT only stages are not supported.", list);
|
||||
|
||||
DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
|
||||
}
|
||||
|
||||
override function createPost() {
|
||||
super.createPost();
|
||||
|
||||
main.changeSelection(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package funkin.editors.stager.elements;
|
||||
|
||||
import funkin.game.Character;
|
||||
import funkin.game.Stage.StageCharPos;
|
||||
import haxe.xml.Access;
|
||||
|
||||
class StageCharacterButton extends StageElementButton {
|
||||
public var charPos:StageCharPos;
|
||||
public var char:Character;
|
||||
|
||||
public function new(x:Float,y:Float, charPos:StageCharPos, xml:Access) {
|
||||
this.charPos = charPos;
|
||||
this.char = charPos.extra.get(StageEditor.exID("char"));
|
||||
super(x,y, xml);
|
||||
|
||||
color = 0xffa5c4ff;
|
||||
|
||||
updateInfo(charPos);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
editButton.selectable = ghostButton.selectable = deleteButton.selectable = selectable;
|
||||
editButton.shouldPress = ghostButton.shouldPress = deleteButton.shouldPress = shouldPress;
|
||||
|
||||
hovered = !deleteButton.hovered;
|
||||
updatePos();
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
public override function updateInfo(charPos:Dynamic) {
|
||||
if(charPos is StageCharPos) {
|
||||
var charPos:StageCharPos = cast charPos;
|
||||
this.charPos = charPos;
|
||||
char.visible = !isHidden;
|
||||
}
|
||||
super.updateInfo(charPos);
|
||||
}
|
||||
|
||||
public override function onSelect() {
|
||||
|
||||
}
|
||||
|
||||
public override function onGhostClick() {
|
||||
isHidden = !isHidden;
|
||||
updateInfo(this.charPos);
|
||||
}
|
||||
|
||||
public override function getInfoText():String {
|
||||
return '${charPos.name} (${charPos.x}, ${charPos.y})';
|
||||
}
|
||||
|
||||
public override function updatePos() {
|
||||
super.updatePos();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package funkin.editors.stager.elements;
|
||||
|
||||
import haxe.xml.Access;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class StageElementButton extends UIButton {
|
||||
public var xml:Access;
|
||||
|
||||
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 var isHidden:Bool = false;
|
||||
|
||||
public function new(x:Float,y:Float, xml:Access) {
|
||||
this.xml = xml;
|
||||
super(x,y, getInfoText(), function () {
|
||||
onSelect();
|
||||
//CharacterEditor.instance.playAnimation(this.sprite.name);
|
||||
}, 282);
|
||||
autoAlpha = false;
|
||||
|
||||
ghostButton = new UIButton(x+282+17, y, "", function () {
|
||||
onGhostClick();
|
||||
//CharacterEditor.instance.ghostAnim(this.anim);
|
||||
}, 32);
|
||||
ghostButton.autoAlpha = false;
|
||||
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 () {
|
||||
onEdit();
|
||||
}, 32);
|
||||
editButton.frames = Paths.getFrames("editors/ui/grayscale-button");
|
||||
editButton.color = FlxColor.YELLOW;
|
||||
editButton.autoAlpha = false;
|
||||
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 () {
|
||||
onDelete();
|
||||
}, 32);
|
||||
deleteButton.color = FlxColor.RED;
|
||||
deleteButton.autoAlpha = false;
|
||||
members.push(deleteButton);
|
||||
|
||||
deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button'));
|
||||
deleteIcon.antialiasing = false;
|
||||
members.push(deleteIcon);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
editButton.selectable = ghostButton.selectable = deleteButton.selectable = selectable;
|
||||
editButton.shouldPress = ghostButton.shouldPress = deleteButton.shouldPress = shouldPress;
|
||||
|
||||
hovered = !deleteButton.hovered;
|
||||
updatePos();
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
public function updateInfo(sprite:Dynamic) {
|
||||
field.text = getInfoText();
|
||||
|
||||
ghostIcon.animation.play(!isHidden ? "alive" : "dead");
|
||||
ghostIcon.alpha = !isHidden ? 1 : 0.5;
|
||||
}
|
||||
|
||||
public function updatePos() {
|
||||
// buttons
|
||||
deleteButton.x = (editButton.x = (ghostButton.x = (x+282+17))+32+17)+32+17;
|
||||
deleteButton.y = editButton.y = ghostButton.y = y;
|
||||
// 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;
|
||||
}
|
||||
|
||||
public function onSelect() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public function onGhostClick() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public function onEdit() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public function onDelete() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public function getInfoText():String {
|
||||
return 'NOT IMPLEMENTED';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package funkin.editors.stager.elements;
|
||||
|
||||
import haxe.xml.Access;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class StageSolidButton extends StageSpriteButton {
|
||||
|
||||
public function new(x:Float,y:Float, sprite:FlxSprite, xml:Access) {
|
||||
super(x,y, sprite, xml);
|
||||
color = 0xFFD9FF50;
|
||||
}
|
||||
|
||||
public override function onEdit() {
|
||||
// TODO: implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package funkin.editors.stager.elements;
|
||||
|
||||
import haxe.xml.Access;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class StageSpriteButton extends StageElementButton {
|
||||
public var sprite:FlxSprite;
|
||||
|
||||
public function new(x:Float,y:Float, sprite:FlxSprite, xml:Access) {
|
||||
this.sprite = sprite;
|
||||
super(x,y, xml);
|
||||
|
||||
//color = 0xFFD9FF50;
|
||||
|
||||
updateInfo(sprite);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
public override function updateInfo(sprite:Dynamic) {
|
||||
if(sprite is FlxSprite) {
|
||||
var sprite:FlxSprite = cast sprite;
|
||||
this.sprite = sprite;
|
||||
sprite.visible = !isHidden;
|
||||
}
|
||||
super.updateInfo(sprite);
|
||||
}
|
||||
|
||||
public override function onGhostClick() {
|
||||
isHidden = !isHidden;
|
||||
updateInfo(this.sprite);
|
||||
}
|
||||
|
||||
public override function onEdit() {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
public override function onDelete() {
|
||||
sprite.destroy();
|
||||
xml.x.parent.removeChild(xml.x);
|
||||
StageEditor.instance.xmlMap.remove(sprite);
|
||||
StageEditor.instance.stageSpritesWindow.remove(this);
|
||||
}
|
||||
|
||||
public override function getInfoText():String {
|
||||
return '${xml.att.name} (${sprite.x}, ${sprite.y})';
|
||||
}
|
||||
|
||||
public override function updatePos() {
|
||||
super.updatePos();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class UIButtonList<T:UIButton> extends UIWindow {
|
||||
public var cameraSpacing = 30;
|
||||
|
||||
public var buttonSpacing:Float = 16;
|
||||
public var buttonSize:FlxPoint = FlxPoint.get();
|
||||
public var buttonSize:FlxPoint = null;
|
||||
public var buttonOffset:FlxPoint = FlxPoint.get();
|
||||
|
||||
public var dragging:Bool = false;
|
||||
@@ -41,9 +41,9 @@ class UIButtonList<T:UIButton> extends UIWindow {
|
||||
addIcon = new FlxSprite(addButton.x + addButton.bHeight / 2, addButton.y + (32/2) - 8).loadGraphic(Paths.image('editors/charter/add-button'));
|
||||
addIcon.antialiasing = false;
|
||||
addButton.members.push(addIcon);
|
||||
members.push(addButton);
|
||||
content.add(addButton);
|
||||
|
||||
members.push(buttons);
|
||||
content.add(buttons);
|
||||
nextscrollY = buttonCameras.scroll.y = -this.buttonSpacing;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import flixel.graphics.frames.FlxFrame;
|
||||
class UISliceSprite extends UISprite {
|
||||
public var bWidth:Int = 120;
|
||||
public var bHeight:Int = 20;
|
||||
public var framesOffset:Int = 0;
|
||||
public var framesOffset(default, set):Int = 0;
|
||||
|
||||
public var incorporeal:Bool = false;
|
||||
|
||||
@@ -14,6 +14,7 @@ class UISliceSprite extends UISprite {
|
||||
|
||||
frames = Paths.getFrames(path);
|
||||
resize(w, h);
|
||||
calculateFrames();
|
||||
}
|
||||
|
||||
public override function updateButton() {
|
||||
@@ -34,21 +35,41 @@ class UISliceSprite extends UISprite {
|
||||
public var drawMiddle:Bool = true;
|
||||
public var drawBottom:Bool = true;
|
||||
|
||||
var topleft:FlxFrame = null;
|
||||
var top:FlxFrame = null;
|
||||
var topright:FlxFrame = null;
|
||||
var middleleft:FlxFrame = null;
|
||||
var middle:FlxFrame = null;
|
||||
var middleright:FlxFrame = null;
|
||||
var bottomleft:FlxFrame = null;
|
||||
var bottom:FlxFrame = null;
|
||||
var bottomright:FlxFrame = null;
|
||||
|
||||
function set_framesOffset(value:Int) {
|
||||
if(value != framesOffset) {
|
||||
framesOffset = value;
|
||||
calculateFrames();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function calculateFrames() {
|
||||
topleft = frames.frames[framesOffset];
|
||||
top = frames.frames[framesOffset + 1];
|
||||
topright = frames.frames[framesOffset + 2];
|
||||
middleleft = frames.frames[framesOffset + 3];
|
||||
middle = frames.frames[framesOffset + 4];
|
||||
middleright = frames.frames[framesOffset + 5];
|
||||
bottomleft = frames.frames[framesOffset + 6];
|
||||
bottom = frames.frames[framesOffset + 7];
|
||||
bottomright = frames.frames[framesOffset + 8];
|
||||
}
|
||||
|
||||
public override function draw() @:privateAccess {
|
||||
var x:Float = this.x;
|
||||
var y:Float = this.y;
|
||||
|
||||
if (visible && !(bWidth == 0 || bHeight == 0)) {
|
||||
var topleft:FlxFrame = frames.frames[framesOffset];
|
||||
var top:FlxFrame = frames.frames[framesOffset + 1];
|
||||
var topright:FlxFrame = frames.frames[framesOffset + 2];
|
||||
var middleleft:FlxFrame = frames.frames[framesOffset + 3];
|
||||
var middle:FlxFrame = frames.frames[framesOffset + 4];
|
||||
var middleright:FlxFrame = frames.frames[framesOffset + 5];
|
||||
var bottomleft:FlxFrame = frames.frames[framesOffset + 6];
|
||||
var bottom:FlxFrame = frames.frames[framesOffset + 7];
|
||||
var bottomright:FlxFrame = frames.frames[framesOffset + 8];
|
||||
|
||||
// TOP
|
||||
if (drawTop) {
|
||||
// TOP LEFT
|
||||
|
||||
@@ -2,17 +2,33 @@ package funkin.editors.ui;
|
||||
|
||||
class UIWindow extends UISliceSprite {
|
||||
public var titleSpr:UIText;
|
||||
public var collapsable:Bool = false;
|
||||
public var content:FlxTypedGroup<FlxBasic>;
|
||||
|
||||
public override function new(x:Float, y:Float, w:Int, h:Int, title:String) {
|
||||
super(x, y, w, h, "editors/ui/normal-popup");
|
||||
|
||||
members.push(titleSpr = new UIText(x + 25, y, bWidth - 50, title, 15, -1));
|
||||
titleSpr.y = y + ((30 - titleSpr.height) / 2);
|
||||
|
||||
content = new FlxTypedGroup<FlxBasic>();
|
||||
members.push(content);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float) {
|
||||
super.update(elapsed);
|
||||
|
||||
if(collapsable && FlxG.mouse.justPressed) {
|
||||
// TOOLBAR
|
||||
__rect.x = x; __rect.y = y;
|
||||
__rect.width = bWidth; __rect.height = 23;
|
||||
if(UIState.state.isOverlapping(this, __rect)) {
|
||||
content.exists = !content.exists;
|
||||
drawMiddle = !drawMiddle;
|
||||
drawBottom = !drawBottom;
|
||||
}
|
||||
}
|
||||
|
||||
__rect.x = x; __rect.y = y+23;
|
||||
__rect.width = bWidth; __rect.height = bHeight-23;
|
||||
hovered = UIState.state.isOverlapping(this, __rect);
|
||||
|
||||
+79
-32
@@ -1,5 +1,6 @@
|
||||
package funkin.game;
|
||||
|
||||
import flixel.util.typeLimit.OneOfTwo;
|
||||
import flixel.FlxState;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.backend.scripting.Script;
|
||||
@@ -12,65 +13,94 @@ import haxe.xml.Access;
|
||||
using StringTools;
|
||||
|
||||
class Stage extends FlxBasic implements IBeatReceiver {
|
||||
public var extra:Map<String, Dynamic> = [];
|
||||
|
||||
public var stageXML:Access;
|
||||
public var stagePath:String;
|
||||
public var stageFile:String;
|
||||
public var stageName:String;
|
||||
public var stageSprites:Map<String, FlxSprite> = [];
|
||||
public var stageScript:Script;
|
||||
public var state:FlxState;
|
||||
public var characterPoses:Map<String, StageCharPos> = [];
|
||||
|
||||
public var defaultZoom:Float = 1.05;
|
||||
public var startCam = new FlxPoint();
|
||||
|
||||
public var onXMLLoaded:(Access, Array<Access>)->Array<Access> = null;
|
||||
public var onNodeLoaded:(Access, Dynamic)->Dynamic = null;
|
||||
|
||||
private var spritesParentFolder = "";
|
||||
|
||||
public function getSprite(name:String) {
|
||||
public inline function getSprite(name:String) {
|
||||
return stageSprites[name];
|
||||
}
|
||||
|
||||
public function new(stage:String, ?state:FlxState) {
|
||||
public function new(stage:String, ?state:FlxState, autoLoad:Bool = true) {
|
||||
super();
|
||||
|
||||
if (state == null) state = PlayState.instance;
|
||||
if (state == null) state = FlxG.state;
|
||||
this.state = state;
|
||||
|
||||
stagePath = Paths.xml('stages/$stage');
|
||||
stageFile = stage;
|
||||
|
||||
stagePath = Paths.xml('stages/$stageFile');
|
||||
try {
|
||||
if (Assets.exists(stagePath)) stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement());
|
||||
} catch(e) {
|
||||
Logs.trace('Couldn\'t load stage "$stage": ${e.message}', ERROR);
|
||||
Logs.trace('Couldn\'t load stage "$stageFile": ${e.message}', ERROR);
|
||||
}
|
||||
|
||||
if (PlayState.instance != null) {
|
||||
stageScript = Script.create(Paths.script('data/stages/$stage'));
|
||||
if (autoLoad) loadXml(stageXML);
|
||||
}
|
||||
|
||||
public function loadXml(xml:Access, forceLoadAll:Bool = false) {
|
||||
if (PlayState.instance == state) {
|
||||
stageScript = Script.create(Paths.script('data/stages/$stageFile'));
|
||||
PlayState.instance.scripts.add(stageScript);
|
||||
stageScript.load();
|
||||
}
|
||||
|
||||
if (stageXML != null) {
|
||||
if (PlayState.instance != null) {
|
||||
var parsed:Null<Float>;
|
||||
if(stageXML.has.startCamPosX && (parsed = Std.parseFloat(stageXML.att.startCamPosX)) != null) PlayState.instance.camFollow.x = parsed;
|
||||
if(stageXML.has.startCamPosY && (parsed = Std.parseFloat(stageXML.att.startCamPosY)) != null) PlayState.instance.camFollow.y = parsed;
|
||||
if(stageXML.has.zoom && (parsed = Std.parseFloat(stageXML.att.zoom)) != null) PlayState.instance.defaultCamZoom = parsed;
|
||||
PlayState.instance.curStage = stageXML.has.name ? stageXML.att.name : stage;
|
||||
if (xml != null) {
|
||||
var parsed:Null<Float>;
|
||||
if((parsed = Std.parseFloat(xml.getAtt("startCamPosX"))).isNotNull()) startCam.x = parsed;
|
||||
if((parsed = Std.parseFloat(xml.getAtt("startCamPosY"))).isNotNull()) startCam.y = parsed;
|
||||
if((parsed = Std.parseFloat(xml.getAtt("zoom"))).isNotNull()) defaultZoom = parsed;
|
||||
|
||||
stageName = xml.getAtt("name").getDefault(stageFile);
|
||||
|
||||
if (PlayState.instance == state) {
|
||||
if(xml.has.startCamPosX) PlayState.instance.camFollow.x = startCam.x;
|
||||
if(xml.has.startCamPosY) PlayState.instance.camFollow.y = startCam.y;
|
||||
if(xml.has.zoom) PlayState.instance.defaultCamZoom = defaultZoom;
|
||||
PlayState.instance.curStage = stageName;
|
||||
}
|
||||
if (stageXML.has.folder) {
|
||||
spritesParentFolder = stageXML.att.folder;
|
||||
if (xml.has.folder) {
|
||||
spritesParentFolder = xml.att.folder;
|
||||
if (!spritesParentFolder.endsWith("/")) spritesParentFolder += "/";
|
||||
}
|
||||
|
||||
var elems = [];
|
||||
for(node in stageXML.elements) {
|
||||
if (node.name == "high-memory" && !Options.lowMemoryMode)
|
||||
var elems:Array<Access> = [];
|
||||
// some way to tag that the sprites are from the group
|
||||
for(node in xml.elements) {
|
||||
if (node.name == "high-memory" && (!Options.lowMemoryMode || forceLoadAll))
|
||||
for(e in node.elements)
|
||||
elems.push(e);
|
||||
else if (node.name == "low-memory" && (Options.lowMemoryMode || forceLoadAll))
|
||||
for(e in node.elements)
|
||||
elems.push(e);
|
||||
else
|
||||
elems.push(node);
|
||||
}
|
||||
|
||||
if (PlayState.instance != null) {
|
||||
var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, stageXML, elems));
|
||||
if (PlayState.instance == state) {
|
||||
var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, xml, elems));
|
||||
elems = event.elems;
|
||||
}
|
||||
if(onXMLLoaded != null) {
|
||||
elems = onXMLLoaded(xml, elems);
|
||||
}
|
||||
|
||||
for(node in elems) {
|
||||
var sprite:Dynamic = switch(node.name) {
|
||||
@@ -79,8 +109,8 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
|
||||
var spr = XMLUtil.createSpriteFromXML(node, spritesParentFolder, LOOP);
|
||||
|
||||
if (!node.has.zoomfactor && PlayState.instance != null)
|
||||
spr.initialZoom = PlayState.instance.defaultCamZoom;
|
||||
if (!node.has.zoomfactor/* && PlayState.instance == state*/)
|
||||
spr.initialZoom = defaultZoom;//PlayState.instance.defaultCamZoom;
|
||||
|
||||
stageSprites.set(spr.name, spr);
|
||||
state.add(spr);
|
||||
@@ -88,10 +118,11 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
case "box" | "solid":
|
||||
if ( !node.has.name || !node.has.width || !node.has.height) continue;
|
||||
|
||||
var spr = new FlxSprite(
|
||||
var spr = new FunkinSprite(
|
||||
(node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0,
|
||||
(node.has.y) ? Std.parseFloat(node.att.y).getDefault(0) : 0
|
||||
);
|
||||
spr.name = node.getAtt("name");
|
||||
|
||||
(node.name == "solid" ? spr.makeSolid : spr.makeGraphic)(
|
||||
Std.parseInt(node.att.width),
|
||||
@@ -99,7 +130,10 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
(node.has.color) ? CoolUtil.getColorFromDynamic(node.att.color) : -1
|
||||
);
|
||||
|
||||
stageSprites.set(node.getAtt("name"), spr);
|
||||
if (!node.has.zoomfactor/* && PlayState.instance == state*/)
|
||||
spr.initialZoom = defaultZoom;//PlayState.instance.defaultCamZoom;
|
||||
|
||||
stageSprites.set(spr.name, spr);
|
||||
state.add(spr);
|
||||
spr;
|
||||
case "boyfriend" | "bf" | "player":
|
||||
@@ -127,7 +161,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
if (!node.has.name) continue;
|
||||
addCharPos(node.att.name, node);
|
||||
case "ratings" | "combo":
|
||||
if (PlayState.instance == null) continue;
|
||||
if (PlayState.instance != state) continue;
|
||||
PlayState.instance.comboGroup.setPosition(
|
||||
Std.parseFloat(node.getAtt("x")).getDefault(PlayState.instance.comboGroup.x),
|
||||
Std.parseFloat(node.getAtt("y")).getDefault(PlayState.instance.comboGroup.y)
|
||||
@@ -137,9 +171,12 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
default: null;
|
||||
}
|
||||
|
||||
if(PlayState.instance != null) {
|
||||
if(PlayState.instance == state) {
|
||||
sprite = PlayState.instance.scripts.event("onStageNodeParsed", EventManager.get(StageNodeEvent).recycle(this, node, sprite, node.name)).sprite;
|
||||
}
|
||||
if(onNodeLoaded != null) {
|
||||
sprite = onNodeLoaded(node, sprite);
|
||||
}
|
||||
|
||||
if (sprite != null) {
|
||||
for(e in node.nodes.property)
|
||||
@@ -172,7 +209,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
flip: true
|
||||
});
|
||||
|
||||
if (PlayState.instance == null) return;
|
||||
if (PlayState.instance != state) return;
|
||||
for(k=>e in stageSprites) {
|
||||
stageScript.set(k, e);
|
||||
}
|
||||
@@ -181,6 +218,7 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos {
|
||||
var charPos = new StageCharPos();
|
||||
charPos.visible = charPos.active = false;
|
||||
charPos.name = name;
|
||||
|
||||
if (nonXMLInfo != null) {
|
||||
charPos.setPosition(nonXMLInfo.x, nonXMLInfo.y);
|
||||
@@ -239,19 +277,28 @@ class Stage extends FlxBasic implements IBeatReceiver {
|
||||
|
||||
public function measureHit(curMeasure:Int) {}
|
||||
|
||||
public static function getList(?mods:Bool = false):Array<String> {
|
||||
public static function getList(?mods:Bool = false, ?xmlOnly:Bool = false):Array<String> {
|
||||
var list:Array<String> = [];
|
||||
for (path in Paths.getFolderContent('data/stages/', true, mods ? MODS : BOTH))
|
||||
if (Path.extension(path) == "xml" || Path.extension(path) == "hx") {
|
||||
var file:String = Path.withoutDirectory(Path.withoutExtension(path));
|
||||
list.pushOnce(file);
|
||||
var extensions:Array<String> = ["xml"];
|
||||
if (!xmlOnly) extensions.push("hx");
|
||||
|
||||
for (path in Paths.getFolderContent("data/stages/", false, mods ? MODS : BOTH)) {
|
||||
var extension = Path.extension(path);
|
||||
if (extensions.contains(extension)) {
|
||||
list.pushOnce(Path.withoutExtension(path));
|
||||
}
|
||||
}
|
||||
|
||||
trace(list);
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
class StageCharPos extends FlxObject {
|
||||
public var extra:Map<String, Dynamic> = [];
|
||||
|
||||
public var name:String;
|
||||
public var charSpacingX:Float = 20;
|
||||
public var charSpacingY:Float = 0;
|
||||
public var camxoffset:Float = 0;
|
||||
|
||||
Reference in New Issue
Block a user