diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index fc0a69c5..a3a1f4ee 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -66,6 +66,16 @@ class CoolUtil return (v == null || isNaN(v)) ? defaultValue : v; } + /** + * For use when using Std.parseFloat, if not using that then use `getDefault`. + * @param v The value + * @param defaultValue The default value + * @return The return value + */ + public static inline function getDefaultFloat(v:Float, defaultValue:Float):Float { + return (Math.isNaN(v)) ? defaultValue : v; + } + /** * Applies the % operator, but without any negatives. * diff --git a/source/funkin/menus/ui/NewAlphabet.hx b/source/funkin/menus/ui/NewAlphabet.hx index 67cebc1e..7a0c76d0 100644 --- a/source/funkin/menus/ui/NewAlphabet.hx +++ b/source/funkin/menus/ui/NewAlphabet.hx @@ -1,5 +1,6 @@ package funkin.menus.ui; +import haxe.ds.Vector; import funkin.menus.ui.effects.RegionEffect; import flixel.animation.FlxAnimation; @@ -13,10 +14,24 @@ using StringTools; using flixel.util.FlxColorTransformUtil; @:structInit -class AlphabetComponent { +final class AlphabetOutline { public var anim:String; public var x:Float; public var y:Float; +} + +@:structInit +final class AlphabetComponentData { + public var anim:String; + + @:optional public var outline:AlphabetOutline; +} + +@:structInit +final class AlphabetComponent { + public var data:AlphabetComponentData; + public var x:Float; + public var y:Float; // Precalculated values for angle public var sin:Float; @@ -26,7 +41,7 @@ class AlphabetComponent { } @:structInit -class AlphabetLetterData { +final class AlphabetLetterData { @:optional public var isDefault:Bool = false; public var advance:Float; public var advanceEmpty:Bool; @@ -91,13 +106,26 @@ class NewAlphabet extends FlxSprite { public var originOffset:FlxPoint = FlxPoint.get(); public var font(default, set):String; - var transformMult:Int = 1; // 1 if multipliers (tint), 0 if offset. + var transformMult:ByteUInt = 1; // 1 if multipliers (tint), 0 if offset. var defaultAdvance:Float = 40.0; var lineGap:Float = 75.0; var fps:Float = 24.0; var letterData:Map = []; - var defaults:Array = [null, null, null]; - var loaded:Array> = [[], [], []]; + var allComponents:Map = []; + var defaults:Vector = { + var v = new Vector(3); + v[0] = null; + v[1] = null; + v[2] = null; + v; + }; + var loaded:Vector> = { + var v = new Vector(3); + v[0] = []; + v[1] = []; + v[2] = []; + v; + }; var failedLetters:Array = []; public function new(?x:Float, ?y:Float, ?font:String = "bold") { @@ -185,20 +213,21 @@ class NewAlphabet extends FlxSprite { } var data = getData(letter); - var advance:Float = Math.NaN; if (data == null) { frameOffset.x -= defaultAdvance; continue; } + var advance:Float = Math.NaN; + for (i => com in data.components) { var anim = getLetterAnim(letter, data, com, i); advance = (Math.isNaN(advance)) ? getAdvance(letter, anim, data) : advance; if (anim == null || __renderData.alpha <= 0.0) { frameOffset.x -= advance; - break; + break; // shouldnt this be like continue? } var frameToGet = frameTime % anim.numFrames; @@ -210,8 +239,7 @@ class NewAlphabet extends FlxSprite { frameOffset.y += offsetY; if (!isOnScreen(camera)) { frameOffset.y -= offsetY; - frameOffset.x -= offsetX; - frameOffset.x -= advance; + frameOffset.x -= offsetX + advance; break; } if (isGraphicsShader) @@ -223,8 +251,7 @@ class NewAlphabet extends FlxSprite { ); super.drawComplex(camera); frameOffset.y -= offsetY; - frameOffset.x -= offsetX; - frameOffset.x -= advance; + frameOffset.x -= offsetX + advance; } } @@ -267,7 +294,7 @@ class NewAlphabet extends FlxSprite { origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y); } - function getAdvance(letter:String, anim:FlxAnimation, ?data:AlphabetLetterData):Float { + function getAdvance(letter:String, anim:FlxAnimation, data:AlphabetLetterData):Float { if (anim == null) return defaultAdvance; @@ -303,16 +330,27 @@ class NewAlphabet extends FlxSprite { failedLetters.push(char); return null; } else if (data.isDefault) - loaded[defaults.indexOf(data)].push(char); + loaded[defaultsIndexOf(data)].push(char); return data; } + final function defaultsIndexOf(data:AlphabetLetterData):Int { + //while(i < defaults.length) { + for(i in 0...3) { + if(defaults[i] == data) + return i; + } + return -1; + } + function getLetterAnim(char:String, data:AlphabetLetterData, component:AlphabetComponent, index:Int):FlxAnimation { if (data == null) return null; var name = char + Std.string(index); if (animation.exists(name)) return animation.getByName(name); - var anim = (data.isDefault) ? component.anim.replace("LETTER", char) : component.anim; + var anim = (data.isDefault) ? + component.data.anim.replace("LETTER", char) : + component.data.anim; animation.addByPrefix(name, anim, fps); if (!animation.exists(name)) { failedLetters.push(char); @@ -333,55 +371,94 @@ class NewAlphabet extends FlxSprite { case "defaultAnim": var idx = ["UPPER", "LOWER"].indexOf(node.get("casing").toUpperCase()) + 1; - var angle:Float = Std.parseFloat(node.get("angle")).getDefault(0.0); + var angle:Float = Std.parseFloat(node.get("angle")).getDefaultFloat(0.0); var angleCos = Math.cos(angle); var angleSin = Math.sin(angle); - defaults[idx] = { + // register character as component + allComponents.set("DEFAULT_" + idx, { + anim: node.firstChild().nodeValue.trim(), + outline: null + }); + + var res:AlphabetLetterData = { isDefault: true, advance: 0.0, advanceEmpty: true, components: [{ - anim: node.firstChild().nodeValue, + data: allComponents.get("DEFAULT_" + idx), x: 0.0, y: 0.0, - scaleX: Std.parseFloat(node.get("scaleX")).getDefault(1.0), - scaleY: Std.parseFloat(node.get("scaleY")).getDefault(1.0), - + scaleX: Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0), + scaleY: Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0), + cos: angleCos, sin: angleSin }] - }; + } + + defaults[idx] = res; + case "component-data": + if(!node.exists("id")) { + Logs.error(" must have an id attribute", "Alphabet"); + return; + } + + var id = node.get("id"); + var anim = node.firstChild().nodeValue.trim(); + var outline:AlphabetOutline = null; + if(node.firstElement() != null) { + var outlineNode = node.firstElement(); + switch(outlineNode.nodeName) { + case "outline": + outline = { + anim: outlineNode.firstChild().nodeValue.trim(), + x: Std.parseFloat(outlineNode.get("x")).getDefaultFloat(0.0), + y: Std.parseFloat(outlineNode.get("y")).getDefaultFloat(0.0) + }; + } + } + + allComponents.set(id, { + anim: anim, + outline: outline + }); case "composite": if(!node.exists("char")) { - Log.error(" must have a char attribute", "Alphabet"); + Logs.error(" must have a char attribute", "Alphabet"); return; } var char = node.get("char"); var advance:Float = Std.parseFloat(node.get("advance")); var components:Array = []; for (component in node.elementsNamed("component")) { - if(!component.exists("anim")) { - Log.error(" must have a anim attribute", "Alphabet"); + if(!component.exists("id")) { + Logs.error(" must have a id attribute", "Alphabet"); return; } - var angle:Float = Std.parseFloat(component.get("angle")).getDefault(0.0); + var angle:Float = Std.parseFloat(component.get("angle")).getDefaultFloat(0.0); var angleCos = Math.cos(angle); var angleSin = Math.sin(angle); - - var xOff:Float = -Std.parseFloat(component.get("x")).getDefault(0.0); - var yOff:Float = Std.parseFloat(component.get("y")).getDefault(0.0); + + var xOff:Float = -Std.parseFloat(component.get("x")).getDefaultFloat(0.0); + var yOff:Float = Std.parseFloat(component.get("y")).getDefaultFloat(0.0); + + var componentId = component.get("id").trim(); + if(!allComponents.exists(componentId)) { + Logs.error("Component with id '" + componentId + "' does not exist", "Alphabet"); + return; + } components.push({ - anim: component.get("anim"), + data: allComponents.get(componentId), x: xOff, y: yOff, - scaleX: Std.parseFloat(component.get("scaleX")).getDefault(1.0), - scaleY: Std.parseFloat(component.get("scaleY")).getDefault(1.0), - + scaleX: Std.parseFloat(component.get("scaleX")).getDefaultFloat(1.0), + scaleY: Std.parseFloat(component.get("scaleY")).getDefaultFloat(1.0), + cos: angleCos, sin: angleSin }); @@ -394,37 +471,46 @@ class NewAlphabet extends FlxSprite { }); case "anim": if(!node.exists("char")) { - Log.error(" must have a char attribute", "Alphabet"); + Logs.error(" must have a char attribute", "Alphabet"); return; } var char = node.get("char"); - var angle:Float = Std.parseFloat(node.get("angle")).getDefault(0.0); + var angle:Float = Std.parseFloat(node.get("angle")).getDefaultFloat(0.0); var angleCos = Math.cos(angle); var angleSin = Math.sin(angle); - var xOff:Float = -Std.parseFloat(node.get("x")).getDefault(0.0); - var yOff:Float = Std.parseFloat(node.get("y")).getDefault(0.0); + var xOff:Float = -Std.parseFloat(node.get("x")).getDefaultFloat(0.0); + var yOff:Float = Std.parseFloat(node.get("y")).getDefaultFloat(0.0); var advance:Float = Std.parseFloat(node.get("advance")); + // register character as component + allComponents.set(char, { + anim: node.firstChild().nodeValue, + outline: null + }); + letterData.set(char, { isDefault: false, advance: advance, advanceEmpty: Math.isNaN(advance), components: [{ - anim: node.firstChild().nodeValue, + data: allComponents.get(char), x: xOff, y: yOff, - scaleX: Std.parseFloat(node.get("scaleX")).getDefault(1.0), - scaleY: Std.parseFloat(node.get("scaleY")).getDefault(1.0), - + scaleX: Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0), + scaleY: Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0), + cos: angleCos, sin: angleSin }] }); case "languageSection": // used to only parse characters if a specific language is selected - if(!node.exists("langs")) throw "[Alphabet] must have a langs attribute"; + if(!node.exists("langs")) { + Logs.error(" must have a langs attribute", "Alphabet"); + return; + } var langs = [for (lang in node.get("langs").split(",")) lang.trim()]; // maybe add a way to toggle this off? like force it to add all if (langs.contains(Options.language)) { @@ -441,13 +527,25 @@ class NewAlphabet extends FlxSprite { // reset old values letterData = new Map(); - defaults = [null, null, null]; - loaded = [[], [], []]; + defaults = { + var v = new Vector(3); + v[0] = null; + v[1] = null; + v[2] = null; + v; + } + loaded = { + var v = new Vector(3); + v[0] = []; + v[1] = []; + v[2] = []; + v; + } failedLetters = []; - defaultAdvance = Std.parseFloat(xml.get("advance")).getDefault(40.0); - lineGap = Std.parseFloat(xml.get("lineGap")).getDefault(75.0); - fps = Std.parseFloat(xml.get("fps")).getDefault(24.0); + defaultAdvance = Std.parseFloat(xml.get("advance")).getDefaultFloat(40.0); + lineGap = Std.parseFloat(xml.get("lineGap")).getDefaultFloat(75.0); + fps = Std.parseFloat(xml.get("fps")).getDefaultFloat(24.0); forceCase = xml.get("forceCasing").getDefault("none").toLowerCase(); transformMult = (xml.get("useColorOffsets") == "true") ? 0 : 1;