diff --git a/assets/data/alphabet/normal.xml b/assets/data/alphabet/normal.xml index 7b7bb076..081fce8b 100644 --- a/assets/data/alphabet/normal.xml +++ b/assets/data/alphabet/normal.xml @@ -1,5 +1,5 @@ - + menus/alphabet LETTER capital diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index b2901b69..b1e7d9c0 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -51,6 +51,17 @@ class CoolUtil return (v == null || isNaN(v)) ? defaultValue : v; } + /** + * Applies the % operator, but without any negatives. + * + * @param dividend The intial value. The left side of the equation. + * @param divisor What to modulo by. The right side of the equation. + * @return A positive reminder of `dividend / divisor`. + */ + public static inline function positiveModulo(dividend:Float, divisor:Float) { + return ((dividend % divisor) + divisor) % divisor; + } + /** * Shortcut to parse JSON from an Asset path * @param assetPath Path to the JSON asset. diff --git a/source/funkin/menus/ui/NewAlphabet.hx b/source/funkin/menus/ui/NewAlphabet.hx index a030fc5d..1ff8492f 100644 --- a/source/funkin/menus/ui/NewAlphabet.hx +++ b/source/funkin/menus/ui/NewAlphabet.hx @@ -1,11 +1,15 @@ 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; -import flixel.animation.FlxAnimation; using StringTools; +using flixel.util.FlxColorTransformUtil; enum abstract AlphabetAlignment(Int) from Int to Int { var LEFT; @@ -46,6 +50,9 @@ enum abstract CaseMode(Int) from Int to Int { } 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; @@ -62,6 +69,7 @@ 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 defaultAdvance:Float = 40.0; var lineGap:Float = 75.0; var fps:Float = 24.0; @@ -75,6 +83,7 @@ class NewAlphabet extends FlxSprite { 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) { @@ -97,6 +106,8 @@ class NewAlphabet extends FlxSprite { // FLXOBJECT UPDATE END __animTime += elapsed; + for (effect in effects) + effect.effectTime += FlxG.elapsed * effect.speed; } override function draw() { @@ -115,6 +126,7 @@ class NewAlphabet extends FlxSprite { if (__queueResize) recalcSizes(); var curLine = 0; + var lastLine = 0; var daText = switch (forceCase) { case UPPER: text.toUpperCase(); case LOWER: text.toLowerCase(); @@ -128,11 +140,22 @@ class NewAlphabet extends FlxSprite { 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; for (i in 0...daText.length) { - var letter = daText.charAt(i); + __renderData.reset(this, 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; @@ -147,8 +170,8 @@ class NewAlphabet extends FlxSprite { var frameToGet = Math.floor(__animTime * fps) % anim.numFrames; frame = frames.frames[anim.frames[frameToGet]]; - var offsetX = xOffset.exists(letter) ? xOffset.get(letter) : 0; - var offsetY = frame.sourceSize.y - lineGap + (yOffset.exists(letter) ? yOffset.get(letter) : 0); + 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)) { @@ -160,11 +183,18 @@ class NewAlphabet extends FlxSprite { 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, colorTransform.alphaMultiplier, + 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; } + colorTransform.redMultiplier = ogRed; + colorTransform.greenMultiplier = ogGreen; + colorTransform.blueMultiplier = ogBlue; } frameOffset.set(ogOffX, ogOffY); @@ -266,6 +296,7 @@ class NewAlphabet extends FlxSprite { 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; for (node in xml.elements()) { switch (node.nodeName) { diff --git a/source/funkin/menus/ui/effects/ColorWaveEffect.hx b/source/funkin/menus/ui/effects/ColorWaveEffect.hx new file mode 100644 index 00000000..f5fcce52 --- /dev/null +++ b/source/funkin/menus/ui/effects/ColorWaveEffect.hx @@ -0,0 +1,22 @@ +package funkin.menus.ui.effects; + +import flixel.util.FlxColor; +import funkin.menus.ui.effects.RegionEffect; + +class ColorWaveEffect extends RegionEffect { + public var period:Float = 30; + public var color1:FlxColor; + public var color2:FlxColor; + + public function new(color1:FlxColor, color2:FlxColor, ?period:Float = 30) { + super(); + speed = 5; + this.color1 = color1; + this.color2 = color2; + this.period = period; + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + renderData.color = FlxColor.interpolate(color1, color2, (-effectTime + lineIndex).positiveModulo(period) / period); + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/RegionEffect.hx b/source/funkin/menus/ui/effects/RegionEffect.hx new file mode 100644 index 00000000..ccc60e63 --- /dev/null +++ b/source/funkin/menus/ui/effects/RegionEffect.hx @@ -0,0 +1,73 @@ +package funkin.menus.ui.effects; + +import flixel.util.FlxColor; + +class AlphabetRenderData { + public var parent:NewAlphabet; + public var letter:String; + public var offsetX:Float = 0; + public var offsetY:Float = 0; + public var color(get, set):FlxColor; + public var red:Float = 0; + public var green:Float = 0; + public var blue:Float = 0; + + public function new(parent:NewAlphabet) { + this.parent = parent; + } + + public function reset(parent:NewAlphabet, letter:String) { + this.parent = parent; + this.letter = letter; + offsetX = 0; + offsetY = 0; + color = 0xFFFFFFFF; + } + + function get_color():FlxColor { + return FlxColor.fromRGBFloat(red, green, blue); + } + function set_color(value:FlxColor):FlxColor { + red = value.redFloat; + green = value.greenFloat; + blue = value.blueFloat; + return value; + } +} + +class RegionEffect { + public var effectTime:Float = 0; + public var speed:Float = 1; + public var enabled:Bool = true; + public var regionMin:Array = []; + public var regionMax:Array = []; + + public function new() {} + + public function resetRegions() { + regionMin.splice(0, regionMin.length); + regionMax.splice(0, regionMax.length); + } + + public function addRegion(min:Int, max:Int) { + regionMin.push(min); + regionMax.push(max); + } + + public function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Bool { + if (!enabled) return false; + + if (regionMin.length == 0) regionMin.push(0); + if (regionMax.length == 0) regionMax.push(-1); + + for (i in 0...Std.int(Math.min(regionMin.length, regionMax.length))) { + var min = (regionMin[i] < 0) ? renderData.parent.text.length - regionMin[i] : regionMin[i]; + var max = (regionMax[i] < 0) ? renderData.parent.text.length - regionMax[i] : regionMax[i]; + if (index >= min && index <= max) + return true; + } + return false; + } + + public function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void {} +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/ShakeEffect.hx b/source/funkin/menus/ui/effects/ShakeEffect.hx new file mode 100644 index 00000000..1144edbf --- /dev/null +++ b/source/funkin/menus/ui/effects/ShakeEffect.hx @@ -0,0 +1,23 @@ +package funkin.menus.ui.effects; + +import funkin.menus.ui.effects.RegionEffect; + +class ShakeEffect extends RegionEffect { + public var intensityX:Float = 2; + public var intensityY:Float = 2; + + public function new(?x:Float = 2, ?y:Float = 2) { + super(); + intensityX = x; + intensityY = y; + } + + override function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + return (intensityX != 0 || intensityY != 0) && super.willModify(index, lineIndex, renderData); + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void { + renderData.offsetX += FlxG.random.float(-intensityX, intensityX); + renderData.offsetY += FlxG.random.float(-intensityY, intensityY); + } +} \ No newline at end of file diff --git a/source/funkin/menus/ui/effects/WaveEffect.hx b/source/funkin/menus/ui/effects/WaveEffect.hx new file mode 100644 index 00000000..26b25fbb --- /dev/null +++ b/source/funkin/menus/ui/effects/WaveEffect.hx @@ -0,0 +1,26 @@ +package funkin.menus.ui.effects; + +import funkin.menus.ui.effects.RegionEffect; + +class WaveEffect extends RegionEffect { + public var intensityX:Float = 0; + public var intensityY:Float = 5; + public var period:Float = 10; + + public function new(?x:Float = 0, ?y:Float = 5, ?p:Float = 10) { + super(); + speed = 3; + intensityX = x; + intensityY = y; + period = p; + } + + override function willModify(index:Int, lineIndex:Int, renderData:AlphabetRenderData) { + return period != 0 && (intensityX != 0 || intensityY != 0) && super.willModify(index, lineIndex, renderData); + } + + override function modify(index:Int, lineIndex:Int, renderData:AlphabetRenderData):Void { + renderData.offsetX += intensityX * Math.cos((effectTime + lineIndex) * Math.PI * 2 / period); + renderData.offsetY += intensityY * Math.sin((effectTime + lineIndex) * Math.PI * 2 / period); + } +} \ No newline at end of file