diff --git a/source/funkin/backend/system/NothingCamera.hx b/source/funkin/backend/system/NothingCamera.hx new file mode 100644 index 00000000..12917499 --- /dev/null +++ b/source/funkin/backend/system/NothingCamera.hx @@ -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, indices:DrawData, uvtData:DrawData, ?colors:DrawData, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void {} + + public override function update(elapsed:Float) {} +} \ No newline at end of file diff --git a/source/funkin/backend/utils/MatrixUtil.hx b/source/funkin/backend/utils/MatrixUtil.hx new file mode 100644 index 00000000..65230b94 --- /dev/null +++ b/source/funkin/backend/utils/MatrixUtil.hx @@ -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>, ?camera:FlxCamera, _width:Float = 1, _height:Float = 1):Array + { + //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 = 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; + } +} \ No newline at end of file diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index 8a2ebd89..534ec538 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -20,7 +20,7 @@ class EditorPicker extends MusicBeatSubstate { { name: "Stage Editor", iconID: 2, - state: null + state: funkin.editors.stager.StageSelection }, { name: "UI Debug State", diff --git a/source/funkin/editors/stager/StageEditor.hx b/source/funkin/editors/stager/StageEditor.hx new file mode 100644 index 00000000..69776852 --- /dev/null +++ b/source/funkin/editors/stager/StageEditor.hx @@ -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; + public var topMenuSpr:UITopMenu; + + public var uiGroup:FlxTypedGroup = new FlxTypedGroup(); + public var stageSpritesWindow:UIButtonList; + + public var xmlMap:Map = new Map(); + + public var chars:Array = []; + + public var stageCamera:FlxCamera; + public var guideCamera:FlxCamera; + public var uiCamera:FlxCamera; + + public var undos:UndoList = new UndoList(); + + 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 = []; + stage = new Stage(__stage, this, false); + stage.onXMLLoaded = function(xml:Access, elems:Array) { + 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(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 "\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); +} \ No newline at end of file diff --git a/source/funkin/editors/stager/StageInfoScreen.hx b/source/funkin/editors/stager/StageInfoScreen.hx new file mode 100644 index 00000000..89b3cedf --- /dev/null +++ b/source/funkin/editors/stager/StageInfoScreen.hx @@ -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(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); + }*/ +} \ No newline at end of file diff --git a/source/funkin/editors/stager/StageSelection.hx b/source/funkin/editors/stager/StageSelection.hx new file mode 100644 index 00000000..d53e5d12 --- /dev/null +++ b/source/funkin/editors/stager/StageSelection.hx @@ -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 = Stage.getList(true, true); + + var list:Array = [ + 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); + } +} \ No newline at end of file diff --git a/source/funkin/editors/stager/elements/StageCharacterButton.hx b/source/funkin/editors/stager/elements/StageCharacterButton.hx new file mode 100644 index 00000000..d133ed29 --- /dev/null +++ b/source/funkin/editors/stager/elements/StageCharacterButton.hx @@ -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(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/stager/elements/StageElementButton.hx b/source/funkin/editors/stager/elements/StageElementButton.hx new file mode 100644 index 00000000..b1b9a507 --- /dev/null +++ b/source/funkin/editors/stager/elements/StageElementButton.hx @@ -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'; + } +} \ No newline at end of file diff --git a/source/funkin/editors/stager/elements/StageSolidButton.hx b/source/funkin/editors/stager/elements/StageSolidButton.hx new file mode 100644 index 00000000..96f74c63 --- /dev/null +++ b/source/funkin/editors/stager/elements/StageSolidButton.hx @@ -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 + } +} \ No newline at end of file diff --git a/source/funkin/editors/stager/elements/StageSpriteButton.hx b/source/funkin/editors/stager/elements/StageSpriteButton.hx new file mode 100644 index 00000000..60405cef --- /dev/null +++ b/source/funkin/editors/stager/elements/StageSpriteButton.hx @@ -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(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIButtonList.hx b/source/funkin/editors/ui/UIButtonList.hx index ec8385e5..c516c73d 100644 --- a/source/funkin/editors/ui/UIButtonList.hx +++ b/source/funkin/editors/ui/UIButtonList.hx @@ -14,7 +14,7 @@ class UIButtonList 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 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; } diff --git a/source/funkin/editors/ui/UISliceSprite.hx b/source/funkin/editors/ui/UISliceSprite.hx index 09226205..2de6aeb0 100644 --- a/source/funkin/editors/ui/UISliceSprite.hx +++ b/source/funkin/editors/ui/UISliceSprite.hx @@ -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 diff --git a/source/funkin/editors/ui/UIWindow.hx b/source/funkin/editors/ui/UIWindow.hx index 6853ce06..59ff735f 100644 --- a/source/funkin/editors/ui/UIWindow.hx +++ b/source/funkin/editors/ui/UIWindow.hx @@ -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; 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(); + 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); diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index f42cc90e..d9e6a288 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -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 = []; + public var stageXML:Access; public var stagePath:String; + public var stageFile:String; + public var stageName:String; public var stageSprites:Map = []; public var stageScript:Script; public var state:FlxState; public var characterPoses:Map = []; + public var defaultZoom:Float = 1.05; + public var startCam = new FlxPoint(); + + public var onXMLLoaded:(Access, Array)->Array = 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; - 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; + 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 = []; + // 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 { + public static function getList(?mods:Bool = false, ?xmlOnly:Bool = false):Array { var list:Array = []; - 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 = ["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 = []; + + public var name:String; public var charSpacingX:Float = 20; public var charSpacingY:Float = 0; public var camxoffset:Float = 0;