package funkin.menus.ui; import funkin.menus.ui.effects.RegionEffect; import flixel.animation.FlxAnimation; import flixel.util.FlxDirectionFlags; import flixel.math.FlxPoint; import haxe.xml.Access; using StringTools; using flixel.util.FlxColorTransformUtil; enum abstract AlphabetAlignment(Int) from Int to Int { var LEFT; var CENTER; var RIGHT; public function getMultiplier():Float { return switch(this) { case CENTER: 0.5; case RIGHT: 1.0; default: 0.0; } } @:from public static function fromString(value:String):AlphabetAlignment return switch(value) { case "left": LEFT; case "center": CENTER; case "right": RIGHT; default: LEFT; } } enum abstract CaseMode(Int) from Int to Int { var NONE; var UPPER; var LOWER; @:from public static function fromString(value:String):CaseMode return switch(value) { case "none": NONE; case "upper": UPPER; case "lower": LOWER; default: NONE; } } class NewAlphabet extends FlxSprite { public var effects:Array = []; var __renderData:AlphabetRenderData; public var text(default, set):String = ""; @:isVar public var textWidth(get, set):Float; public var textHeight(get, null):Float; public var alignment:AlphabetAlignment = LEFT; public var forceCase:CaseMode = NONE; var __laneWidths:Array = []; // TODO: add alignment. var __forceWidth:Float = 0.0; // TODO: implement functionality for this. var __queueResize:Bool = false; var __animTime:Float = 0.0; var __ogForceScreen:Bool = false; public var originOffset:FlxPoint = FlxPoint.get(); public var font(default, set):String; var transformMult:Int = 1; // 1 if multipliers (tint), 0 if offset. var defaultAdvance:Float = 40.0; var lineGap:Float = 75.0; var fps:Float = 24.0; var defaults:Map = []; var anims:Map = []; var failedLetters:Array = []; var xOffset:Map = []; var yOffset:Map = []; var advances:Map = []; public function new(?x:Float, ?y:Float, ?font:String = "bold") { super(x, y); this.font = font; this.__renderData = new AlphabetRenderData(this); } override function update(elapsed:Float) { //super.update(elapsed); // FLXOBJECT UPDATE #if FLX_DEBUG @:privateAccess FlxBasic.activeCount += 1; #end last.set(x, y); if (path != null && path.active) path.update(elapsed); if (moves) updateMotion(elapsed); wasTouching = touching; touching = FlxDirectionFlags.NONE; // FLXOBJECT UPDATE END __animTime += elapsed; for (effect in effects) effect.effectTime += FlxG.elapsed * effect.speed; } override function draw() { __ogForceScreen = forceIsOnScreen; forceIsOnScreen = true; super.draw(); } override function isSimpleRender(?camera:FlxCamera):Bool { return false; // maybe ill get simple render working another time??? not right now tho. } override function drawComplex(camera:FlxCamera) { forceIsOnScreen = __ogForceScreen; if (__queueResize) recalcSizes(); var curLine = 0; var lastLine = 0; var daText = switch (forceCase) { case UPPER: text.toUpperCase(); case LOWER: text.toLowerCase(); case NONE: text; } var alignmentMultiplier = alignment.getMultiplier(); var ogOffX = frameOffset.x; var ogOffY = frameOffset.y; frameOffset.x -= (textWidth - __laneWidths[0]) * alignmentMultiplier; var isGraphicsShader = shader != null && shader is flixel.graphics.tile.FlxGraphicsShader; var offsetMult = (1 - transformMult) * 255; var ogRed:Float = colorTransform.redMultiplier; var ogGreen:Float = colorTransform.greenMultiplier; var ogBlue:Float = colorTransform.blueMultiplier; var ogAlpha:Float = colorTransform.alphaMultiplier; for (i in 0...daText.length) { __renderData.reset(this, ogRed, ogGreen, ogBlue, ogAlpha, daText.charAt(i)); for (effect in effects) { if (effect.willModify(i, i - lastLine, __renderData)) effect.modify(i, i - lastLine, __renderData); } var letter = __renderData.letter; if (letter == "\n") { curLine++; lastLine = i + 1; frameOffset.x = ogOffX - (textWidth - __laneWidths[curLine]) * alignmentMultiplier; frameOffset.y -= lineGap; continue; } var anim = getLetterAnim(letter); var advance:Float = getAdvance(letter, anim); if (anim == null || __renderData.alpha <= 0.0) { frameOffset.x -= advance; } else { var frameToGet = Math.floor(__animTime * fps) % anim.numFrames; frame = frames.frames[anim.frames[frameToGet]]; var offsetX = (xOffset.exists(letter) ? xOffset.get(letter) : 0) + __renderData.offsetX; var offsetY = frame.sourceSize.y - lineGap + (yOffset.exists(letter) ? yOffset.get(letter) : 0) + __renderData.offsetY; frameOffset.x += offsetX; frameOffset.y += offsetY; if (!isOnScreen(camera)) { frameOffset.y -= offsetY; frameOffset.x -= offsetX; frameOffset.x -= advance; continue; } if (isGraphicsShader) shader.setCamSize(_frame.frame.x, _frame.frame.y, _frame.frame.width, _frame.frame.height); setColorTransform( __renderData.red * transformMult, __renderData.green * transformMult, __renderData.blue * transformMult, __renderData.alpha, Math.round(__renderData.red * offsetMult), Math.round(__renderData.green * offsetMult), Math.round(__renderData.blue * offsetMult), 0 ); super.drawComplex(camera); frameOffset.y -= offsetY; frameOffset.x -= offsetX; frameOffset.x -= advance; } } setColorTransform(ogRed, ogGreen, ogBlue, ogAlpha, 0, 0, 0, 0); frameOffset.set(ogOffX, ogOffY); } override function updateHitbox() { width = Math.abs(scale.x) * textWidth; height = Math.abs(scale.y) * textHeight; offset.set(-0.5 * (width - textWidth), -0.5 * (height - textHeight)); origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y); } function recalcSizes():Void { __queueResize = false; var curLine = 0; var daText = switch (forceCase) { case UPPER: text.toUpperCase(); case LOWER: text.toLowerCase(); case NONE: text; } @:bypassAccessor textWidth = 0; textHeight = lineGap; __laneWidths = [0]; for (i in 0...daText.length) { var letter = daText.charAt(i); if (letter == "\n") { textHeight += lineGap; __laneWidths.push(0); curLine++; continue; } __laneWidths[curLine] += getAdvance(letter); @:bypassAccessor textWidth = Math.max(textWidth, __laneWidths[curLine]); } origin.set(textWidth * 0.5 + originOffset.x, textHeight * 0.5 + originOffset.y); } function getAdvance(letter:String, ?anim:FlxAnimation):Float { var advance:Null = defaultAdvance; if(advances.exists(letter)) { advance = advances.get(letter); } else { var anim = anim != null ? anim : getLetterAnim(letter); if(anim != null) advance = frames.frames[anim.frames[0]].sourceSize.x; advances.set(letter, advance); } return advance; } function getLetterAnim(char:String):FlxAnimation { if (failedLetters.contains(char)) return null; if (animation.exists(char)) return animation.getByName(char); var charCode:Int = char.charCodeAt(0); var anim:String = null; if (anims.exists(char)) anim = anims.get(char); if (anim == null) { if (charCode >= 'A'.code && charCode <= 'A'.code && defaults.exists("UPPER")) anim = defaults.get("UPPER").replace("LETTER", char); else if (charCode >= 'a'.code && charCode <= 'z'.code && defaults.exists("LOWER")) anim = defaults.get("LOWER").replace("LETTER", char); else anim = defaults.get("").replace("LETTER", char); } if(anim == null) { failedLetters.push(char); return null; } animation.addByPrefix(char, anim, fps); if (!animation.exists(char)) { failedLetters.push(char); return null; } return animation.getByName(char); } function checkNode(node:Xml) { switch (node.nodeName) { case "spritesheet": if (frames == null) frames = Paths.getFrames(node.firstChild().nodeValue); else { for (frame in Paths.getFrames(node.firstChild().nodeValue).frames) frames.pushFrame(frame); } case "defaultAnim": defaults.set(node.get("casing").getDefault("").toUpperCase(), node.firstChild().nodeValue); case "anim": var char = node.get("char"); anims.set(char, node.firstChild().nodeValue); if (node.exists("advance")) { var advance = Std.parseFloat(node.get("advance")).getDefault(defaultAdvance); advances.set(char, advance); } if (node.exists("x")) { // negative since flixel is weird var xOff = -Std.parseFloat(node.get("x")).getDefault(0.0); xOffset.set(char, xOff); } if (node.exists("y")) { var yOff = Std.parseFloat(node.get("y")).getDefault(0.0); yOffset.set(char, yOff); } case "languageSection": var langs = [for (lang in node.get("langs").split(",")) lang.trim()]; if (langs.contains(Options.language)) { for (langNode in node.elements()) checkNode(langNode); } } } function loadFont(value:String) { __queueResize = true; var xml:Xml = Xml.parse(Assets.getText(Paths.xml("alphabet/" + value))).firstElement(); // reset old values defaults = new Map(); anims = new Map(); advances = new Map(); xOffset = new Map(); yOffset = new Map(); 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); forceCase = xml.get("forceCasing").getDefault("none").toLowerCase(); transformMult = (xml.get("useColorOffsets") == "true") ? 0 : 1; frames = null; for (node in xml.elements()) checkNode(node); } override function destroy() { originOffset = FlxDestroyUtil.destroy(originOffset); failedLetters = []; super.destroy(); } function get_textWidth():Float { if (__queueResize) recalcSizes(); return textWidth; } function set_textWidth(value:Float):Float { if(!__queueResize) __queueResize = __forceWidth != value; return __forceWidth = value; } function get_textHeight():Float { if (__queueResize) recalcSizes(); return textHeight; } function set_text(value:String):String { if(!__queueResize) __queueResize = text != value; return text = value; } function set_font(value:String):String { if (font != value) { loadFont(font = value); } return value; } }