diff --git a/source/funkin/editors/character/CharacterAnimButton.hx b/source/funkin/editors/character/CharacterAnimButton.hx index 254e97d5..eb20767b 100644 --- a/source/funkin/editors/character/CharacterAnimButton.hx +++ b/source/funkin/editors/character/CharacterAnimButton.hx @@ -417,7 +417,7 @@ class CharacterAnimButton extends UIButton { public function updateText() { field.applyMarkup( - '${anim} ' + (valid ? '(${parent.character.animOffsets[anim].x} ${parent.character.animOffsets[anim].y})' : "|(Invalid Animation!!!)|"), + '${anim} ' + (valid ? '(${parent.character.animOffsets[anim].x}, ${parent.character.animOffsets[anim].y})' : "|(Invalid Animation!!!)|"), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFF29797), "|")]); } diff --git a/source/funkin/editors/character/CharacterCreationScreen.hx b/source/funkin/editors/character/CharacterCreationScreen.hx new file mode 100644 index 00000000..7112026d --- /dev/null +++ b/source/funkin/editors/character/CharacterCreationScreen.hx @@ -0,0 +1,149 @@ +package funkin.editors.character; + +import haxe.xml.Printer; +import funkin.game.Character; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; +import flixel.text.FlxText.FlxTextFormat; +import flixel.text.FlxText.FlxTextFormatMarkerPair; + +class CharacterCreationScreen extends UISubstateWindow { + private var onSave:(String, ImageSaveData, Xml)-> Void = null; + + public var characterNameTextBox:UITextBox; + public var imageExplorer:UIImageExplorer; + + public var saveButton:UIButton; + public var closeButton:UIButton; + + public function new(?onSave:(String, ImageSaveData, Xml)->Void) { + super(); + if (onSave != null) this.onSave = onSave; + } + + public override function create() { + winTitle = "Creating New Character"; + + winWidth = 360; + winHeight = 520; + + super.create(); + + function addLabelOn(ui:UISprite, text:String):UIText { + var text:UIText = new UIText(ui.x, ui.y - 24, 0, text); + ui.members.push(text); + return text; + } + + characterNameTextBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 30 + 16 + 20, "character", 320); + characterNameTextBox.onChange = (_) -> {checkRequired();}; + add(characterNameTextBox); + addLabelOn(characterNameTextBox, "Character File Name").applyMarkup( + "Character File Name $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + + imageExplorer = new UIImageExplorer(characterNameTextBox.x, characterNameTextBox.y + 30 + 16 + 20, null, 320, 58, (_, _) -> {onLoadImage();}); + add(imageExplorer); + addLabelOn(imageExplorer, "Character Image File").applyMarkup( + "Character Image File $* Required$", + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + imageExplorer.maxSize.y -= 100; + + saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() { + createCharacter(); + close(); + }, 125); + add(saveButton); + + closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() { + close(); + }, 125); + add(closeButton); + closeButton.color = 0xFFFF0000; + + onLoadImage(); + } + + public function onLoadImage() { + refreshWindowSize(); + checkRequired(); + } + + public function refreshWindowSize() { + if (imageExplorer == null) return; + windowSpr.bWidth = 20 + imageExplorer.bWidth + 20; + windowSpr.bHeight = 30 + 16 + 20 + 32 + 30 + 10 + imageExplorer.bHeight + 14 + saveButton.bHeight + 14; + + saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - saveButton.bWidth; + closeButton.x = saveButton.x - 20 - closeButton.bWidth; + closeButton.y = saveButton.y = imageExplorer.y + imageExplorer.bHeight + 14; + } + + public function checkRequired() { + saveButton.selectable = characterNameTextBox.label.text.length > 0 && !CoolUtil.isMapEmpty(imageExplorer.imageFiles) && (imageExplorer.animationList.length > 0); + } + + public function createCharacter() { + var imageSaveData:ImageSaveData = imageExplorer.getSaveData(); + + var xml:Xml = Xml.createElement("character"); + xml.attributeOrder = Character.characterProperties.copy(); + + xml.set("sprite", imageSaveData.imageName); + + // Look for animations >:D + var animationList:Array = imageExplorer.animationList.copy(); + animationList.sort((a, b) -> { + var lengthCompare = a.length - b.length; + if (lengthCompare != 0) return lengthCompare; + + // Miss animations don't show up as regular if they are the same length as regular >:D + var aIsMiss = a.toLowerCase().contains("miss"); + var bIsMiss = b.toLowerCase().contains("miss"); + return aIsMiss == bIsMiss ? 0 : (aIsMiss ? 1 : -1); + }); + + var animationsFound:Map = [ + "singLEFT" => null, + "singRIGHT" => null, + "singUP" => null, + "singDOWN" => null, + "idle" => null + ]; + + for (anim => found in animationsFound) { + var animToLookFor:String = StringTools.replace(anim, "sing", "").toLowerCase(); + for (imageAnim in animationList) + if (imageAnim.toLowerCase().contains(animToLookFor)) { + animationsFound.set(anim, imageAnim); + animationList.remove(imageAnim); + break; + } + } + + // Add said animations >:D + for (anim => found in animationsFound) { + var animXml:Xml = Xml.createElement('anim'); + animXml.attributeOrder = Character.characterAnimProperties; + + animXml.set("name", anim); + animXml.set("anim", found.getDefault(animationList[0])); + + xml.addChild(animXml); + } + + // Do the rest only if not atlas (until we make it animations and not symbols >:D) + if (!imageSaveData.isAtlas) { + for (imageAnim in animationList) { + var animXml:Xml = Xml.createElement('anim'); + animXml.attributeOrder = Character.characterAnimProperties; + + animXml.set("name", imageAnim); + animXml.set("anim", imageAnim); + + xml.addChild(animXml); + } + } + + if (onSave != null) onSave(characterNameTextBox.label.text, imageSaveData, xml); + } +} \ No newline at end of file diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index cf5e26d6..9145618b 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -1,5 +1,7 @@ package funkin.editors.character; +import haxe.xml.Printer; +import funkin.editors.ui.UIImageExplorer.ImageSaveData; import funkin.game.Character; import funkin.options.OptionsScreen; import funkin.options.type.IconOption; @@ -44,9 +46,7 @@ class CharacterSelection extends EditorTreeMenu } list.insert(0, new NewOption("New Character", "New Character", function() { - openSubState(new UIWarningSubstate("New Character: Feature Not Implemented!", "This feature isn't implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ - {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} - ])); + openSubState(new CharacterCreationScreen(createCharacter)); })); return list; @@ -64,4 +64,30 @@ class CharacterSelection extends EditorTreeMenu main.changeSelection(1); } + + public function createCharacter(name:String, imageSaveData:ImageSaveData, xml:Xml) { + var characterAlreadyExists:Bool = Character.getList().contains(name); + + if (characterAlreadyExists) { + openSubState(new UIWarningSubstate("Creating Character: Error!", "The character you are trying to create already exists, if you would like to override it delete the character first!", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + return; + } + + // Save Data file + var characterPath:String = '${Paths.getAssetsRoot()}/data/characters/${name}.xml'; + CoolUtil.safeSaveFile(characterPath, "\n" + Printer.print(xml, true)); + + // Save Image files + UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters'); + + // Add to Menu >:D + var option:IconOption = new IconOption(name, "Press ACCEPT to edit this character.", Character.getIconFromCharName(name), + function() { + FlxG.switchState(new CharacterEditor(name)); + } + ); + optionsTree.members[optionsTree.members.length-1].insert(1, option); + } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIImageExplorer.hx b/source/funkin/editors/ui/UIImageExplorer.hx index 3e6961a5..971fe483 100644 --- a/source/funkin/editors/ui/UIImageExplorer.hx +++ b/source/funkin/editors/ui/UIImageExplorer.hx @@ -16,6 +16,12 @@ using StringTools; using funkin.backend.utils.BitmapUtil; using flixel.util.FlxSpriteUtil; +typedef ImageSaveData = { + var imageName:String; + var isAtlas:Bool; + var imageFiles:Map>; +} + // TODO: make this limited if on web class UIImageExplorer extends UIFileExplorer { private var allowAtlases:Bool = true; @@ -29,13 +35,15 @@ class UIImageExplorer extends UIFileExplorer { deleteButton.bWidth = 26; deleteButton.bHeight = 26; - var fullImagePath:String = '${Path.normalize(Sys.getCwd())}/${Paths.image(image)}'.replace('/', '\\'); - var noExt = Path.withoutExtension(fullImagePath); - if (FileSystem.exists('$noExt\\spritemap1.png')) - fullImagePath = '$noExt\\spritemap1.png'; - - if (FileSystem.exists(fullImagePath)) - loadFile(fullImagePath); + if (image != null) { + var fullImagePath:String = '${Path.normalize(Sys.getCwd())}/${Paths.image(image)}'.replace('/', '\\'); + var noExt = Path.withoutExtension(fullImagePath); + if (FileSystem.exists('$noExt\\spritemap1.png')) + fullImagePath = '$noExt\\spritemap1.png'; + + if (FileSystem.exists(fullImagePath)) + loadFile(fullImagePath); + } } public var isAtlas:Bool = false; @@ -50,6 +58,8 @@ class UIImageExplorer extends UIFileExplorer { public var fileText:UIText; + public var maxSize:FlxPoint = FlxPoint.get(700, 500); + public function uploadImage(filePath:String, file:Bytes) { __resetData(); @@ -139,7 +149,7 @@ class UIImageExplorer extends UIFileExplorer { } else { var dataPathExt:String = CoolUtil.imageHasFrameData(imagePath); var dataPath:String = Path.withExtension(imagePath, dataPathExt); - var dataPathFile:String = !isAtlas ? File.getContent(dataPath) : null; + var dataPathFile:String = !isAtlas && dataPathExt != null ? File.getContent(dataPath) : null; if (dataPathExt != null) { frames = CoolUtil.loadFramesFromData(dataPathFile, dataPathExt); @@ -182,10 +192,10 @@ class UIImageExplorer extends UIFileExplorer { // DISPLAY IMAGE!! uiElement = new FlxSprite().loadGraphic(image); var imageScale:Float = 1; - if (uiElement.width < 300 || uiElement.height < 200) - imageScale = Math.max(300/uiElement.width, 200/uiElement.height); - else if (uiElement.width > 700 || uiElement.height > 500) - imageScale = Math.min(700/uiElement.width, 500/uiElement.height); + if (uiElement.width < Math.min(300, maxSize.x) || uiElement.height < Math.min(200, maxSize.y)) + imageScale = Math.max(Math.min(300, maxSize.x) / uiElement.width, Math.min(200, maxSize.y) / uiElement.height); + else if (uiElement.width > maxSize.x || uiElement.height > maxSize.y) + imageScale = Math.min(maxSize.x/uiElement.width, maxSize.y/uiElement.height); uiElement.scale.set(imageScale, imageScale); uiElement.updateHitbox(); @@ -231,14 +241,25 @@ class UIImageExplorer extends UIFileExplorer { deleteIcon.x = deleteButton.x + deleteButton.bWidth/2 - 8; deleteIcon.y = deleteButton.y + deleteButton.bHeight/2 - 8; - } - public function saveFiles(directory:String, ?onFinishSaving:Void->Void, ?checkExisting:Bool = true) { - if (isAtlas) directory += '/$imageName'; + public inline function getSaveData():ImageSaveData { + return { + imageName: this.imageName, + isAtlas: this.isAtlas, + imageFiles: this.imageFiles + }; + } + + public inline function saveFiles(directory:String, ?onFinishSaving:Void->Void, ?checkExisting:Bool = true) { + UIImageExplorer.saveFilesGlobal(getSaveData(), directory, onFinishSaving, checkExisting); + } + + public static function saveFilesGlobal(imageData:ImageSaveData, directory:String, ?onFinishSaving:Void->Void, ?checkExisting:Bool = true) { + if (imageData.isAtlas) directory += '/${imageData.imageName}'; var alreadlyExistingFiles:Array = []; - for (name => file in imageFiles) + for (name => file in imageData.imageFiles) if (FileSystem.exists('$directory/$name')) alreadlyExistingFiles.push('$directory/$name'); @@ -249,7 +270,7 @@ class UIImageExplorer extends UIFileExplorer { } function acuttalySaveFiles() { - for (name => file in imageFiles) + for (name => file in imageData.imageFiles) if (!alreadlyExistingFiles.contains('$directory/$name')) { CoolUtil.safeSaveFile('$directory/$name', file); trace('SAVED: $directory/$name'); @@ -290,4 +311,9 @@ class UIImageExplorer extends UIFileExplorer { @:noCompletion inline function __resetData() { imageFiles.clear(); isAtlas = false; animationList = []; } + + public override function destroy() { + maxSize.put(); + super.destroy(); + } } \ No newline at end of file