mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-24 08:38:16 -05:00
the stage editor shit
This commit is contained in:
parent
b2647fe09f
commit
0a5419d7fc
24 changed files with 4025 additions and 3 deletions
|
@ -20,6 +20,10 @@ class StageData
|
|||
@:optional
|
||||
public var cameraZoom:Null<Float>;
|
||||
|
||||
@:default("shared")
|
||||
@:optional
|
||||
public var directory:Null<String>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = StageRegistry.STAGE_DATA_VERSION;
|
||||
|
@ -198,6 +202,32 @@ typedef StageDataProp =
|
|||
@:default("sparrow")
|
||||
@:optional
|
||||
var animType:String;
|
||||
|
||||
/**
|
||||
* The angle of the prop, as a float.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var angle:Float;
|
||||
|
||||
/**
|
||||
* The blend mode of the prop, as a string.
|
||||
* Just like in photoshop.
|
||||
* @default Nothing.
|
||||
*/
|
||||
@:default("")
|
||||
@:optional
|
||||
var blend:String;
|
||||
|
||||
/**
|
||||
* The color of the prop overlay, as a hex string.
|
||||
* White overlays, or the ones with the value #FFFFFF, do not appear.
|
||||
* @default `#FFFFFF`
|
||||
*/
|
||||
@:default("#FFFFFF")
|
||||
@:optional
|
||||
var color:String;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
|
|
|
@ -256,6 +256,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
propSprite.scrollFactor.x = dataProp.scroll[0];
|
||||
propSprite.scrollFactor.y = dataProp.scroll[1];
|
||||
|
||||
propSprite.angle = dataProp.angle;
|
||||
propSprite.color = FlxColor.fromString(dataProp.color);
|
||||
@:privateAccess if (!isSolidColor) propSprite.blend = BlendMode.fromString(dataProp.blend);
|
||||
|
||||
propSprite.zIndex = dataProp.zIndex;
|
||||
|
||||
propSprite.flipX = dataProp.flipX;
|
||||
|
|
|
@ -10,6 +10,7 @@ import funkin.save.migrator.SaveDataMigrator;
|
|||
import funkin.save.migrator.SaveDataMigrator;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorTheme;
|
||||
import funkin.util.SerializerUtil;
|
||||
import thx.semver.Version;
|
||||
import thx.semver.Version;
|
||||
|
@ -146,6 +147,14 @@ class Save
|
|||
hitsoundVolumeOpponent: 1.0,
|
||||
themeMusic: true
|
||||
},
|
||||
|
||||
optionsStageEditor:
|
||||
{
|
||||
previousFiles: [],
|
||||
moveStep: "1px",
|
||||
angleStep: 5,
|
||||
theme: StageEditorTheme.Light
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -428,6 +437,91 @@ class Save
|
|||
return data.unlocks.oldChar;
|
||||
}
|
||||
|
||||
public var stageEditorPreviousFiles(get, set):Array<String>;
|
||||
|
||||
function get_stageEditorPreviousFiles():Array<String>
|
||||
{
|
||||
if (data.optionsStageEditor.previousFiles == null) data.optionsStageEditor.previousFiles = [];
|
||||
|
||||
return data.optionsStageEditor.previousFiles;
|
||||
}
|
||||
|
||||
function set_stageEditorPreviousFiles(value:Array<String>):Array<String>
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.previousFiles = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.previousFiles;
|
||||
}
|
||||
|
||||
public var stageEditorHasBackup(get, set):Bool;
|
||||
|
||||
function get_stageEditorHasBackup():Bool
|
||||
{
|
||||
if (data.optionsStageEditor.hasBackup == null) data.optionsStageEditor.hasBackup = false;
|
||||
|
||||
return data.optionsStageEditor.hasBackup;
|
||||
}
|
||||
|
||||
function set_stageEditorHasBackup(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.hasBackup = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.hasBackup;
|
||||
}
|
||||
|
||||
public var stageEditorMoveStep(get, set):String;
|
||||
|
||||
function get_stageEditorMoveStep():String
|
||||
{
|
||||
if (data.optionsStageEditor.moveStep == null) data.optionsStageEditor.moveStep = "1px";
|
||||
|
||||
return data.optionsStageEditor.moveStep;
|
||||
}
|
||||
|
||||
function set_stageEditorMoveStep(value:String):String
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.moveStep = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.moveStep;
|
||||
}
|
||||
|
||||
public var stageEditorAngleStep(get, set):Float;
|
||||
|
||||
function get_stageEditorAngleStep():Float
|
||||
{
|
||||
if (data.optionsStageEditor.angleStep == null) data.optionsStageEditor.angleStep = 5;
|
||||
|
||||
return data.optionsStageEditor.angleStep;
|
||||
}
|
||||
|
||||
function set_stageEditorAngleStep(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.angleStep = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.angleStep;
|
||||
}
|
||||
|
||||
public var stageEditorTheme(get, set):StageEditorTheme;
|
||||
|
||||
function get_stageEditorTheme():StageEditorTheme
|
||||
{
|
||||
if (data.optionsStageEditor.theme == null) data.optionsStageEditor.theme = StageEditorTheme.Light;
|
||||
|
||||
return data.optionsStageEditor.theme;
|
||||
}
|
||||
|
||||
function set_stageEditorTheme(value:StageEditorTheme):StageEditorTheme
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.theme = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we've seen a character unlock, add it to the list of characters seen.
|
||||
* @param character
|
||||
|
@ -1068,6 +1162,11 @@ typedef RawSaveData =
|
|||
* The user's preferences specific to the Chart Editor.
|
||||
*/
|
||||
var optionsChartEditor:SaveDataChartEditorOptions;
|
||||
|
||||
/**
|
||||
* The user's preferences specific to the Stage Editor.
|
||||
*/
|
||||
var optionsStageEditor:SaveDataStageEditorOptions;
|
||||
};
|
||||
|
||||
typedef SaveApiData =
|
||||
|
@ -1441,3 +1540,39 @@ typedef SaveDataChartEditorOptions =
|
|||
*/
|
||||
var ?playbackSpeed:Float;
|
||||
};
|
||||
|
||||
typedef SaveDataStageEditorOptions =
|
||||
{
|
||||
// a lot of these things were copied from savedatacharteditoroptions
|
||||
|
||||
/**
|
||||
* Whether the Stage Editor created a backup the last time it closed.
|
||||
* Prompt the user to load it, then set this back to `false`.
|
||||
* @default `false`
|
||||
*/
|
||||
var ?hasBackup:Bool;
|
||||
|
||||
/**
|
||||
* Previous files opened in the Stage Editor.
|
||||
* @default `[]`
|
||||
*/
|
||||
var ?previousFiles:Array<String>;
|
||||
|
||||
/**
|
||||
* The Step at which an Object or Character is moved.
|
||||
* @default `1px`
|
||||
*/
|
||||
var ?moveStep:String;
|
||||
|
||||
/**
|
||||
* The Step at which an Object is rotated.
|
||||
* @default `5`
|
||||
*/
|
||||
var ?angleStep:Float;
|
||||
|
||||
/**
|
||||
* Theme in the Stage Editor.
|
||||
* @default `StageEditorTheme.Light`
|
||||
*/
|
||||
var ?theme:StageEditorTheme;
|
||||
};
|
||||
|
|
|
@ -60,7 +60,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
// createItem("Input Offset Testing", openInputOffsetTesting);
|
||||
createItem("CHARACTER SELECT", openCharSelect, true);
|
||||
createItem("ANIMATION EDITOR", openAnimationEditor);
|
||||
// createItem("STAGE EDITOR", openStageEditor);
|
||||
createItem("STAGE EDITOR", openStageEditor);
|
||||
// createItem("TEST STICKERS", testStickers);
|
||||
#if sys
|
||||
createItem("OPEN CRASH LOG FOLDER", openLogFolder);
|
||||
|
@ -125,6 +125,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
function openStageEditor()
|
||||
{
|
||||
trace('Stage Editor');
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stageeditor.StageEditorState());
|
||||
}
|
||||
|
||||
#if sys
|
||||
|
|
119
source/funkin/ui/debug/stageeditor/StageEditorObject.hx
Normal file
119
source/funkin/ui/debug/stageeditor/StageEditorObject.hx
Normal file
|
@ -0,0 +1,119 @@
|
|||
package funkin.ui.debug.stageeditor;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* Contains all the Logic needed for Stage Editor. Only for Stage Editor, as in the gameplay StageProps and Boppers will be used.
|
||||
*/
|
||||
class StageEditorObject extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* The internal Name of the Object.
|
||||
*/
|
||||
public var name:String = "Unnamed";
|
||||
|
||||
/**
|
||||
* What animation to play upon starting.
|
||||
*/
|
||||
public var startingAnimation:String = "";
|
||||
|
||||
public var animDatas:Map<String, AnimationData> = [];
|
||||
|
||||
override public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Object is currently being modified in the Stage Editor.
|
||||
*/
|
||||
public var isDebugged(default, set):Bool = true;
|
||||
|
||||
function set_isDebugged(value:Bool)
|
||||
{
|
||||
this.isDebugged = value;
|
||||
|
||||
if (value == false) // plays upon starting yippee!!!
|
||||
playAnim(startingAnimation, true);
|
||||
else
|
||||
{
|
||||
if (animation.curAnim != null)
|
||||
{
|
||||
animation.stop();
|
||||
offset.set();
|
||||
updateHitbox();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public function playAnim(name:String, restart:Bool = false, reversed:Bool = false)
|
||||
{
|
||||
if (!animation.getNameList().contains(name)) return;
|
||||
|
||||
animation.play(name, restart, reversed, 0);
|
||||
|
||||
if (animDatas.exists(name)) offset.set(animDatas[name].offsets[0], animDatas[name].offsets[1]);
|
||||
else
|
||||
offset.set();
|
||||
}
|
||||
|
||||
/**
|
||||
* On which beat should it dance?
|
||||
*/
|
||||
public var danceEvery:Float = 0;
|
||||
|
||||
/**
|
||||
* Internal, handles danceLeft and danceRight.
|
||||
*/
|
||||
var _danced:Bool = true;
|
||||
|
||||
public function dance(restart:Bool = false)
|
||||
{
|
||||
if (isDebugged) return;
|
||||
|
||||
var idle = animation.getNameList().contains("idle");
|
||||
var dancing = animation.getNameList().contains("danceLeft") && animation.getNameList().contains("danceRight");
|
||||
|
||||
if (!idle && !dancing) return;
|
||||
|
||||
if (dancing)
|
||||
{
|
||||
if (_danced) playAnim("danceRight", restart);
|
||||
else
|
||||
playAnim("danceLeft", restart);
|
||||
|
||||
_danced = !_danced;
|
||||
}
|
||||
else if (idle)
|
||||
{
|
||||
playAnim("idle", restart);
|
||||
}
|
||||
}
|
||||
|
||||
public function addAnim(name:String, prefix:String, offsets:Array<Float>, indices:Array<Int>, frameRate:Int = 24, looped:Bool = true, flipX:Bool = false,
|
||||
flipY:Bool = false)
|
||||
{
|
||||
if (indices.length > 0) animation.addByIndices(name, prefix, indices, "", frameRate, looped, flipX, flipY);
|
||||
else
|
||||
animation.addByPrefix(name, prefix, frameRate, looped, flipX, flipY);
|
||||
|
||||
if (animation.getNameList().contains(name)) // sometimes the animation doesnt add
|
||||
{
|
||||
animDatas.set(name,
|
||||
{
|
||||
name: name,
|
||||
prefix: prefix,
|
||||
offsets: offsets,
|
||||
looped: looped,
|
||||
frameRate: frameRate,
|
||||
flipX: flipX,
|
||||
flipY: flipY,
|
||||
frameIndices: indices
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
1486
source/funkin/ui/debug/stageeditor/StageEditorState.hx
Normal file
1486
source/funkin/ui/debug/stageeditor/StageEditorState.hx
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,6 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/about.xml"))
|
||||
class AboutDialog extends Dialog {}
|
|
@ -0,0 +1,80 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Path;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:xml('
|
||||
<dialog id="backupAvailableDialog" width="475" height="150" title="Hey! Listen!">
|
||||
<vbox width="100%" height="100%">
|
||||
<label text="There is a chart backup available, would you like to open it?\n" width="100%" textAlign="center" />
|
||||
<spacer height="6" />
|
||||
<label id="backupTimeLabel" text="Jan 1, 1970 0:00" width="100%" textAlign="center" />
|
||||
<spacer height="100%" />
|
||||
<hbox width="100%">
|
||||
<button text="No Thanks" id="dialogCancel" />
|
||||
<spacer width="100%" />
|
||||
<button text="Take Me There" id="buttonGoToFolder" />
|
||||
<spacer width="100%" />
|
||||
<button text="Open It" id="buttonOpenBackup" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
')
|
||||
class BackupAvailableDialog extends Dialog
|
||||
{
|
||||
override public function new(state:StageEditorState, filePath:String)
|
||||
{
|
||||
super();
|
||||
|
||||
if (!FileUtil.doesFileExist(filePath)) return;
|
||||
|
||||
// time text
|
||||
var fileDate = Path.withoutExtension(Path.withoutDirectory(filePath));
|
||||
var dateParts = fileDate.split("-");
|
||||
|
||||
while (dateParts.length < 8)
|
||||
dateParts.push("0");
|
||||
|
||||
var year:Int = Std.parseInt(dateParts[2]) ?? 0; // copied parts from ChartEditorImportExportHandler.hx
|
||||
var month:Int = Std.parseInt(dateParts[3]) ?? 1;
|
||||
var day:Int = Std.parseInt(dateParts[4]) ?? 0;
|
||||
var hour:Int = Std.parseInt(dateParts[5]) ?? 0;
|
||||
var minute:Int = Std.parseInt(dateParts[6]) ?? 0;
|
||||
var second:Int = Std.parseInt(dateParts[7]) ?? 0;
|
||||
|
||||
backupTimeLabel.text = DateUtil.generateCleanTimestamp(new Date(year, month - 1, day, hour, minute, second));
|
||||
|
||||
// button callbacks
|
||||
dialogCancel.onClick = function(_) hideDialog(DialogButton.CANCEL);
|
||||
|
||||
buttonGoToFolder.onClick = function(_) {
|
||||
// :[
|
||||
#if sys
|
||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), StageEditorState.BACKUPS_PATH]);
|
||||
WindowUtil.openFolder(absoluteBackupsPath);
|
||||
#end
|
||||
}
|
||||
|
||||
buttonOpenBackup.onClick = function(_) {
|
||||
if (FileUtil.doesFileExist(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
|
||||
{
|
||||
state.welcomeDialog.loadFromFilePath(filePath);
|
||||
}
|
||||
hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
|
||||
// uhhh
|
||||
onDialogClosed = function(event) {
|
||||
if (event.button == DialogButton.APPLY)
|
||||
{
|
||||
if (state.welcomeDialog != null) state.welcomeDialog.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/exit-confirm.xml"))
|
||||
class ExitConfirmDialog extends Dialog
|
||||
{
|
||||
var onComplete:Void->Void = null;
|
||||
|
||||
override public function new(onComp:Void->Void)
|
||||
{
|
||||
super();
|
||||
|
||||
onComplete = onComp;
|
||||
|
||||
buttons = DialogButton.CANCEL | "{{Proceed}}";
|
||||
defaultButton = "{{Proceed}}";
|
||||
|
||||
destroyOnClose = true;
|
||||
}
|
||||
|
||||
public override function validateDialog(button:DialogButton, fn:Bool->Void)
|
||||
{
|
||||
if (button == "{{Proceed}}" && onComplete != null)
|
||||
{
|
||||
onComplete();
|
||||
}
|
||||
|
||||
fn(true);
|
||||
}
|
||||
}
|
105
source/funkin/ui/debug/stageeditor/components/FindObjDialog.hx
Normal file
105
source/funkin/ui/debug/stageeditor/components/FindObjDialog.hx
Normal file
|
@ -0,0 +1,105 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import funkin.play.stage.StageProp;
|
||||
import funkin.ui.debug.stageeditor.handlers.AssetDataHandler;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.components.CheckBox;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/find-object.xml"))
|
||||
class FindObjDialog extends Dialog
|
||||
{
|
||||
var stageEditorState:StageEditorState;
|
||||
|
||||
var assets:Array<StageEditorObject> = [];
|
||||
var curSelected:Int = 0;
|
||||
var field:TextField;
|
||||
|
||||
var checkWord:CheckBox;
|
||||
var checkCaps:CheckBox;
|
||||
|
||||
override public function new(state:StageEditorState, searchFor:String = "")
|
||||
{
|
||||
super();
|
||||
|
||||
stageEditorState = state;
|
||||
nameField.text = searchFor;
|
||||
|
||||
this.field = nameField;
|
||||
this.checkWord = wordCheck;
|
||||
this.checkCaps = capsCheck;
|
||||
|
||||
field.onChange = function(_) updateIndicator();
|
||||
indicator.hide();
|
||||
|
||||
top = 20;
|
||||
left = FlxG.width - width - 20;
|
||||
|
||||
buttons = DialogButton.CANCEL | "{{Find Next}}";
|
||||
defaultButton = "{{Find Next}}";
|
||||
}
|
||||
|
||||
public function updateIndicator()
|
||||
{
|
||||
var prevObjCheck = assets[curSelected];
|
||||
|
||||
assets = [];
|
||||
|
||||
for (ass in stageEditorState.spriteArray)
|
||||
{
|
||||
var name = ass.name;
|
||||
var checkFor = field.text;
|
||||
|
||||
if (!checkCaps.selected)
|
||||
{
|
||||
name = name.toLowerCase();
|
||||
checkFor = checkFor.toLowerCase();
|
||||
}
|
||||
|
||||
if (((name.contains(checkFor) && !checkWord.selected) || (name == checkFor && checkWord.selected)) && ass.visible) assets.push(ass);
|
||||
}
|
||||
|
||||
if (assets.length > 0 && prevObjCheck == null)
|
||||
{
|
||||
stageEditorState.selectedSprite = assets[0];
|
||||
}
|
||||
|
||||
if (assets.length > 0)
|
||||
{
|
||||
indicator.text = "Selected: " + (assets.indexOf(stageEditorState.selectedSprite) + 1) + " / " + assets.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
indicator.text = "No Matches Found";
|
||||
}
|
||||
|
||||
if (field.text != "" && field.text != null) indicator.show();
|
||||
else
|
||||
indicator.hide();
|
||||
}
|
||||
|
||||
public override function validateDialog(button:DialogButton, fn:Bool->Void)
|
||||
{
|
||||
var done = true;
|
||||
|
||||
if (button == "{{Find Next}}")
|
||||
{
|
||||
done = false;
|
||||
|
||||
if (assets.length > 0)
|
||||
{
|
||||
curSelected = assets.indexOf(stageEditorState.selectedSprite);
|
||||
curSelected++;
|
||||
|
||||
if (curSelected >= assets.length) curSelected = 0;
|
||||
|
||||
stageEditorState.selectedSprite = assets[curSelected];
|
||||
indicator.text = "Selected: " + (assets.indexOf(stageEditorState.selectedSprite) + 1) + " / " + assets.length;
|
||||
|
||||
stageEditorState.camFollow.x = assets[curSelected].getMidpoint().x;
|
||||
stageEditorState.camFollow.y = assets[curSelected].getMidpoint().y;
|
||||
}
|
||||
}
|
||||
fn(done);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import lime.utils.Bytes;
|
||||
import haxe.ui.components.TextField;
|
||||
import openfl.net.URLLoader;
|
||||
import openfl.net.URLRequest;
|
||||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.events.ProgressEvent;
|
||||
import openfl.events.SecurityErrorEvent;
|
||||
import openfl.utils.ByteArray;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/load-url.xml"))
|
||||
class LoadFromUrlDialog extends Dialog
|
||||
{
|
||||
var urlField:TextField;
|
||||
var loader:URLLoader;
|
||||
|
||||
override public function new(successCallback:Bytes->Void = null, failCallback:String->Void = null)
|
||||
{
|
||||
super();
|
||||
destroyOnClose = true;
|
||||
|
||||
loader = new URLLoader();
|
||||
loader.dataFormat = BINARY;
|
||||
|
||||
urlField.text = "";
|
||||
|
||||
loader.addEventListener(Event.COMPLETE, function(event:Event) {
|
||||
var bytes:Bytes = cast(loader.data, ByteArray);
|
||||
|
||||
if (successCallback != null) successCallback(bytes);
|
||||
|
||||
trace("loaded the image and did success callback");
|
||||
|
||||
@:privateAccess
|
||||
loader.__removeAllListeners();
|
||||
|
||||
hideDialog(DialogButton.CANCEL);
|
||||
});
|
||||
|
||||
loader.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent) {
|
||||
if (failCallback != null) failCallback(urlField.text);
|
||||
|
||||
trace("error with this shit");
|
||||
});
|
||||
|
||||
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(event:SecurityErrorEvent) {
|
||||
if (failCallback != null) failCallback(urlField.text);
|
||||
|
||||
trace("error with this shit");
|
||||
});
|
||||
|
||||
buttons = DialogButton.CANCEL | "{{Load}}";
|
||||
defaultButton = "{{Load}}";
|
||||
}
|
||||
|
||||
override public function validateDialog(button:DialogButton, fn:Bool->Void)
|
||||
{
|
||||
if (button == DialogButton.CANCEL)
|
||||
{
|
||||
fn(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
loader.load(new URLRequest(urlField.text));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.components.Link;
|
||||
import funkin.ui.debug.stageeditor.handlers.AssetDataHandler;
|
||||
import funkin.save.Save;
|
||||
import funkin.util.FileUtil;
|
||||
import lime.ui.FileDialog;
|
||||
import flixel.FlxG;
|
||||
import openfl.display.BitmapData;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import funkin.play.stage.StageProp;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/new-object.xml"))
|
||||
class NewObjDialog extends Dialog
|
||||
{
|
||||
var stageEditorState:StageEditorState;
|
||||
var bitmap:BitmapData;
|
||||
|
||||
override public function new(state:StageEditorState, img:BitmapData = null)
|
||||
{
|
||||
super();
|
||||
|
||||
stageEditorState = state;
|
||||
bitmap = img;
|
||||
|
||||
field.onChange = function(_) {
|
||||
field.removeClasses(["invalid-value", "valid-value"]);
|
||||
}
|
||||
|
||||
buttons = DialogButton.CANCEL | "{{Create}}";
|
||||
defaultButton = "{{Create}}";
|
||||
|
||||
destroyOnClose = true;
|
||||
}
|
||||
|
||||
public override function validateDialog(button:DialogButton, fn:Bool->Void)
|
||||
{
|
||||
var done = true;
|
||||
|
||||
if (button == "{{Create}}")
|
||||
{
|
||||
var objNames = [for (obj in StageEditorState.instance.spriteArray) obj.name];
|
||||
|
||||
if (field.text == "" || field.text == null || objNames.contains(field.text))
|
||||
{
|
||||
field.swapClass("invalid-value", "valid-value");
|
||||
done = false;
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: "Problem Creating an Object",
|
||||
body: objNames.contains(field.text) ? "Object with the Name " + field.text + " already exists!" : "Invalid Object Name!",
|
||||
type: NotificationType.Error
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var spr = new StageEditorObject();
|
||||
|
||||
if (bitmap != null)
|
||||
{
|
||||
var bitToLoad = stageEditorState.addBitmap(bitmap);
|
||||
spr.loadGraphic(stageEditorState.bitmaps[bitToLoad]);
|
||||
}
|
||||
else
|
||||
spr.loadGraphic(AssetDataHandler.getDefaultGraphic());
|
||||
|
||||
spr.name = field.text;
|
||||
spr.screenCenter();
|
||||
spr.zIndex = 0;
|
||||
|
||||
stageEditorState.selectedSprite = spr;
|
||||
stageEditorState.createAndPushAction(OBJECT_CREATED);
|
||||
|
||||
stageEditorState.add(spr);
|
||||
stageEditorState.updateArray();
|
||||
stageEditorState.saved = false;
|
||||
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: "Object Creating Successful",
|
||||
body: "Successfully created an Object with the Name " + field.text + "!",
|
||||
type: NotificationType.Success
|
||||
});
|
||||
}
|
||||
}
|
||||
fn(done);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/user-guide.xml"))
|
||||
class UserGuideDialog extends Dialog {}
|
146
source/funkin/ui/debug/stageeditor/components/WelcomeDialog.hx
Normal file
146
source/funkin/ui/debug/stageeditor/components/WelcomeDialog.hx
Normal file
|
@ -0,0 +1,146 @@
|
|||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
|
||||
import haxe.ui.components.Link;
|
||||
import funkin.ui.debug.stageeditor.handlers.StageDataHandler;
|
||||
import funkin.save.Save;
|
||||
import funkin.util.FileUtil;
|
||||
import lime.ui.FileDialog;
|
||||
import flixel.FlxG;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorDialogType;
|
||||
|
||||
using funkin.util.tools.FloatTools;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/welcome.xml"))
|
||||
class WelcomeDialog extends Dialog
|
||||
{
|
||||
var stageEditorState:StageEditorState;
|
||||
|
||||
override public function new(state:StageEditorState)
|
||||
{
|
||||
super();
|
||||
|
||||
stageEditorState = state;
|
||||
|
||||
buttonNew.onClick = function(_) {
|
||||
stageEditorState.clearAssets();
|
||||
stageEditorState.loadDummyData();
|
||||
stageEditorState.currentFile = "";
|
||||
killDaDialog();
|
||||
}
|
||||
|
||||
for (file in Save.instance.stageEditorPreviousFiles)
|
||||
{
|
||||
trace(file);
|
||||
|
||||
if (!FileUtil.doesFileExist(file)) continue; // whats the point of loading something that doesnt exist
|
||||
|
||||
var patj = new haxe.io.Path(file);
|
||||
|
||||
var fileText = new Link();
|
||||
fileText.percentWidth = 100;
|
||||
fileText.text = patj.file + "." + patj.ext;
|
||||
fileText.onClick = function(_) loadFromFilePath(file);
|
||||
|
||||
#if sys
|
||||
var stat = sys.FileSystem.stat(file);
|
||||
var sizeInMB = (stat.size / 1000000).round(2);
|
||||
|
||||
fileText.tooltip = "Full Name: " + file + "\nLast Modified: " + stat.mtime.toString() + "\nSize: " + sizeInMB + "MB";
|
||||
#end
|
||||
|
||||
contentRecent.addComponent(fileText);
|
||||
}
|
||||
|
||||
boxDrag.onClick = function(_) FileUtil.browseForSaveFile([FileUtil.FILE_FILTER_FNFS], loadFromFilePath, null, null, "Open Stage Data");
|
||||
|
||||
var defaultStages = StageRegistry.instance.listBaseGameStageIds();
|
||||
defaultStages.sort(funkin.util.SortUtil.alphabetically);
|
||||
|
||||
for (stage in defaultStages)
|
||||
{
|
||||
var baseStage = StageRegistry.instance.parseEntryDataWithMigration(stage, StageRegistry.instance.fetchEntryVersion(stage));
|
||||
if (baseStage == null) continue;
|
||||
|
||||
var link = new Link(); // this is how the legend of zelda started btw
|
||||
link.percentWidth = 100;
|
||||
link.text = baseStage.name;
|
||||
|
||||
link.onClick = function(_) loadFromPreset(baseStage);
|
||||
|
||||
contentPresets.addComponent(link);
|
||||
}
|
||||
|
||||
FlxG.stage.window.onDropFile.add(loadFromFilePath);
|
||||
}
|
||||
|
||||
public function loadFromPreset(data:StageData)
|
||||
{
|
||||
if (data == null) return;
|
||||
|
||||
if (!stageEditorState.saved)
|
||||
{
|
||||
Dialogs.messageBox("This will destroy all of your Unsaved Work.\n\nAre you sure? This cannot be undone.", "Load Stage", MessageBoxType.TYPE_YESNO, true,
|
||||
function(btn:DialogButton) {
|
||||
if (btn == DialogButton.YES)
|
||||
{
|
||||
stageEditorState.saved = true;
|
||||
loadFromPreset(data);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stageEditorState.clearAssets();
|
||||
stageEditorState.currentFile = "";
|
||||
stageEditorState.loadFromDataRaw(data);
|
||||
killDaDialog();
|
||||
}
|
||||
|
||||
public function loadFromFilePath(file:String)
|
||||
{
|
||||
if (!stageEditorState.saved)
|
||||
{
|
||||
Dialogs.messageBox("This will destroy all of your Unsaved Work.\n\nAre you sure? This cannot be undone.", "Load Stage", MessageBoxType.TYPE_YESNO, true,
|
||||
function(btn:DialogButton) {
|
||||
if (btn == DialogButton.YES)
|
||||
{
|
||||
stageEditorState.saved = true;
|
||||
loadFromFilePath(file);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = FileUtil.readBytesFromPath(file);
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
stageEditorState.notifyChange("Problem Loading the Stage", "The Stage File could not be loaded.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
stageEditorState.clearAssets();
|
||||
stageEditorState.currentFile = file;
|
||||
stageEditorState.unpackShitFromZip(bytes);
|
||||
killDaDialog();
|
||||
}
|
||||
|
||||
function killDaDialog()
|
||||
{
|
||||
stageEditorState.updateDialog(StageEditorDialogType.OBJECT);
|
||||
stageEditorState.updateDialog(StageEditorDialogType.CHARACTER);
|
||||
stageEditorState.updateDialog(StageEditorDialogType.STAGE);
|
||||
|
||||
FlxG.stage.window.onDropFile.remove(loadFromFilePath);
|
||||
hide();
|
||||
destroy();
|
||||
}
|
||||
}
|
208
source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx
Normal file
208
source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx
Normal file
|
@ -0,0 +1,208 @@
|
|||
package funkin.ui.debug.stageeditor.handlers;
|
||||
|
||||
import flixel.FlxG;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.math.FlxRect;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.data.stage.StageData.StageDataProp;
|
||||
|
||||
using StringTools;
|
||||
|
||||
/**
|
||||
* Handles the Stage Props and Datas - being able to convert one to the other.
|
||||
*/
|
||||
class AssetDataHandler
|
||||
{
|
||||
static var state:StageEditorState;
|
||||
|
||||
public static function init(state:StageEditorState)
|
||||
{
|
||||
AssetDataHandler.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns an Object into Data.
|
||||
* @param obj the Object whose data to read.
|
||||
* @param useBitmaps Whether to Save object's BitmapData directly.
|
||||
* @return Data of the Object
|
||||
*/
|
||||
public static function toData(obj:StageEditorObject, useBitmaps:Bool = false):StageEditorObjectData
|
||||
{
|
||||
var outputData:StageEditorObjectData =
|
||||
{
|
||||
name: obj.name,
|
||||
assetPath: "",
|
||||
position: [obj.x, obj.y],
|
||||
zIndex: obj.zIndex,
|
||||
isPixel: !obj.antialiasing,
|
||||
scale: obj.scale.x == obj.scale.y ? Left(obj.scale.x) : Right([obj.scale.x, obj.scale.y]),
|
||||
alpha: obj.alpha,
|
||||
danceEvery: obj.animation.getNameList().length > 0 ? obj.danceEvery : 0,
|
||||
scroll: [obj.scrollFactor.x, obj.scrollFactor.y],
|
||||
animations: [for (n => d in obj.animDatas) d],
|
||||
startingAnimation: obj.startingAnimation,
|
||||
animType: "sparrow", // automatically making sparrow atlases yeah
|
||||
angle: obj.angle,
|
||||
blend: obj.blend == null ? "" : Std.string(obj.blend),
|
||||
color: obj.color.toWebString(),
|
||||
xmlData: obj.generateXML()
|
||||
}
|
||||
|
||||
if (useBitmaps)
|
||||
{
|
||||
outputData.bitmap = obj.pixels.clone();
|
||||
return outputData;
|
||||
}
|
||||
|
||||
for (name => bit in state.bitmaps)
|
||||
{
|
||||
if (areTheseBitmapsEqual(bit, obj.pixels))
|
||||
{
|
||||
outputData.assetPath = name;
|
||||
return outputData;
|
||||
}
|
||||
}
|
||||
|
||||
outputData.assetPath = "#FFFFFF";
|
||||
|
||||
return outputData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an Object based on the Data.
|
||||
* @param object Object to modify. Set to null to create a new one.
|
||||
* @param data The Data used for the Object.
|
||||
*/
|
||||
public static function fromData(object:StageEditorObject, data:StageEditorObjectData)
|
||||
{
|
||||
if (data.bitmap != null)
|
||||
{
|
||||
var bitToLoad = state.addBitmap(data.bitmap.clone());
|
||||
object.loadGraphic(state.bitmaps[bitToLoad]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data.animations != null && data.animations.length > 0) // considering we're unpacking we might as well just do this instead of switch
|
||||
{
|
||||
object.frames = flixel.graphics.frames.FlxAtlasFrames.fromSparrow(state.bitmaps[data.assetPath].clone(), data.xmlData);
|
||||
}
|
||||
else if (data.assetPath.startsWith("#"))
|
||||
{
|
||||
object.loadGraphic(getDefaultGraphic());
|
||||
object.color = FlxColor.fromString(data.assetPath);
|
||||
}
|
||||
else
|
||||
object.loadGraphic(state.bitmaps[data.assetPath].clone());
|
||||
}
|
||||
|
||||
object.name = data.name;
|
||||
object.setPosition(data.position[0], data.position[1]);
|
||||
object.zIndex = data.zIndex;
|
||||
object.antialiasing = !data.isPixel;
|
||||
object.alpha = data.alpha;
|
||||
object.danceEvery = data.danceEvery;
|
||||
object.scrollFactor.set(data.scroll[0], data.scroll[1]);
|
||||
object.startingAnimation = data.startingAnimation;
|
||||
object.angle = data.angle;
|
||||
object.blend = blendFromString(data.blend);
|
||||
if (!data.assetPath.startsWith("#")) object.color = FlxColor.fromString(data.color);
|
||||
|
||||
// yeah
|
||||
object.pixelPerfectRender = data.isPixel;
|
||||
object.pixelPerfectPosition = data.isPixel;
|
||||
|
||||
for (anim in data.animations)
|
||||
{
|
||||
object.addAnim(anim.name, anim.prefix, anim.offsets ?? [0, 0], anim.frameIndices ?? [], anim.frameRate ?? 24, anim.looped ?? false, anim.flipX ?? false,
|
||||
anim.flipY ?? false);
|
||||
}
|
||||
|
||||
if (object.animation.getNameList().contains(data.startingAnimation)) object.startingAnimation = data.startingAnimation;
|
||||
|
||||
switch (data.scale)
|
||||
{
|
||||
case Left(value):
|
||||
object.scale.set(value, value);
|
||||
|
||||
case Right(values):
|
||||
object.scale.set(values[0], values[1]);
|
||||
}
|
||||
object.updateHitbox();
|
||||
|
||||
object.playAnim(object.startingAnimation);
|
||||
|
||||
flixel.util.FlxTimer.wait(StageEditorState.TIME_BEFORE_ANIM_STOP, function() {
|
||||
if (object != null && object.animation.curAnim != null) object.animation.stop();
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a default BitmapData to be used for all the props.
|
||||
* @return BitmapData
|
||||
*/
|
||||
public static function getDefaultGraphic():BitmapData
|
||||
{
|
||||
return new FlxSprite().makeGraphic(1, 1, FlxColor.WHITE).pixels.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns OpenFL's BlendMode based on the Name.
|
||||
* @param blend the BlendMode Name.
|
||||
* @return BlendMode
|
||||
*/
|
||||
public static function blendFromString(blend:String):BlendMode
|
||||
{
|
||||
// originally this was a MASSIVE and I do mean MASSIVE switch case, though then I found out that blendmode already has one implemented
|
||||
@:privateAccess
|
||||
return BlendMode.fromString(blend.toLowerCase().trim());
|
||||
}
|
||||
|
||||
public static function generateXML(obj:StageEditorObject)
|
||||
{
|
||||
// the last check is for if the only frame is the standard graphic frame
|
||||
if (obj == null || obj.frames.frames.length == 0 || obj.frames.frames[0].name == null) return "";
|
||||
|
||||
var xml = [
|
||||
"<!--This XML File was automatically generated by StageEditorEngine, in order to make Funkin' be able to load it.-->",
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<TextureAtlas imagePath="${obj.toData(false).assetPath}.png" width="${obj.pixels.width}" height="${obj.pixels.height}">'
|
||||
].join("\n");
|
||||
|
||||
for (daFrame in obj.frames.frames)
|
||||
{
|
||||
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}"/>\n';
|
||||
}
|
||||
|
||||
xml += "</TextureAtlas>";
|
||||
return xml;
|
||||
}
|
||||
|
||||
// I am aware OpenFL has it's own compare bitmap function, though I find this to be better ngl
|
||||
static function areTheseBitmapsEqual(bitmap1:BitmapData, bitmap2:BitmapData)
|
||||
{
|
||||
if (bitmap1.width != bitmap2.width || bitmap1.height != bitmap2.height) return false;
|
||||
|
||||
for (px in 0...bitmap1.width)
|
||||
{
|
||||
for (py in 0...bitmap1.height)
|
||||
{
|
||||
if (bitmap1.getPixel32(px, py) != bitmap2.getPixel32(px, py)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
typedef StageEditorObjectData =
|
||||
{
|
||||
> StageDataProp,
|
||||
var xmlData:String;
|
||||
var ?bitmap:BitmapData;
|
||||
}
|
365
source/funkin/ui/debug/stageeditor/handlers/StageDataHandler.hx
Normal file
365
source/funkin/ui/debug/stageeditor/handlers/StageDataHandler.hx
Normal file
|
@ -0,0 +1,365 @@
|
|||
package funkin.ui.debug.stageeditor.handlers;
|
||||
|
||||
import funkin.ui.debug.stageeditor.handlers.AssetDataHandler;
|
||||
import haxe.io.Bytes;
|
||||
import funkin.util.FileUtil;
|
||||
import openfl.display.BitmapData;
|
||||
import haxe.Json;
|
||||
import haxe.zip.Entry;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.data.stage.StageData.StageDataCharacter;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import openfl.utils.Assets as OpenFLAssets;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class StageDataHandler
|
||||
{
|
||||
public static function checkForCharacter(char:BaseCharacter)
|
||||
return char != null;
|
||||
|
||||
public static function packShitToZip(state:StageEditorState)
|
||||
{
|
||||
// step 1: data
|
||||
var endData:StageData = new StageData();
|
||||
endData.name = state.stageName;
|
||||
endData.cameraZoom = state.stageZoom;
|
||||
endData.directory = state.stageFolder;
|
||||
|
||||
// step 1 phase 1: object data
|
||||
var xmlMap:Map<String, String> = [];
|
||||
|
||||
for (obj in state.spriteArray)
|
||||
{
|
||||
var data = obj.toData(false);
|
||||
endData.props.push(
|
||||
{
|
||||
name: data.name,
|
||||
assetPath: data.assetPath.startsWith("#") ? data.color : data.assetPath,
|
||||
position: data.position.copy(),
|
||||
zIndex: data.zIndex,
|
||||
isPixel: data.isPixel,
|
||||
scale: data.scale,
|
||||
alpha: data.alpha,
|
||||
danceEvery: data.danceEvery,
|
||||
scroll: data.scroll.copy(),
|
||||
animations: data.animations,
|
||||
startingAnimation: data.startingAnimation,
|
||||
animType: data.animType,
|
||||
angle: data.angle,
|
||||
blend: data.blend,
|
||||
color: data.assetPath.startsWith("#") ? "#FFFFFF" : data.color
|
||||
});
|
||||
|
||||
if (!xmlMap.exists(data.assetPath) && data.xmlData != "") xmlMap.set(data.assetPath, data.xmlData);
|
||||
}
|
||||
|
||||
// step 1 phase 2: character data
|
||||
endData.characters.bf.zIndex = state.charGroups[CharacterType.BF].zIndex;
|
||||
endData.characters.dad.zIndex = state.charGroups[CharacterType.DAD].zIndex;
|
||||
endData.characters.gf.zIndex = state.charGroups[CharacterType.GF].zIndex;
|
||||
|
||||
endData.characters.bf.scale = state.bf.scale.x / state.bf.getBaseScale();
|
||||
endData.characters.dad.scale = state.dad.scale.x / state.dad.getBaseScale();
|
||||
endData.characters.gf.scale = state.gf.scale.x / state.gf.getBaseScale();
|
||||
|
||||
endData.characters.bf.cameraOffsets = state.charCamOffsets[CharacterType.BF].copy();
|
||||
endData.characters.gf.cameraOffsets = state.charCamOffsets[CharacterType.GF].copy();
|
||||
endData.characters.dad.cameraOffsets = state.charCamOffsets[CharacterType.DAD].copy();
|
||||
|
||||
endData.characters.bf.position = [
|
||||
state.bf.feetPosition.x - state.bf.globalOffsets[0],
|
||||
state.bf.feetPosition.y - state.bf.globalOffsets[1]
|
||||
];
|
||||
|
||||
endData.characters.gf.position = [
|
||||
state.gf.feetPosition.x - state.gf.globalOffsets[0],
|
||||
state.gf.feetPosition.y - state.gf.globalOffsets[1]
|
||||
];
|
||||
|
||||
endData.characters.dad.position = [
|
||||
state.dad.feetPosition.x - state.dad.globalOffsets[0],
|
||||
state.dad.feetPosition.y - state.dad.globalOffsets[1]
|
||||
];
|
||||
|
||||
// step 2: saving everything to entryList
|
||||
var entryList = new Array<Entry>();
|
||||
|
||||
// step 2 phase 1: images
|
||||
state.removeUnusedBitmaps();
|
||||
for (name => img in state.bitmaps)
|
||||
{
|
||||
var bytes = img?.image?.encode(PNG);
|
||||
if (bytes == null) continue;
|
||||
|
||||
var entry:Entry =
|
||||
{
|
||||
fileName: name + ".png",
|
||||
fileSize: bytes.length,
|
||||
fileTime: Date.now(),
|
||||
compressed: false,
|
||||
dataSize: bytes.length,
|
||||
data: bytes,
|
||||
crc32: null // apparently fileutil.hx does not like crc32, idk why but i dont even know what crc32 is
|
||||
}
|
||||
|
||||
entryList.push(entry);
|
||||
}
|
||||
|
||||
// step 2 phase 2: xmls
|
||||
for (obj in endData.props)
|
||||
{
|
||||
if (!xmlMap.exists(obj.assetPath)) continue; // damn
|
||||
|
||||
var bytes = Bytes.ofString(xmlMap[obj.assetPath]);
|
||||
|
||||
var entry:Entry =
|
||||
{
|
||||
fileName: obj.assetPath + ".xml",
|
||||
fileSize: bytes.length,
|
||||
fileTime: Date.now(),
|
||||
compressed: false,
|
||||
dataSize: bytes.length,
|
||||
data: bytes,
|
||||
crc32: null
|
||||
}
|
||||
|
||||
entryList.push(entry);
|
||||
}
|
||||
|
||||
// step 2 phase 3: the main data
|
||||
var stageBytes = Bytes.ofString(endData.serialize());
|
||||
entryList.push(
|
||||
{
|
||||
fileName: "yourstagename.json",
|
||||
fileSize: stageBytes.length,
|
||||
fileTime: Date.now(),
|
||||
compressed: false,
|
||||
dataSize: stageBytes.length,
|
||||
data: stageBytes,
|
||||
crc32: null
|
||||
});
|
||||
|
||||
var zipFileBytes = FileUtil.createZIPFromEntries(entryList);
|
||||
return zipFileBytes;
|
||||
}
|
||||
|
||||
public static function unpackShitFromZip(state:StageEditorState, zip:Bytes)
|
||||
{
|
||||
state.clearAssets();
|
||||
state.bitmaps.clear();
|
||||
|
||||
var entries = FileUtil.readZIPFromBytes(zip);
|
||||
var stageData:StageData = new StageData();
|
||||
|
||||
var xmls:Map<String, String> = [];
|
||||
|
||||
for (stuff in entries)
|
||||
{
|
||||
var ext = stuff.fileName.split(".")[1];
|
||||
|
||||
switch (ext)
|
||||
{
|
||||
case "png":
|
||||
var data = BitmapData.fromBytes(stuff.data);
|
||||
state.bitmaps.set(stuff.fileName.replace(".png", ""), data);
|
||||
|
||||
case "xml":
|
||||
xmls.set(stuff.fileName.replace(".xml", ""), stuff.data.toString());
|
||||
|
||||
case "json":
|
||||
stageData = StageRegistry.instance.parseEntryDataRaw(stuff.data.toString(), stuff.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (stageData == null)
|
||||
{
|
||||
// TODO: throw an error, then load a dummy data
|
||||
loadDummyData(state);
|
||||
return;
|
||||
}
|
||||
|
||||
// actual data unpacking
|
||||
state.stageName = stageData.name;
|
||||
state.stageZoom = stageData.cameraZoom;
|
||||
state.stageFolder = stageData.directory ?? "shared";
|
||||
|
||||
// chars
|
||||
state.loadCharDatas(stageData);
|
||||
|
||||
// objects
|
||||
for (objData in stageData.props)
|
||||
{
|
||||
// make the data and roll with it
|
||||
var spr = new StageEditorObject();
|
||||
spr.fromData(
|
||||
{
|
||||
name: objData.name ?? "Unnamed",
|
||||
assetPath: objData.assetPath,
|
||||
animations: objData.animations.copy(),
|
||||
scale: objData.scale,
|
||||
position: objData.position,
|
||||
alpha: objData.alpha,
|
||||
angle: objData.angle,
|
||||
zIndex: objData.zIndex,
|
||||
danceEvery: objData.danceEvery,
|
||||
isPixel: objData.isPixel,
|
||||
scroll: objData.scroll.copy(),
|
||||
color: objData.color,
|
||||
blend: objData.blend,
|
||||
startingAnimation: objData.startingAnimation,
|
||||
xmlData: xmls[objData.assetPath] ?? ""
|
||||
});
|
||||
|
||||
state.add(spr);
|
||||
}
|
||||
|
||||
state.updateArray();
|
||||
state.sortAssets();
|
||||
state.updateMarkerPos();
|
||||
}
|
||||
|
||||
static function loadCharDatas(state:StageEditorState, data:StageData)
|
||||
{
|
||||
var chars = state.getCharacters();
|
||||
for (char in chars)
|
||||
{
|
||||
var charData:StageDataCharacter = null;
|
||||
|
||||
switch (char.characterType)
|
||||
{
|
||||
case CharacterType.BF:
|
||||
charData = data.characters.bf;
|
||||
case CharacterType.GF:
|
||||
charData = data.characters.gf;
|
||||
case CharacterType.DAD:
|
||||
charData = data.characters.dad;
|
||||
default: // nothing rip
|
||||
}
|
||||
|
||||
char.resetCharacter(true);
|
||||
|
||||
if (charData == null) continue;
|
||||
|
||||
char.x = charData.position[0] - char.characterOrigin.x + char.globalOffsets[0];
|
||||
char.y = charData.position[1] - char.characterOrigin.y + char.globalOffsets[1];
|
||||
state.charGroups[char.characterType].zIndex = charData.zIndex;
|
||||
|
||||
char.setScale(char.getBaseScale() * charData.scale);
|
||||
char.cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
char.cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
|
||||
state.charCamOffsets[char.characterType] = charData.cameraOffsets.copy();
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadFromDataRaw(state:StageEditorState, data:StageData)
|
||||
{
|
||||
state.clearAssets();
|
||||
state.bitmaps.clear();
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
loadDummyData(state);
|
||||
return;
|
||||
}
|
||||
@:privateAccess
|
||||
if (!LimeAssets.libraryPaths.exists(data.directory))
|
||||
{
|
||||
loadDummyData(state);
|
||||
return;
|
||||
}
|
||||
|
||||
Paths.setCurrentLevel(data.directory);
|
||||
|
||||
if (OpenFLAssets.getLibrary(data.directory) == null)
|
||||
{
|
||||
OpenFLAssets.loadLibrary(data.directory).onComplete(function(_) {
|
||||
loadFromDataRaw(state, data);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
state.stageName = data.name;
|
||||
state.stageZoom = data.cameraZoom;
|
||||
state.stageFolder = data.directory ?? "shared";
|
||||
|
||||
state.loadCharDatas(data);
|
||||
|
||||
for (objData in data.props)
|
||||
{
|
||||
var spr = new StageEditorObject();
|
||||
if (!objData.assetPath.startsWith("#")) state.bitmaps.set(objData.assetPath, Assets.getBitmapData(Paths.image(objData.assetPath)));
|
||||
|
||||
spr.fromData(
|
||||
{
|
||||
name: objData.name ?? "Unnamed",
|
||||
assetPath: objData.assetPath,
|
||||
animations: objData.animations.copy(),
|
||||
scale: objData.scale,
|
||||
position: objData.position,
|
||||
alpha: objData.alpha,
|
||||
angle: objData.angle,
|
||||
zIndex: objData.zIndex,
|
||||
danceEvery: objData.danceEvery,
|
||||
isPixel: objData.isPixel,
|
||||
scroll: objData.scroll.copy(),
|
||||
color: objData.color,
|
||||
blend: objData.blend,
|
||||
startingAnimation: objData.startingAnimation,
|
||||
xmlData: Assets.exists(Paths.file("images/" + objData.assetPath + ".xml")) ? Assets.getText(Paths.file("images/" + objData.assetPath + ".xml")) : ""
|
||||
});
|
||||
|
||||
state.add(spr);
|
||||
}
|
||||
|
||||
state.updateArray();
|
||||
state.sortAssets();
|
||||
state.updateMarkerPos();
|
||||
}
|
||||
|
||||
public static function loadDummyData(state:StageEditorState)
|
||||
{
|
||||
state.clearAssets();
|
||||
|
||||
state.stageName = "Unnamed";
|
||||
state.stageZoom = 1.0;
|
||||
state.stageFolder = "shared";
|
||||
|
||||
state.charCamOffsets = StageEditorState.DEFAULT_CAMERA_OFFSETS.copy();
|
||||
state.charPos = StageEditorState.DEFAULT_POSITIONS.copy();
|
||||
|
||||
state.gf.resetCharacter(true);
|
||||
state.dad.resetCharacter(true);
|
||||
state.bf.resetCharacter(true);
|
||||
|
||||
state.charGroups[CharacterType.BF].zIndex = 300;
|
||||
state.charGroups[CharacterType.DAD].zIndex = 200;
|
||||
state.charGroups[CharacterType.GF].zIndex = 100;
|
||||
|
||||
state.gf.x = state.charPos[CharacterType.GF][0] - state.gf.characterOrigin.x + state.gf.globalOffsets[0];
|
||||
state.gf.y = state.charPos[CharacterType.GF][1] - state.gf.characterOrigin.y + state.gf.globalOffsets[1];
|
||||
state.dad.x = state.charPos[CharacterType.DAD][0] - state.dad.characterOrigin.x + state.dad.globalOffsets[0];
|
||||
state.dad.y = state.charPos[CharacterType.DAD][1] - state.dad.characterOrigin.y + state.dad.globalOffsets[1];
|
||||
state.bf.x = state.charPos[CharacterType.BF][0] - state.bf.characterOrigin.x + state.bf.globalOffsets[0];
|
||||
state.bf.y = state.charPos[CharacterType.BF][1] - state.bf.characterOrigin.y + state.bf.globalOffsets[1];
|
||||
|
||||
state.gf.setScale(state.gf.getBaseScale());
|
||||
state.dad.setScale(state.dad.getBaseScale());
|
||||
state.bf.setScale(state.bf.getBaseScale());
|
||||
|
||||
state.gf.cameraFocusPoint.x += state.charCamOffsets[CharacterType.GF][0];
|
||||
state.gf.cameraFocusPoint.y += state.charCamOffsets[CharacterType.GF][1];
|
||||
state.dad.cameraFocusPoint.x += state.charCamOffsets[CharacterType.DAD][0];
|
||||
state.dad.cameraFocusPoint.y += state.charCamOffsets[CharacterType.DAD][1];
|
||||
state.bf.cameraFocusPoint.x += state.charCamOffsets[CharacterType.BF][0];
|
||||
state.bf.cameraFocusPoint.y += state.charCamOffsets[CharacterType.BF][1];
|
||||
|
||||
// no props :p
|
||||
|
||||
state.updateMarkerPos();
|
||||
}
|
||||
}
|
191
source/funkin/ui/debug/stageeditor/handlers/UndoRedoHandler.hx
Normal file
191
source/funkin/ui/debug/stageeditor/handlers/UndoRedoHandler.hx
Normal file
|
@ -0,0 +1,191 @@
|
|||
package funkin.ui.debug.stageeditor.handlers;
|
||||
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.ui.debug.stageeditor.handlers.AssetDataHandler.StageEditorObjectData;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorDialogType;
|
||||
|
||||
class UndoRedoHandler
|
||||
{
|
||||
public static function performLastAction(state:StageEditorState, redo:Bool = false)
|
||||
{
|
||||
if (state == null || (state.undoArray.length <= 0 && !redo) || (state.redoArray.length <= 0 && redo)) return;
|
||||
var actionToDo = redo ? state.redoArray.pop() : state.undoArray.pop();
|
||||
|
||||
switch (actionToDo.type)
|
||||
{
|
||||
case CHARACTER_MOVED:
|
||||
createAndPushAction(state, actionToDo.type, !redo);
|
||||
|
||||
var type = actionToDo.data.type == null ? CharacterType.BF : actionToDo.data.type;
|
||||
var pos = actionToDo.data.pos == null ? [0, 0] : actionToDo.data.pos;
|
||||
|
||||
for (char in state.getCharacters())
|
||||
{
|
||||
if (char.characterType == type) state.selectedChar = char;
|
||||
}
|
||||
|
||||
state.selectedChar.x = pos[0] - state.selectedChar.characterOrigin.x + state.selectedChar.globalOffsets[0];
|
||||
state.selectedChar.y = pos[1] - state.selectedChar.characterOrigin.y + state.selectedChar.globalOffsets[1];
|
||||
|
||||
state.updateMarkerPos();
|
||||
state.updateDialog(StageEditorDialogType.CHARACTER);
|
||||
|
||||
case OBJECT_MOVED:
|
||||
var id = actionToDo.data.ID ?? -1;
|
||||
var pos = actionToDo.data.pos ?? [0, 0];
|
||||
|
||||
for (obj in state.spriteArray)
|
||||
{
|
||||
if (obj.ID == id) state.selectedSprite = obj;
|
||||
}
|
||||
|
||||
if (state.selectedSprite != null)
|
||||
{
|
||||
createAndPushAction(state, actionToDo.type, !redo);
|
||||
|
||||
state.selectedSprite.x = pos[0];
|
||||
state.selectedSprite.y = pos[1];
|
||||
|
||||
state.updateDialog(StageEditorDialogType.OBJECT);
|
||||
}
|
||||
|
||||
case OBJECT_CREATED: // this removes the object
|
||||
var id = actionToDo.data.ID ?? -1;
|
||||
|
||||
for (obj in state.spriteArray)
|
||||
{
|
||||
if (obj.ID == id)
|
||||
{
|
||||
state.selectedSprite = obj;
|
||||
createAndPushAction(state, OBJECT_DELETED, !redo);
|
||||
|
||||
state.selectedSprite = null;
|
||||
|
||||
obj.kill();
|
||||
state.remove(obj, true);
|
||||
obj.destroy();
|
||||
|
||||
state.updateArray();
|
||||
state.updateDialog(StageEditorDialogType.OBJECT);
|
||||
trace("found object");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
case OBJECT_DELETED: // this creates the object
|
||||
if (actionToDo.data.data == null) return;
|
||||
|
||||
var id = actionToDo.data.ID ?? -1;
|
||||
var data:StageEditorObjectData = cast actionToDo.data.data;
|
||||
|
||||
var obj = new StageEditorObject().fromData(data);
|
||||
obj.ID = id;
|
||||
state.selectedSprite = obj;
|
||||
|
||||
createAndPushAction(state, OBJECT_CREATED, !redo);
|
||||
state.add(obj);
|
||||
|
||||
state.updateDialog(StageEditorDialogType.OBJECT);
|
||||
state.updateArray();
|
||||
|
||||
case OBJECT_ROTATED: // primarily copied from OBJECT_MOVED
|
||||
var id = actionToDo.data.ID ?? -1;
|
||||
var angle = actionToDo.data.angle ?? 0;
|
||||
|
||||
for (obj in state.spriteArray)
|
||||
{
|
||||
if (obj.ID == id) state.selectedSprite = obj;
|
||||
}
|
||||
|
||||
if (state.selectedSprite != null)
|
||||
{
|
||||
createAndPushAction(state, actionToDo.type, !redo);
|
||||
state.selectedSprite.angle = angle;
|
||||
state.updateDialog(StageEditorDialogType.OBJECT);
|
||||
}
|
||||
|
||||
default: // do nothing dumbass
|
||||
}
|
||||
}
|
||||
|
||||
public static function createAndPushAction(state:StageEditorState, action:UndoActionType, redo:Bool = false)
|
||||
{
|
||||
if (state == null) return;
|
||||
|
||||
var finalAction:UndoAction = {type: action, data: null};
|
||||
|
||||
if (!redo && state.redoArray.length > 0) state.redoArray = []; // incorporate resetting as well
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case CHARACTER_MOVED:
|
||||
var char = state.selectedChar.characterType;
|
||||
finalAction.data = {type: char, pos: state.charPos[char].copy()};
|
||||
|
||||
case OBJECT_MOVED:
|
||||
finalAction.data = {ID: state.selectedSprite.ID, pos: [state.selectedSprite.x, state.selectedSprite.y]}
|
||||
|
||||
case OBJECT_CREATED:
|
||||
finalAction.data = {ID: state.selectedSprite.ID}
|
||||
|
||||
case OBJECT_DELETED:
|
||||
finalAction.data =
|
||||
{
|
||||
ID: state.selectedSprite.ID,
|
||||
data: state.selectedSprite.toData(true)
|
||||
}
|
||||
|
||||
case OBJECT_ROTATED:
|
||||
finalAction.data = {ID: state.selectedSprite.ID, angle: state.selectedSprite.angle}
|
||||
|
||||
default: // nop
|
||||
}
|
||||
|
||||
if (finalAction.data == null) return;
|
||||
|
||||
if (redo) state.redoArray.push(finalAction);
|
||||
else if (!redo) state.undoArray.push(finalAction);
|
||||
}
|
||||
}
|
||||
|
||||
typedef UndoAction =
|
||||
{
|
||||
/**
|
||||
* The Type of Undo Action to store.
|
||||
*/
|
||||
var type:UndoActionType;
|
||||
|
||||
/**
|
||||
* The added Data of the Action.
|
||||
*/
|
||||
var data:Dynamic;
|
||||
}
|
||||
|
||||
enum abstract UndoActionType(String) from String
|
||||
{
|
||||
/**
|
||||
* Triggerred when an Object is deleted.
|
||||
*/
|
||||
var OBJECT_DELETED = "object_deleted";
|
||||
|
||||
/**
|
||||
* Triggerred when an Object is created.
|
||||
*/
|
||||
var OBJECT_CREATED = "object_created";
|
||||
|
||||
/**
|
||||
* Triggerred when an Object is moved.
|
||||
*/
|
||||
var OBJECT_MOVED = "object_moved";
|
||||
|
||||
/**
|
||||
* Triggerred when a Character is moved.
|
||||
*/
|
||||
var CHARACTER_MOVED = "character_moved";
|
||||
|
||||
/**
|
||||
* Triggerred when an Object is rotated.
|
||||
*/
|
||||
var OBJECT_ROTATED = "object_rotated";
|
||||
}
|
7
source/funkin/ui/debug/stageeditor/import.hx
Normal file
7
source/funkin/ui/debug/stageeditor/import.hx
Normal file
|
@ -0,0 +1,7 @@
|
|||
package funkin.ui.debug.stageeditor;
|
||||
|
||||
#if !macro
|
||||
using funkin.ui.debug.stageeditor.handlers.StageDataHandler;
|
||||
using funkin.ui.debug.stageeditor.handlers.AssetDataHandler;
|
||||
using funkin.ui.debug.stageeditor.handlers.UndoRedoHandler;
|
||||
#end
|
|
@ -0,0 +1,242 @@
|
|||
package funkin.ui.debug.stageeditor.toolboxes;
|
||||
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
import haxe.ui.data.ArrayDataSource;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.Label;
|
||||
import funkin.ui.debug.stageeditor.handlers.StageDataHandler;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import haxe.ui.core.Screen;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxEase;
|
||||
import haxe.ui.containers.Grid;
|
||||
import funkin.play.character.CharacterData;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/toolboxes/character-properties.xml"))
|
||||
class StageEditorCharacterToolbox extends StageEditorDefaultToolbox
|
||||
{
|
||||
var characterPosXStepper:NumberStepper;
|
||||
var characterPosYStepper:NumberStepper;
|
||||
var characterPosReset:Button;
|
||||
|
||||
var characterZIdxStepper:NumberStepper;
|
||||
var characterZIdxReset:Button;
|
||||
|
||||
var characterCamXStepper:NumberStepper;
|
||||
var characterCamYStepper:NumberStepper;
|
||||
var characterCamReset:Button;
|
||||
|
||||
var characterScaleSlider:Slider;
|
||||
var characterScaleReset:Button;
|
||||
|
||||
var characterTypeButton:Button;
|
||||
var charMenu:StageEditorCharacterMenu;
|
||||
|
||||
override public function new(state:StageEditorState)
|
||||
{
|
||||
super(state);
|
||||
|
||||
// position
|
||||
characterPosXStepper.onChange = characterPosYStepper.onChange = function(_) {
|
||||
repositionCharacter();
|
||||
state.saved = false;
|
||||
}
|
||||
|
||||
characterPosReset.onClick = function(_) {
|
||||
if (!StageEditorState.DEFAULT_POSITIONS.exists(state.selectedChar.characterType)) return;
|
||||
|
||||
var oldPositions = StageEditorState.DEFAULT_POSITIONS[state.selectedChar.characterType];
|
||||
characterPosXStepper.pos = oldPositions[0];
|
||||
characterPosYStepper.pos = oldPositions[1];
|
||||
}
|
||||
|
||||
// zidx
|
||||
characterZIdxStepper.max = StageEditorState.MAX_Z_INDEX;
|
||||
characterZIdxStepper.onChange = function(_) {
|
||||
state.charGroups[state.selectedChar.characterType].zIndex = Std.int(characterZIdxStepper.pos);
|
||||
state.saved = false;
|
||||
state.sortAssets();
|
||||
}
|
||||
|
||||
characterZIdxReset.onClick = function(_) {
|
||||
var thingies = [CharacterType.GF, CharacterType.DAD, CharacterType.BF];
|
||||
var thingIdxies = thingies.indexOf(state.selectedChar.characterType);
|
||||
|
||||
characterZIdxStepper.pos = (thingIdxies * 100);
|
||||
}
|
||||
|
||||
// camera
|
||||
characterCamXStepper.onChange = characterCamYStepper.onChange = function(_) {
|
||||
state.charCamOffsets[state.selectedChar.characterType] = [characterCamXStepper.pos, characterCamYStepper.pos];
|
||||
state.updateMarkerPos();
|
||||
state.saved = false;
|
||||
}
|
||||
|
||||
characterCamReset.onClick = function(_) characterCamXStepper.pos = characterCamYStepper.pos = 0; // lol
|
||||
|
||||
// scale
|
||||
characterScaleSlider.onChange = function(_) {
|
||||
state.selectedChar.setScale(state.selectedChar.getBaseScale() * characterScaleSlider.pos);
|
||||
repositionCharacter();
|
||||
state.saved = false;
|
||||
}
|
||||
|
||||
characterScaleReset.onChange = function(_) characterScaleSlider.pos = 1;
|
||||
|
||||
// character button
|
||||
characterTypeButton.onClick = function(_) {
|
||||
charMenu = new StageEditorCharacterMenu(state, this);
|
||||
Screen.instance.addComponent(charMenu);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
override public function refresh()
|
||||
{
|
||||
var name = stageEditorState.selectedChar.characterType;
|
||||
|
||||
characterPosXStepper.step = characterPosYStepper.step = stageEditorState.moveStep;
|
||||
characterCamXStepper.step = characterCamYStepper.step = stageEditorState.moveStep;
|
||||
|
||||
if (characterPosXStepper.pos != stageEditorState.charPos[name][0]) characterPosXStepper.pos = stageEditorState.charPos[name][0];
|
||||
if (characterPosYStepper.pos != stageEditorState.charPos[name][1]) characterPosYStepper.pos = stageEditorState.charPos[name][1];
|
||||
|
||||
if (characterZIdxStepper.pos != stageEditorState.charGroups[stageEditorState.selectedChar.characterType].zIndex)
|
||||
characterZIdxStepper.pos = stageEditorState.charGroups[stageEditorState.selectedChar.characterType].zIndex;
|
||||
|
||||
if (characterCamXStepper.pos != stageEditorState.charCamOffsets[name][0]) characterCamXStepper.pos = stageEditorState.charCamOffsets[name][0];
|
||||
if (characterCamYStepper.pos != stageEditorState.charCamOffsets[name][1]) characterCamYStepper.pos = stageEditorState.charCamOffsets[name][1];
|
||||
|
||||
if (characterScaleSlider.pos != stageEditorState.selectedChar.scale.x / stageEditorState.selectedChar.getBaseScale())
|
||||
characterScaleSlider.pos = stageEditorState.selectedChar.scale.x / stageEditorState.selectedChar.getBaseScale();
|
||||
|
||||
var prevText = characterTypeButton.text;
|
||||
|
||||
var charData = CharacterDataParser.fetchCharacterData(stageEditorState.selectedChar.characterId);
|
||||
characterTypeButton.icon = (charData == null ? null : CharacterDataParser.getCharPixelIconAsset(stageEditorState.selectedChar.characterId));
|
||||
characterTypeButton.text = (charData == null ? "None" : charData.name.length > 6 ? '${charData.name.substr(0, 6)}.' : '${charData.name}');
|
||||
|
||||
if (prevText != characterTypeButton.text)
|
||||
{
|
||||
Screen.instance.removeComponent(charMenu);
|
||||
}
|
||||
}
|
||||
|
||||
public function repositionCharacter()
|
||||
{
|
||||
stageEditorState.selectedChar.x = characterPosXStepper.pos - stageEditorState.selectedChar.characterOrigin.x
|
||||
+ stageEditorState.selectedChar.globalOffsets[0];
|
||||
stageEditorState.selectedChar.y = characterPosYStepper.pos - stageEditorState.selectedChar.characterOrigin.y
|
||||
+ stageEditorState.selectedChar.globalOffsets[1];
|
||||
|
||||
stageEditorState.selectedChar.setScale(stageEditorState.selectedChar.getBaseScale() * characterScaleSlider.pos);
|
||||
|
||||
stageEditorState.updateMarkerPos();
|
||||
}
|
||||
}
|
||||
|
||||
@:xml('
|
||||
<menu id="iconSelector" width="410" height="185" padding="8">
|
||||
<vbox width="100%" height="100%">
|
||||
<scrollview id="charSelectScroll" width="390" height="150" contentWidth="100%" />
|
||||
<label id="charIconName" text="(choose a character)" />
|
||||
</vbox>
|
||||
</menu>
|
||||
')
|
||||
class StageEditorCharacterMenu extends Menu // copied from chart editor
|
||||
{
|
||||
override public function new(state:StageEditorState, parent:StageEditorCharacterToolbox)
|
||||
{
|
||||
super();
|
||||
|
||||
this.x = Screen.instance.currentMouseX;
|
||||
this.y = Screen.instance.currentMouseY;
|
||||
|
||||
var charGrid = new Grid();
|
||||
charGrid.columns = 5;
|
||||
charGrid.width = this.width;
|
||||
charSelectScroll.addComponent(charGrid);
|
||||
|
||||
var charIds = CharacterDataParser.listCharacterIds();
|
||||
charIds.sort(SortUtil.alphabetically);
|
||||
|
||||
var defaultText:String = '(choose a character)';
|
||||
|
||||
for (charIndex => charId in charIds)
|
||||
{
|
||||
var charData:CharacterData = CharacterDataParser.fetchCharacterData(charId);
|
||||
|
||||
var charButton = new haxe.ui.components.Button();
|
||||
charButton.width = 70;
|
||||
charButton.height = 70;
|
||||
charButton.padding = 8;
|
||||
charButton.iconPosition = "top";
|
||||
|
||||
if (charId == state.selectedChar.characterId)
|
||||
{
|
||||
// Scroll to the character if it is already selected.
|
||||
charSelectScroll.hscrollPos = Math.floor(charIndex / 5) * 80;
|
||||
charButton.selected = true;
|
||||
|
||||
defaultText = '${charData.name} [${charId}]';
|
||||
}
|
||||
|
||||
var LIMIT = 6;
|
||||
charButton.icon = CharacterDataParser.getCharPixelIconAsset(charId);
|
||||
charButton.text = charData.name.length > LIMIT ? '${charData.name.substr(0, LIMIT)}.' : '${charData.name}';
|
||||
|
||||
charButton.onClick = _ -> {
|
||||
var type = state.selectedChar.characterType;
|
||||
if (state.selectedChar.characterId == charId) return; // saves on memory
|
||||
|
||||
var group = state.charGroups[type];
|
||||
group.killMembers();
|
||||
for (member in group.members)
|
||||
{
|
||||
member.kill();
|
||||
group.remove(member, true);
|
||||
member.destroy();
|
||||
}
|
||||
group.clear();
|
||||
|
||||
// okay i think that was enough cleaning phew you can see how clean this group is now!!!
|
||||
// anyways new character!!!!
|
||||
|
||||
var newChar = CharacterDataParser.fetchCharacter(charId, true);
|
||||
newChar.characterType = type;
|
||||
|
||||
newChar.resetCharacter(true);
|
||||
newChar.flipX = type == CharacterType.BF ? !newChar.getDataFlipX() : newChar.getDataFlipX();
|
||||
|
||||
state.selectedChar = newChar;
|
||||
group.add(newChar);
|
||||
|
||||
parent.repositionCharacter();
|
||||
};
|
||||
|
||||
charButton.onMouseOver = _ -> {
|
||||
charIconName.text = '${charData.name} [${charId}]';
|
||||
};
|
||||
charButton.onMouseOut = _ -> {
|
||||
charIconName.text = defaultText;
|
||||
};
|
||||
charGrid.addComponent(charButton);
|
||||
}
|
||||
|
||||
charIconName.text = defaultText;
|
||||
|
||||
this.alpha = 0;
|
||||
this.y -= 10;
|
||||
FlxTween.tween(this, {alpha: 1, y: this.y + 10}, 0.2, {ease: FlxEase.quartOut});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package funkin.ui.debug.stageeditor.toolboxes;
|
||||
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import funkin.audio.FunkinSound;
|
||||
|
||||
@:access(funkin.ui.debug.stageeditor.StageEditorState)
|
||||
class StageEditorDefaultToolbox extends CollapsibleDialog
|
||||
{
|
||||
var stageEditorState:StageEditorState;
|
||||
|
||||
public var dialogVisible:Bool = false;
|
||||
|
||||
private function new(stageEditorState:StageEditorState)
|
||||
{
|
||||
super();
|
||||
|
||||
this.stageEditorState = stageEditorState;
|
||||
|
||||
closable = false;
|
||||
modal = true;
|
||||
destroyOnClose = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the Sound and Visibility
|
||||
* @param on
|
||||
*/
|
||||
public function toggle(on:Bool)
|
||||
{
|
||||
if (!dialogVisible && on) FunkinSound.playOnce(Paths.sound('chartingSounds/openWindow'));
|
||||
else if (dialogVisible && !on) FunkinSound.playOnce(Paths.sound('chartingSounds/exitWindow'));
|
||||
|
||||
if (on) showDialog(false);
|
||||
else
|
||||
hide();
|
||||
|
||||
dialogVisible = on;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to implement this.
|
||||
*/
|
||||
public function refresh() {}
|
||||
}
|
|
@ -0,0 +1,581 @@
|
|||
package funkin.ui.debug.stageeditor.toolboxes;
|
||||
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.components.TextArea;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.Image;
|
||||
import haxe.ui.containers.dialogs.Dialogs.FileDialogTypes;
|
||||
import haxe.ui.ToolkitAssets;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import funkin.ui.debug.stageeditor.handlers.AssetDataHandler;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.containers.ListView;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.components.Switch;
|
||||
import flixel.util.FlxTimer;
|
||||
import haxe.ui.data.ArrayDataSource;
|
||||
import haxe.ui.events.ItemEvent;
|
||||
import haxe.ui.components.ColorPicker;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.ui.util.Color;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.animation.FlxAnimation;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.ui.debug.stageeditor.components.LoadFromUrlDialog;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/toolboxes/object-properties.xml"))
|
||||
class StageEditorObjectToolbox extends StageEditorDefaultToolbox
|
||||
{
|
||||
var linkedObject:StageEditorObject = null;
|
||||
|
||||
var objectImagePreview:Image;
|
||||
var objectLoadImageButton:Button;
|
||||
var objectLoadInternetButton:Button;
|
||||
var objectDownloadImageButton:Button;
|
||||
var objectResetImageButton:Button;
|
||||
var objectZIdxStepper:NumberStepper;
|
||||
var objectZIdxReset:Button;
|
||||
|
||||
var objectPosXStepper:NumberStepper;
|
||||
var objectPosYStepper:NumberStepper;
|
||||
var objectPosResetButton:Button;
|
||||
var objectAlphaSlider:HorizontalSlider;
|
||||
var objectAlphaResetButton:Button;
|
||||
var objectAngleSlider:HorizontalSlider;
|
||||
var objectAngleResetButton:Button;
|
||||
var objectScaleXStepper:NumberStepper;
|
||||
var objectScaleYStepper:NumberStepper;
|
||||
var objectScaleResetButton:Button;
|
||||
var objectSizeXStepper:NumberStepper;
|
||||
var objectSizeYStepper:NumberStepper;
|
||||
var objectSizeResetButton:Button;
|
||||
var objectScrollXSlider:HorizontalSlider;
|
||||
var objectScrollYSlider:HorizontalSlider;
|
||||
var objectScrollResetButton:Button;
|
||||
|
||||
var objectFrameText:TextArea;
|
||||
var objectFrameTextLoad:Button;
|
||||
var objectFrameTextSparrow:Button;
|
||||
var objectFrameTextPacker:Button;
|
||||
var objectFrameImageWidth:NumberStepper;
|
||||
var objectFrameImageHeight:NumberStepper;
|
||||
var objectFrameImageSetter:Button;
|
||||
var objectFrameReset:Button;
|
||||
|
||||
var objectAnimDropdown:DropDown;
|
||||
var objectAnimName:TextField;
|
||||
var objectAnimFrameList:ListView;
|
||||
var objectAnimPrefix:TextField;
|
||||
var objectAnimFrames:TextField;
|
||||
var objectAnimLooped:CheckBox;
|
||||
var objectAnimFlipX:CheckBox;
|
||||
var objectAnimFlipY:CheckBox;
|
||||
var objectAnimFramerate:NumberStepper;
|
||||
var objectAnimOffsetX:NumberStepper;
|
||||
var objectAnimOffsetY:NumberStepper;
|
||||
var objectAnimDanceBeat:NumberStepper;
|
||||
var objectAnimDanceBeatReset:Button;
|
||||
var objectAnimStart:TextField;
|
||||
var objectAnimStartReset:Button;
|
||||
|
||||
var objectMiscAntialias:CheckBox;
|
||||
var objectMiscAntialiasReset:Button;
|
||||
var objectMiscFlipReset:Button;
|
||||
var objectMiscBlendDrop:DropDown;
|
||||
var objectMiscBlendReset:Button;
|
||||
var objectMiscColor:ColorPicker;
|
||||
var objectMiscColorReset:Button;
|
||||
|
||||
override public function new(state:StageEditorState)
|
||||
{
|
||||
super(state);
|
||||
|
||||
// basic callbacks
|
||||
objectLoadImageButton.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
Dialogs.openBinaryFile("Open Image File", FileDialogTypes.IMAGES, function(selectedFile) {
|
||||
if (selectedFile == null) return;
|
||||
|
||||
objectImagePreview.resource = null;
|
||||
|
||||
ToolkitAssets.instance.imageFromBytes(selectedFile.bytes, function(imageInfo) {
|
||||
if (imageInfo == null) return;
|
||||
|
||||
objectImagePreview.resource = imageInfo.data;
|
||||
|
||||
linkedObject.frame = imageInfo.data;
|
||||
|
||||
var bit = linkedObject.updateFramePixels();
|
||||
var bitToLoad = state.addBitmap(bit);
|
||||
|
||||
linkedObject.loadGraphic(state.bitmaps[bitToLoad]);
|
||||
linkedObject.updateHitbox();
|
||||
|
||||
// update size stuff
|
||||
objectSizeXStepper.pos = linkedObject.width;
|
||||
objectSizeYStepper.pos = linkedObject.height;
|
||||
|
||||
// remove unused bitmaps
|
||||
state.removeUnusedBitmaps();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
objectLoadInternetButton.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
state.createURLDialog(function(bytes:lime.utils.Bytes) {
|
||||
linkedObject.loadGraphic(BitmapData.fromBytes(bytes));
|
||||
linkedObject.updateHitbox();
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
objectDownloadImageButton.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
FileUtil.saveFile(linkedObject.pixels.image.encode(PNG), [FileUtil.FILE_FILTER_PNG], null, null,
|
||||
linkedObject.name + "-graphic.png"); // i'on need any callbacks
|
||||
}
|
||||
|
||||
objectZIdxStepper.max = StageEditorState.MAX_Z_INDEX;
|
||||
objectZIdxStepper.onChange = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.zIndex = Std.int(objectZIdxStepper.pos);
|
||||
state.sortAssets();
|
||||
}
|
||||
}
|
||||
|
||||
// numeric callbacks
|
||||
objectPosXStepper.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.x = objectPosXStepper.pos;
|
||||
};
|
||||
|
||||
objectPosYStepper.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.y = objectPosYStepper.pos;
|
||||
};
|
||||
|
||||
objectAlphaSlider.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.alpha = objectAlphaSlider.pos;
|
||||
};
|
||||
|
||||
objectAngleSlider.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.angle = objectAngleSlider.pos;
|
||||
};
|
||||
|
||||
objectScaleXStepper.onChange = objectScaleYStepper.onChange = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.scale.set(objectScaleXStepper.pos, objectScaleYStepper.pos);
|
||||
linkedObject.updateHitbox();
|
||||
objectSizeXStepper.pos = linkedObject.width;
|
||||
objectSizeYStepper.pos = linkedObject.height;
|
||||
|
||||
linkedObject.playAnim(linkedObject.animation.name); // load offsets
|
||||
}
|
||||
};
|
||||
|
||||
objectSizeXStepper.onChange = objectSizeYStepper.onChange = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.setGraphicSize(Std.int(objectSizeXStepper.pos), Std.int(objectSizeYStepper.pos));
|
||||
linkedObject.updateHitbox();
|
||||
objectScaleXStepper.pos = linkedObject.scale.x;
|
||||
objectScaleYStepper.pos = linkedObject.scale.y;
|
||||
|
||||
linkedObject.playAnim(linkedObject.animation.name); // load offsets
|
||||
}
|
||||
};
|
||||
|
||||
objectScrollXSlider.onChange = objectScrollYSlider.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.scrollFactor.set(objectScrollXSlider.pos, objectScrollYSlider.pos);
|
||||
};
|
||||
|
||||
// frame callbacks
|
||||
objectFrameTextLoad.onClick = function(_) {
|
||||
Dialogs.openTextFile("Open Text File", FileDialogTypes.TEXTS, function(selectedFile) {
|
||||
if (selectedFile.text == null || (!selectedFile.name.endsWith(".xml") && !selectedFile.name.endsWith(".txt"))) return;
|
||||
|
||||
objectFrameText.text = selectedFile.text;
|
||||
|
||||
state.notifyChange("Frame Text Loaded", "The Text File " + selectedFile.name + " has been loaded.");
|
||||
});
|
||||
}
|
||||
|
||||
objectFrameTextSparrow.onClick = function(_) {
|
||||
if (linkedObject == null || objectFrameText.text == null || objectFrameText.text == "") return;
|
||||
|
||||
try
|
||||
{
|
||||
linkedObject.frames = FlxAtlasFrames.fromSparrow(linkedObject.graphic, objectFrameText.text);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
state.notifyChange("Frame Setup Error", e.toString(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// might as well clear animations because frames SUCK
|
||||
linkedObject.animDatas.clear();
|
||||
linkedObject.animation.destroyAnimations();
|
||||
linkedObject.updateHitbox();
|
||||
refresh();
|
||||
|
||||
state.notifyChange("Frame Setup Done", "Finished the Sparrow Frame Setup for the Object " + linkedObject.name + ".");
|
||||
}
|
||||
|
||||
objectFrameTextPacker.onClick = function(_) {
|
||||
if (linkedObject == null || objectFrameText.text == null || objectFrameText.text == "") return;
|
||||
|
||||
try // crash prevention
|
||||
{
|
||||
linkedObject.frames = FlxAtlasFrames.fromSpriteSheetPacker(linkedObject.graphic, objectFrameText.text);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
state.notifyChange("Frame Setup Error", e.toString(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// might as well clear animations because frames SUCK
|
||||
linkedObject.animDatas.clear();
|
||||
linkedObject.animation.destroyAnimations();
|
||||
|
||||
linkedObject.updateHitbox();
|
||||
refresh();
|
||||
|
||||
state.notifyChange("Frame Setup Done", "Finished the Packer Frame Setup for the Object " + linkedObject.name + ".");
|
||||
}
|
||||
|
||||
objectFrameImageSetter.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
linkedObject.loadGraphic(linkedObject.graphic, true, Std.int(objectFrameImageWidth.pos), Std.int(objectFrameImageHeight.pos));
|
||||
linkedObject.updateHitbox();
|
||||
|
||||
// set da names
|
||||
for (i in 0...linkedObject.frames.frames.length)
|
||||
{
|
||||
linkedObject.frames.framesHash.set("Frame" + i, linkedObject.frames.frames[i]);
|
||||
linkedObject.frames.frames[i].name = "Frame" + i;
|
||||
}
|
||||
|
||||
// might as well clear animations because frames SUCK
|
||||
linkedObject.animDatas.clear();
|
||||
linkedObject.animation.destroyAnimations();
|
||||
refresh();
|
||||
|
||||
state.notifyChange("Frame Setup Done", "Finished the Image Frame Setup for the Object " + linkedObject.name + ".");
|
||||
}
|
||||
|
||||
// animation
|
||||
objectAnimDropdown.onChange = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
if (objectAnimDropdown.selectedIndex == -1) // RESET EVERYTHING INSTANTENEOUSLY
|
||||
{
|
||||
objectAnimName.text = "";
|
||||
objectAnimLooped.selected = objectAnimFlipX.selected = objectAnimFlipY.selected = false;
|
||||
objectAnimFramerate.pos = 24;
|
||||
objectAnimOffsetX.pos = objectAnimOffsetY.pos = 0;
|
||||
objectAnimFrames.text = "";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var animData = linkedObject.animDatas[objectAnimDropdown.selectedItem.text];
|
||||
if (animData == null) return;
|
||||
|
||||
objectAnimName.text = objectAnimDropdown.selectedItem.text;
|
||||
objectAnimPrefix.text = animData.prefix ?? "";
|
||||
objectAnimFrames.text = (animData.frameIndices != null && animData.frameIndices.length > 0 ? animData.frameIndices.join(", ") : "");
|
||||
|
||||
objectAnimLooped.selected = animData.looped ?? false;
|
||||
objectAnimFlipX.selected = animData.flipX ?? false;
|
||||
objectAnimFlipY.selected = animData.flipY ?? false;
|
||||
objectAnimFramerate.pos = animData.frameRate ?? 24;
|
||||
|
||||
objectAnimOffsetX.pos = (animData.offsets != null && animData.offsets.length == 2 ? animData.offsets[0] : 0);
|
||||
objectAnimOffsetY.pos = (animData.offsets != null && animData.offsets.length == 2 ? animData.offsets[1] : 0);
|
||||
}
|
||||
|
||||
objectAnimSave.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
if (objectAnimName.text == null || objectAnimName.text == "")
|
||||
{
|
||||
state.notifyChange("Animation Saving Error", "Invalid Animation Name!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectAnimPrefix.text == null || objectAnimPrefix.text == "")
|
||||
{
|
||||
state.notifyChange("Animation Saving Error", "Missing Animation Prefix!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkedObject.animation.getNameList().contains(objectAnimName.text)) linkedObject.animation.remove(objectAnimName.text);
|
||||
|
||||
var indices = [];
|
||||
|
||||
if (objectAnimFrames.text != null && objectAnimFrames.text != "")
|
||||
{
|
||||
var splitter = objectAnimFrames.text.replace(" ", "").split(",");
|
||||
|
||||
for (num in splitter)
|
||||
{
|
||||
indices.push(Std.parseInt(num));
|
||||
}
|
||||
}
|
||||
|
||||
var shouldDoIndices:Bool = (indices.length > 0 && !indices.contains(null));
|
||||
|
||||
linkedObject.addAnim(objectAnimName.text, objectAnimPrefix.text, [objectAnimOffsetX.pos, objectAnimOffsetY.pos], (shouldDoIndices ? indices : []),
|
||||
Std.int(objectAnimFramerate.pos), objectAnimLooped.selected, objectAnimFlipX.selected, objectAnimFlipY.selected);
|
||||
|
||||
if (linkedObject.animation.getByName(objectAnimName.text) == null)
|
||||
{
|
||||
state.notifyChange("Animation Saving Error", "Invalid Frames!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
linkedObject.playAnim(objectAnimName.text);
|
||||
|
||||
state.notifyChange("Animation Saving Done", "Animation " + objectAnimName.text + " has been saved to the Object " + linkedObject.name + ".");
|
||||
updateAnimList();
|
||||
|
||||
// stops the animation preview if animation is looped for too long
|
||||
FlxTimer.wait(StageEditorState.TIME_BEFORE_ANIM_STOP, function() {
|
||||
if (linkedObject != null && linkedObject.animation.curAnim != null)
|
||||
linkedObject.animation.stop(); // null check cuz if we stop an anim for a null object the game crashes :[
|
||||
});
|
||||
}
|
||||
|
||||
objectAnimDelete.onClick = function(_) {
|
||||
if (linkedObject == null || linkedObject.animation.getNameList().length <= 0 || objectAnimDropdown.selectedIndex < 0) return;
|
||||
|
||||
linkedObject.animation.pause();
|
||||
linkedObject.animation.stop();
|
||||
linkedObject.animation.curAnim = null;
|
||||
|
||||
var daAnim = linkedObject.animation.getNameList()[objectAnimDropdown.selectedIndex];
|
||||
|
||||
linkedObject.animation.remove(daAnim);
|
||||
linkedObject.animDatas.remove(daAnim);
|
||||
linkedObject.offset.set();
|
||||
|
||||
state.notifyChange("Animation Deletion Done",
|
||||
"Animation "
|
||||
+ objectAnimDropdown.selectedItem.text
|
||||
+ " has been removed from the Object "
|
||||
+ linkedObject.name
|
||||
+ ".");
|
||||
|
||||
updateAnimList();
|
||||
|
||||
objectAnimDropdown.selectedIndex = objectAnimDropdown.dataSource.size - 1;
|
||||
}
|
||||
|
||||
objectAnimDanceBeat.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.danceEvery = Std.int(objectAnimDanceBeat.pos);
|
||||
}
|
||||
|
||||
objectAnimStart.onChange = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
if (linkedObject.animation.getNameList().contains(objectAnimStart.text)) objectAnimStart.styleString = "color: white";
|
||||
else
|
||||
objectAnimStart.styleString = "color: indianred";
|
||||
|
||||
linkedObject.startingAnimation = objectAnimStart.text;
|
||||
}
|
||||
}
|
||||
|
||||
// misc
|
||||
objectMiscAntialias.onClick = function(_) {
|
||||
if (linkedObject != null) linkedObject.antialiasing = objectMiscAntialias.selected;
|
||||
}
|
||||
|
||||
objectMiscBlendDrop.onChange = function(_) {
|
||||
if (linkedObject != null)
|
||||
linkedObject.blend = objectMiscBlendDrop.selectedItem.text == "NONE" ? null : AssetDataHandler.blendFromString(objectMiscBlendDrop.selectedItem.text);
|
||||
}
|
||||
|
||||
objectMiscColor.onChange = function(_) {
|
||||
if (linkedObject != null) linkedObject.color = FlxColor.fromRGB(objectMiscColor.currentColor.r, objectMiscColor.currentColor.g,
|
||||
objectMiscColor.currentColor.b);
|
||||
}
|
||||
|
||||
// reset button callbacks
|
||||
objectResetImageButton.onClick = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.loadGraphic(AssetDataHandler.getDefaultGraphic());
|
||||
linkedObject.updateHitbox();
|
||||
refresh();
|
||||
|
||||
// remove unused bitmaps
|
||||
state.removeUnusedBitmaps();
|
||||
}
|
||||
}
|
||||
|
||||
objectZIdxReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectZIdxStepper.pos = 0; // corner cutting because onChange will activate with this
|
||||
}
|
||||
|
||||
objectPosResetButton.onClick = function(_) {
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.screenCenter();
|
||||
objectPosXStepper.pos = linkedObject.x;
|
||||
objectPosYStepper.pos = linkedObject.y;
|
||||
}
|
||||
}
|
||||
|
||||
objectAlphaResetButton.onClick = function(_) {
|
||||
if (linkedObject != null) linkedObject.alpha = objectAlphaSlider.pos = 1;
|
||||
}
|
||||
|
||||
objectAngleResetButton.onClick = function(_) {
|
||||
if (linkedObject != null) linkedObject.angle = objectAngleSlider.pos = 0;
|
||||
}
|
||||
|
||||
objectScaleResetButton.onClick = objectSizeResetButton.onClick = function(_) // the corner cutting goes crazy
|
||||
{
|
||||
if (linkedObject != null)
|
||||
{
|
||||
linkedObject.scale.set(1, 1);
|
||||
refresh(); // refreshes like multiple shit
|
||||
}
|
||||
}
|
||||
|
||||
objectScrollResetButton.onClick = function(_) {
|
||||
if (linkedObject != null) linkedObject.scrollFactor.x = linkedObject.scrollFactor.y = objectScrollXSlider.pos = objectScrollYSlider.pos = 1;
|
||||
}
|
||||
|
||||
objectFrameReset.onClick = function(_) {
|
||||
if (linkedObject == null) return;
|
||||
|
||||
linkedObject.loadGraphic(linkedObject.pixels);
|
||||
linkedObject.animDatas.clear();
|
||||
linkedObject.animation.destroyAnimations();
|
||||
refresh();
|
||||
}
|
||||
|
||||
objectMiscAntialiasReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectMiscAntialias.selected = true;
|
||||
}
|
||||
|
||||
objectMiscBlendReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectMiscBlendDrop.selectedItem = "NORMAL";
|
||||
}
|
||||
|
||||
objectMiscColorReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectMiscColor.currentColor = Color.fromString("white");
|
||||
}
|
||||
|
||||
objectAnimDanceBeatReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectAnimDanceBeat.pos = 0;
|
||||
}
|
||||
|
||||
objectAnimStartReset.onClick = function(_) {
|
||||
if (linkedObject != null) objectAnimStart.text = "";
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
var prevFrames:Array<FlxFrame> = [];
|
||||
var prevAnims:Array<String> = [];
|
||||
|
||||
override public function refresh()
|
||||
{
|
||||
linkedObject = stageEditorState.selectedSprite;
|
||||
|
||||
objectPosXStepper.step = stageEditorState.moveStep;
|
||||
objectPosYStepper.step = stageEditorState.moveStep;
|
||||
objectAngleSlider.step = funkin.save.Save.instance.stageEditorAngleStep;
|
||||
|
||||
if (linkedObject == null)
|
||||
{
|
||||
updateFrameList();
|
||||
updateAnimList();
|
||||
return;
|
||||
}
|
||||
|
||||
// saving fps
|
||||
if (objectImagePreview.resource != linkedObject.frame) objectImagePreview.resource = linkedObject.frame;
|
||||
|
||||
if (objectZIdxStepper.pos != linkedObject.zIndex) objectZIdxStepper.pos = linkedObject.zIndex;
|
||||
if (objectPosXStepper.pos != linkedObject.x) objectPosXStepper.pos = linkedObject.x;
|
||||
if (objectPosYStepper.pos != linkedObject.y) objectPosYStepper.pos = linkedObject.y;
|
||||
if (objectAlphaSlider.pos != linkedObject.alpha) objectAlphaSlider.pos = linkedObject.alpha;
|
||||
if (objectAngleSlider.pos != linkedObject.angle) objectAngleSlider.pos = linkedObject.angle;
|
||||
if (objectScaleXStepper.pos != linkedObject.scale.x) objectScaleXStepper.pos = linkedObject.scale.x;
|
||||
if (objectScaleYStepper.pos != linkedObject.scale.y) objectScaleYStepper.pos = linkedObject.scale.y;
|
||||
if (objectSizeXStepper.pos != linkedObject.width) objectSizeXStepper.pos = linkedObject.width;
|
||||
if (objectSizeYStepper.pos != linkedObject.height) objectSizeYStepper.pos = linkedObject.height;
|
||||
if (objectScrollXSlider.pos != linkedObject.scrollFactor.x) objectScrollXSlider.pos = linkedObject.scrollFactor.x;
|
||||
if (objectScrollYSlider.pos != linkedObject.scrollFactor.y) objectScrollYSlider.pos = linkedObject.scrollFactor.y;
|
||||
if (objectMiscAntialias.selected != linkedObject.antialiasing) objectMiscAntialias.selected = linkedObject.antialiasing;
|
||||
|
||||
if (objectMiscColor.currentColor != Color.fromString(linkedObject.color.toHexString() ?? "white"))
|
||||
objectMiscColor.currentColor = Color.fromString(linkedObject.color.toHexString());
|
||||
|
||||
if (objectAnimDanceBeat.pos != linkedObject.danceEvery) objectAnimDanceBeat.pos = linkedObject.danceEvery;
|
||||
if (objectAnimStart.text != linkedObject.startingAnimation) objectAnimStart.text = linkedObject.startingAnimation;
|
||||
|
||||
var objBlend = Std.string(linkedObject.blend) ?? "NONE";
|
||||
if (objectMiscBlendDrop.selectedItem != objBlend.toUpperCase()) objectMiscBlendDrop.selectedItem = objBlend.toUpperCase();
|
||||
|
||||
// ough the max
|
||||
if (objectFrameImageWidth.max != linkedObject.pixels.width) objectFrameImageWidth.max = linkedObject.graphic.width;
|
||||
if (objectFrameImageHeight.max != linkedObject.pixels.height) objectFrameImageHeight.max = linkedObject.graphic.height;
|
||||
|
||||
// update some anim shit
|
||||
if (prevFrames != linkedObject.frames.frames.copy()) updateFrameList();
|
||||
if (prevAnims != linkedObject.animation.getNameList().copy()) updateAnimList();
|
||||
}
|
||||
|
||||
function updateFrameList()
|
||||
{
|
||||
prevFrames = [];
|
||||
objectAnimFrameList.dataSource = new ArrayDataSource();
|
||||
|
||||
if (linkedObject == null) return;
|
||||
|
||||
for (fname in linkedObject.frames.frames)
|
||||
{
|
||||
if (fname != null) objectAnimFrameList.dataSource.add({name: fname.name, tooltip: fname.name});
|
||||
|
||||
prevFrames.push(fname);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAnimList()
|
||||
{
|
||||
objectAnimDropdown.dataSource.clear();
|
||||
prevAnims = [];
|
||||
if (linkedObject == null) return;
|
||||
|
||||
for (aname in linkedObject.animation.getNameList())
|
||||
{
|
||||
objectAnimDropdown.dataSource.add({text: aname});
|
||||
prevAnims.push(aname);
|
||||
}
|
||||
|
||||
if (linkedObject.animation.getNameList().contains(objectAnimStart.text)) objectAnimStart.styleString = "color: white";
|
||||
else
|
||||
objectAnimStart.styleString = "color: indianred";
|
||||
|
||||
linkedObject.startingAnimation = objectAnimStart.text;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package funkin.ui.debug.stageeditor.toolboxes;
|
||||
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.components.DropDown;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/toolboxes/stage-settings.xml"))
|
||||
class StageEditorStageToolbox extends StageEditorDefaultToolbox
|
||||
{
|
||||
var stageNameText:TextField;
|
||||
var stageZoomStepper:NumberStepper;
|
||||
var stageLibraryDrop:DropDown;
|
||||
|
||||
override public function new(state:StageEditorState)
|
||||
{
|
||||
super(state);
|
||||
|
||||
stageNameText.onChange = function(_) {
|
||||
state.stageName = stageNameText.text;
|
||||
state.saved = false;
|
||||
}
|
||||
|
||||
stageZoomStepper.onChange = function(_) {
|
||||
state.stageZoom = stageZoomStepper.pos;
|
||||
state.updateMarkerPos();
|
||||
state.saved = false;
|
||||
}
|
||||
|
||||
final EXCLUDE_LIBS = ["art", "default", "vlc", "videos", "songs"];
|
||||
var allLibs = [];
|
||||
|
||||
@:privateAccess
|
||||
{
|
||||
for (lib => idk in lime.utils.Assets.libraryPaths)
|
||||
{
|
||||
if (!EXCLUDE_LIBS.contains(lib)) allLibs.push(lib);
|
||||
}
|
||||
}
|
||||
allLibs.sort(SortUtil.alphabetically); // this system is VERY stupid, it relies on the possibility that the future libraries will be named week(end)[x]
|
||||
|
||||
for (lib in allLibs)
|
||||
{
|
||||
stageLibraryDrop.dataSource.add({text: lib});
|
||||
}
|
||||
|
||||
stageLibraryDrop.onChange = function(_) {
|
||||
state.stageFolder = stageLibraryDrop.selectedItem.text;
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
override public function refresh()
|
||||
{
|
||||
stageNameText.text = stageEditorState.stageName;
|
||||
stageZoomStepper.pos = stageEditorState.stageZoom;
|
||||
stageLibraryDrop.selectedItem = stageEditorState.stageFolder;
|
||||
}
|
||||
}
|
|
@ -86,7 +86,7 @@ class LoadingState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
checkLibrary('shared');
|
||||
checkLibrary(PlayStatePlaylist.campaignId);
|
||||
checkLibrary(stageDirectory);
|
||||
checkLibrary('tutorial');
|
||||
|
||||
var fadeTime:Float = 0.5;
|
||||
|
@ -204,6 +204,8 @@ class LoadingState extends MusicBeatSubState
|
|||
return Paths.inst(PlayState.instance.currentSong.id);
|
||||
}
|
||||
|
||||
static var stageDirectory:String = "shared";
|
||||
|
||||
/**
|
||||
* Starts the transition to a new `PlayState` to start a new song.
|
||||
* First switches to the `LoadingState` if assets need to be loaded.
|
||||
|
@ -213,7 +215,13 @@ class LoadingState extends MusicBeatSubState
|
|||
*/
|
||||
public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false, asSubState = false, ?onConstruct:PlayState->Void):Void
|
||||
{
|
||||
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||
var daChart = params.targetSong.getDifficulty(params.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY,
|
||||
params.targetVariation ?? Constants.DEFAULT_VARIATION);
|
||||
|
||||
var daStage = funkin.data.stage.StageRegistry.instance.fetchEntry(daChart.stage);
|
||||
stageDirectory = daStage?._data?.directory ?? "shared";
|
||||
Paths.setCurrentLevel(stageDirectory);
|
||||
|
||||
var playStateCtor:() -> PlayState = function() {
|
||||
return new PlayState(params);
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ class FileUtil
|
|||
public static final FILE_FILTER_JSON:FileFilter = new FileFilter("JSON Data File (.json)", "*.json");
|
||||
public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip");
|
||||
public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png");
|
||||
public static final FILE_FILTER_FNFS:FileFilter = new FileFilter("Friday Night Funkin' Stage (.fnfs)", "*.fnfs");
|
||||
|
||||
public static final FILE_EXTENSION_INFO_FNFC:FileDialogExtensionInfo =
|
||||
{
|
||||
|
@ -39,6 +40,12 @@ class FileUtil
|
|||
label: 'PNG Image',
|
||||
};
|
||||
|
||||
public static final FILE_EXTENSION_INFO_FNFS:FileDialogExtensionInfo =
|
||||
{
|
||||
extension: 'fnfs',
|
||||
label: 'Friday Night Funkin\' Stage',
|
||||
};
|
||||
|
||||
/**
|
||||
* Browses for a single file, then calls `onSelect(fileInfo)` when a file is selected.
|
||||
* Powered by HaxeUI, so it works on all platforms.
|
||||
|
|
Loading…
Reference in a new issue