diff --git a/hxformat.json b/hxformat.json index 0a0bfd1fcf..4d31f14579 100644 --- a/hxformat.json +++ b/hxformat.json @@ -1,30 +1,16 @@ { - "disableFormatting": false, - - "indentation": { - "character": " ", - "tabWidth": 1, - "indentCaseLabels": true - }, - "lineEnds": { - "anonFunctionCurly": { - "emptyCurly": "break", - "leftCurly": "after", - "rightCurly": "both" - }, - "leftCurly": "both", - "rightCurly": "both" - }, - - "sameLine": { - "ifBody": "same", - "ifElse": "next", - "doWhile": "next", - "tryBody": "next", - "tryCatch": "next" - }, - - "whitespace": { - "switchPolicy": "around" - } + "lineEnds": { + "leftCurly": "both", + "rightCurly": "both", + "emptyCurly": "break", + "objectLiteralCurly": { + "leftCurly": "after" + } + }, + "sameLine": { + "ifElse": "next", + "doWhile": "next", + "tryBody": "next", + "tryCatch": "next" + } } diff --git a/source/Main.hx b/source/Main.hx index 3c5ca34027..14512fc153 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -65,11 +65,13 @@ class Main extends flixel.FlxGame initMainConfig(); initHaxeUI(); + // Load mods to override assets. + funkin.modding.PolymodHandler.loadEnabledMods(); + flixel.system.FlxAssets.FONT_DEFAULT = "VCR OSD Mono"; haxe.Log.trace = funkin.util.logging.AnsiTrace.trace; funkin.util.logging.AnsiTrace.traceBF(); - funkin.modding.PolymodHandler.loadAllMods(); statisticMonitor = new funkin.ui.debug.StatisticMonitor(10, 3, 0xFFFFFF); diff --git a/source/flixel/graphics/tile/FlxDrawBaseItem.hx b/source/flixel/graphics/tile/FlxDrawBaseItem.hx new file mode 100644 index 0000000000..c72a5358b5 --- /dev/null +++ b/source/flixel/graphics/tile/FlxDrawBaseItem.hx @@ -0,0 +1,90 @@ +package flixel.graphics.tile; + +import openfl.display.ShaderParameter; +import flixel.FlxCamera; +import flixel.graphics.frames.FlxFrame; +import flixel.math.FlxMatrix; +import openfl.display.BlendMode; +import openfl.geom.ColorTransform; + +/** + * @author Zaphod + */ +class FlxDrawBaseItem +{ + /** + * Tracks the total number of draw calls made each frame. + */ + public static var drawCalls:Int = 0; + + public static inline function blendToInt(blend:BlendMode):Int + { + return 0; // no blend mode support in drawQuads() + } + + public var nextTyped:T; + + public var next:FlxDrawBaseItem; + + public var graphics:FlxGraphic; + public var antialiasing:Bool = false; + public var colored(default, set):Bool = false; + public var hasColorOffsets:Bool = false; + public var blending:Int = 0; + public var blend:BlendMode; + + public var type:Null; + + public var numVertices(get, never):Int; + + public var numTriangles(get, never):Int; + + public function new() {} + + public function reset():Void + { + graphics = null; + antialiasing = false; + nextTyped = null; + next = null; + } + + public function dispose():Void + { + graphics = null; + next = null; + type = null; + nextTyped = null; + } + + public function render(camera:FlxCamera):Void + { + drawCalls++; + } + + function set_colored(value:Bool) return colored = value; + + public function addQuad(frame:FlxFrame, matrix:FlxMatrix, ?transform:ColorTransform):Void {} + + function get_numVertices():Int + { + return 0; + } + + function get_numTriangles():Int + { + return 0; + } + + inline function setParameterValue(parameter:ShaderParameter, value:Bool):Void + { + if (parameter.value == null) + parameter.value = []; + parameter.value[0] = value; + } +} + +enum abstract FlxDrawItemType(Bool) { + var TILES = false; + var TRIANGLES = true; +} \ No newline at end of file diff --git a/source/flixel/graphics/tile/FlxDrawQuadsItem.hx b/source/flixel/graphics/tile/FlxDrawQuadsItem.hx new file mode 100644 index 0000000000..b37b6f0bb3 --- /dev/null +++ b/source/flixel/graphics/tile/FlxDrawQuadsItem.hx @@ -0,0 +1,172 @@ +package flixel.graphics.tile; + +import openfl.display.GraphicsShader; +import openfl.display.Graphics; +import flixel.FlxCamera; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawBaseItem.FlxDrawItemType; +import flixel.system.FlxAssets.FlxShader; +import flixel.math.FlxMatrix; +import openfl.geom.ColorTransform; +import openfl.geom.Rectangle; +import openfl.Vector; + +class FlxDrawQuadsItem extends FlxDrawBaseItem +{ + static inline var VERTICES_PER_QUAD = 4; + + public var shader:FlxShader; + + var rects:Vector; + var transforms:Vector; + var alphas:Array; + var colorMultipliers:Array; + var colorOffsets:Array; + + public function new() + { + super(); + type = FlxDrawItemType.TILES; + rects = new Vector(); + transforms = new Vector(); + alphas = []; + } + + override public function reset():Void + { + super.reset(); + rects.length = 0; + transforms.length = 0; + alphas.resize(0); + if (colored) + { + colorMultipliers.resize(0); + colorOffsets.resize(0); + } + } + + override public function dispose():Void + { + super.dispose(); + rects = null; + transforms = null; + alphas = null; + colorMultipliers = null; + colorOffsets = null; + } + + override inline function set_colored(value:Bool) { + if (value) if (colorMultipliers == null) { + colorMultipliers = []; + colorOffsets = []; + } + return colored = value; + } + + override public function addQuad(frame:FlxFrame, matrix:FlxMatrix, ?transform:ColorTransform):Void + { + rects.push(frame.frame.x); + rects.push(frame.frame.y); + rects.push(frame.frame.width); + rects.push(frame.frame.height); + + transforms.push(matrix.a); + transforms.push(matrix.b); + transforms.push(matrix.c); + transforms.push(matrix.d); + transforms.push(matrix.tx); + transforms.push(matrix.ty); + + final alphaMultiplier = transform != null ? transform.alphaMultiplier : 1.0; + for (i in 0...VERTICES_PER_QUAD) + alphas.push(alphaMultiplier); + + if (colored) + { + for (i in 0...VERTICES_PER_QUAD) + { + colorMultipliers.push(transform.redMultiplier); + colorMultipliers.push(transform.greenMultiplier); + colorMultipliers.push(transform.blueMultiplier); + colorMultipliers.push(1); + + colorOffsets.push(transform.redOffset); + colorOffsets.push(transform.greenOffset); + colorOffsets.push(transform.blueOffset); + colorOffsets.push(transform.alphaOffset); + } + } + } + + #if !flash + override public function render(camera:FlxCamera):Void + { + if (#if cpp untyped __cpp__('this->rects->_hx___array->length == 0') #else rects.length == 0 #end) + return; + + if (shader == null) + { + shader = graphics.shader; + if (shader == null) + return; + } + + shader.bitmap.input = graphics.bitmap; + shader.alpha.value = alphas; + + shader.bitmap.filter = (camera.antialiasing || antialiasing) ? LINEAR : NEAREST; + + if (colored) + { + shader.colorMultiplier.value = colorMultipliers; + shader.colorOffset.value = colorOffsets; + } + + setParameterValue(shader.hasColorTransform, colored); + drawFlxQuad(camera.canvas.graphics, shader, rects, transforms); + + #if FLX_DEBUG + FlxDrawBaseItem.drawCalls++; + #end + } + + // Copy pasted from openfl Graphics, made SPECIFICALLY to work with funkin draw quads + + private static final bounds:Rectangle = new Rectangle(0, 0, 1280, 720); + + function drawFlxQuad(graphics:Graphics, shader:GraphicsShader, rects:Vector, transforms:Vector):Void @:privateAccess + { + // Override blend mode + if (blend == null) blend = NORMAL; + graphics.__commands.overrideBlendMode(blend); + + // Begin shader fill + final shaderBuffer = graphics.__shaderBufferPool.get(); + graphics.__usedShaderBuffers.add(shaderBuffer); + shaderBuffer.update(shader); + graphics.__commands.beginShaderFill(shaderBuffer); + + // Draw the quad + if (graphics.__bounds == null) + { + graphics.__bounds = bounds; + graphics.__transformDirty = true; + } + + graphics.__commands.drawQuads(rects, null, transforms); + + graphics.__dirty = true; + graphics.__visible = true; + } + #end + + override inline function get_numVertices():Int + { + return VERTICES_PER_QUAD; + } + + override inline function get_numTriangles():Int + { + return 2; + } +} \ No newline at end of file diff --git a/source/flixel/graphics/tile/FlxDrawTrianglesItem.hx b/source/flixel/graphics/tile/FlxDrawTrianglesItem.hx new file mode 100644 index 0000000000..6edf42b0a4 --- /dev/null +++ b/source/flixel/graphics/tile/FlxDrawTrianglesItem.hx @@ -0,0 +1,370 @@ +package flixel.graphics.tile; + +import flixel.FlxCamera; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawBaseItem.FlxDrawItemType; +import flixel.math.FlxMatrix; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; +import flixel.system.FlxAssets.FlxShader; +import flixel.util.FlxColor; +import openfl.display.Graphics; +import openfl.display.TriangleCulling; +import openfl.geom.ColorTransform; + +typedef DrawData = openfl.Vector; + +/** + * @author Zaphod + */ + @:unreflective +class FlxDrawTrianglesItem extends FlxDrawBaseItem +{ + static var point:FlxPoint = FlxPoint.get(); + static var rect:FlxRect = FlxRect.get(); + + #if !flash + public var shader:FlxShader; + var alphas:Array; + var colorMultipliers:Array; + var colorOffsets:Array; + #end + + public var vertices:DrawData = new DrawData(); + public var indices:DrawData = new DrawData(); + public var uvtData:DrawData = new DrawData(); + public var colors:DrawData = new DrawData(); + + public var verticesPosition:Int = 0; + public var indicesPosition:Int = 0; + public var colorsPosition:Int = 0; + + var bounds:FlxRect = FlxRect.get(); + + public function new() + { + super(); + type = FlxDrawItemType.TRIANGLES; + #if !flash + alphas = []; + #end + } + + override public function render(camera:FlxCamera):Void + { + if (numTriangles <= 0) + return; + + #if !flash + if (shader == null) + { + shader = graphics.shader; + if (shader == null) + return; + } + + shader.bitmap.input = graphics.bitmap; + shader.alpha.value = alphas; + shader.bitmap.wrap = REPEAT; // in order to prevent breaking tiling behaviour in classes that use drawTriangles + + shader.bitmap.filter = (camera.antialiasing || antialiasing) ? LINEAR : NEAREST; + + if (colored) + { + shader.colorMultiplier.value = colorMultipliers; + shader.colorOffset.value = colorOffsets; + } + + setParameterValue(shader.hasColorTransform, colored); + drawFlxTriangle(camera.canvas.graphics); + #else + camera.canvas.graphics.beginBitmapFill(graphics.bitmap, null, true, (camera.antialiasing || antialiasing)); + #end + + #if FLX_DEBUG + if (FlxG.debugger.drawDebug) + { + var gfx:Graphics = camera.debugLayer.graphics; + gfx.lineStyle(1, FlxColor.BLUE, 0.5); + gfx.drawTriangles(vertices, indices, uvtData); + } + + FlxDrawBaseItem.drawCalls++; + #end + } + + #if !flash + function drawFlxTriangle(graphics:Graphics):Void @:privateAccess + { + #if (openfl > "8.7.0") + graphics.__commands.overrideBlendMode(blend ?? NORMAL); + #end + + graphics.beginShaderFill(shader); + + graphics.drawTriangles(vertices, indices, uvtData, TriangleCulling.NONE); + graphics.endFill(); + } + #end + + override public function reset():Void + { + super.reset(); + vertices.length = 0; + indices.length = 0; + uvtData.length = 0; + colors.length = 0; + + verticesPosition = 0; + indicesPosition = 0; + colorsPosition = 0; + + #if !flash + alphas.resize(0); + if (colored) { + colorMultipliers.resize(0); + colorOffsets.resize(0); + } + #end + } + + override public function dispose():Void + { + super.dispose(); + + vertices = null; + indices = null; + uvtData = null; + colors = null; + bounds = null; + #if !flash + alphas = null; + colorMultipliers = null; + colorOffsets = null; + #end + } + + public function addTriangles(vertices:DrawData, indices:DrawData, uvtData:DrawData, ?colors:DrawData, ?position:FlxPoint, + ?cameraBounds:FlxRect #if !flash , ?transform:ColorTransform #end):Void + { + if (position == null) + position = point.set(); + + if (cameraBounds == null) + cameraBounds = rect.set(0, 0, FlxG.width, FlxG.height); + + var verticesLength:Int = vertices.length; + var prevVerticesLength:Int = this.vertices.length; + var numberOfVertices:Int = Std.int(verticesLength / 2); + var prevIndicesLength:Int = this.indices.length; + var prevUVTDataLength:Int = this.uvtData.length; + var prevColorsLength:Int = this.colors.length; + var prevNumberOfVertices:Int = this.numVertices; + + var tempX:Float, tempY:Float; + var i:Int = 0; + var currentVertexPosition:Int = prevVerticesLength; + + while (i < verticesLength) + { + tempX = position.x + vertices[i]; + tempY = position.y + vertices[i + 1]; + + this.vertices[currentVertexPosition++] = tempX; + this.vertices[currentVertexPosition++] = tempY; + + (i == 0) ? bounds.set(tempX, tempY, 0, 0) : inflateBounds(bounds, tempX, tempY); + + i += 2; + } + + if (!cameraBounds.overlaps(bounds)) + { + this.vertices.splice(this.vertices.length - verticesLength, verticesLength); + } + else + { + var uvtDataLength:Int = uvtData.length; + for (i in 0...uvtDataLength) + this.uvtData[prevUVTDataLength + i] = uvtData[i]; + + var indicesLength:Int = indices.length; + for (i in 0...indicesLength) + this.indices[prevIndicesLength + i] = indices[i] + prevNumberOfVertices; + + if (colored) + { + for (i in 0...numberOfVertices) + this.colors[prevColorsLength + i] = colors[i]; + + colorsPosition += numberOfVertices; + } + + verticesPosition += verticesLength; + indicesPosition += indicesLength; + } + + position.putWeak(); + cameraBounds.putWeak(); + + #if !flash + for (_ in 0...numTriangles) + { + if (transform != null) + { + alphas.push(transform.alphaMultiplier); + alphas.push(transform.alphaMultiplier); + alphas.push(transform.alphaMultiplier); + } + else + { + alphas.push(1.0); + alphas.push(1.0); + alphas.push(1.0); + } + } + + if (colored || hasColorOffsets) + { + if (colorMultipliers == null) { + colorMultipliers = []; + colorOffsets = []; + } + + for (_ in 0...(numTriangles * 3)) + { + if(transform != null) + { + colorMultipliers.push(transform.redMultiplier); + colorMultipliers.push(transform.greenMultiplier); + colorMultipliers.push(transform.blueMultiplier); + + colorOffsets.push(transform.redOffset); + colorOffsets.push(transform.greenOffset); + colorOffsets.push(transform.blueOffset); + colorOffsets.push(transform.alphaOffset); + } + else + { + colorMultipliers.push(1); + colorMultipliers.push(1); + colorMultipliers.push(1); + + colorOffsets.push(0); + colorOffsets.push(0); + colorOffsets.push(0); + colorOffsets.push(0); + } + + colorMultipliers.push(1); + } + } + #end + } + + public static inline function inflateBounds(bounds:FlxRect, x:Float, y:Float):FlxRect + { + if (x < bounds.x) + { + bounds.width += bounds.x - x; + bounds.x = x; + } + + if (y < bounds.y) + { + bounds.height += bounds.y - y; + bounds.y = y; + } + + if (x > bounds.x + bounds.width) + bounds.width = x - bounds.x; + + if (y > bounds.y + bounds.height) + bounds.height = y - bounds.y; + + return bounds; + } + + override public function addQuad(frame:FlxFrame, matrix:FlxMatrix, ?transform:ColorTransform):Void + { + var prevVerticesPos:Int = verticesPosition; + var prevIndicesPos:Int = indicesPosition; + var prevColorsPos:Int = colorsPosition; + var prevNumberOfVertices:Int = numVertices; + + var point = FlxPoint.get(); + point.transform(matrix); + + vertices[prevVerticesPos] = point.x; + vertices[prevVerticesPos + 1] = point.y; + + uvtData[prevVerticesPos] = frame.uv.x; + uvtData[prevVerticesPos + 1] = frame.uv.y; + + point.set(frame.frame.width, 0); + point.transform(matrix); + + vertices[prevVerticesPos + 2] = point.x; + vertices[prevVerticesPos + 3] = point.y; + + uvtData[prevVerticesPos + 2] = frame.uv.width; + uvtData[prevVerticesPos + 3] = frame.uv.y; + + point.set(frame.frame.width, frame.frame.height); + point.transform(matrix); + + vertices[prevVerticesPos + 4] = point.x; + vertices[prevVerticesPos + 5] = point.y; + + uvtData[prevVerticesPos + 4] = frame.uv.width; + uvtData[prevVerticesPos + 5] = frame.uv.height; + + point.set(0, frame.frame.height); + point.transform(matrix); + + vertices[prevVerticesPos + 6] = point.x; + vertices[prevVerticesPos + 7] = point.y; + + point.put(); + + uvtData[prevVerticesPos + 6] = frame.uv.x; + uvtData[prevVerticesPos + 7] = frame.uv.height; + + indices[prevIndicesPos] = prevNumberOfVertices; + indices[prevIndicesPos + 1] = prevNumberOfVertices + 1; + indices[prevIndicesPos + 2] = prevNumberOfVertices + 2; + indices[prevIndicesPos + 3] = prevNumberOfVertices + 2; + indices[prevIndicesPos + 4] = prevNumberOfVertices + 3; + indices[prevIndicesPos + 5] = prevNumberOfVertices; + + if (colored) + { + final color = FlxColor.fromRGBFloat( + transform.redMultiplier, + transform.greenMultiplier, + transform.blueMultiplier, + #if !neko transform.alphaMultiplier #else 1.0 #end + ); + + colors[prevColorsPos] = color; + colors[prevColorsPos + 1] = color; + colors[prevColorsPos + 2] = color; + colors[prevColorsPos + 3] = color; + + colorsPosition += 4; + } + + verticesPosition += 8; + indicesPosition += 6; + } + + override inline function get_numVertices():Int + { + return Std.int(vertices.length * .5); + } + + override inline function get_numTriangles():Int + { + return Std.int(indices.length / 3); + } +} \ No newline at end of file diff --git a/source/flixel/graphics/tile/FlxGraphicsShader.hx b/source/flixel/graphics/tile/FlxGraphicsShader.hx new file mode 100644 index 0000000000..e47b215e1a --- /dev/null +++ b/source/flixel/graphics/tile/FlxGraphicsShader.hx @@ -0,0 +1,61 @@ +package flixel.graphics.tile; + +import openfl.display.GraphicsShader; + +class FlxGraphicsShader extends GraphicsShader +{ + @:glVertexSource(" + #pragma header + + attribute float alpha; + attribute vec4 colorMultiplier; + attribute vec4 colorOffset; + uniform bool hasColorTransform; + + void main(void) + { + #pragma body + + openfl_Alphav = openfl_Alpha * alpha; + + if (hasColorTransform) + { + openfl_ColorOffsetv = colorOffset * 0.00392; + openfl_ColorMultiplierv = colorMultiplier; + } + }") + @:glFragmentHeader(" + uniform bool hasTransform; + uniform bool hasColorTransform; + + vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) + { + vec4 color = texture2D(bitmap, coord); + + if (color.a == 0.0) + { + return vec4(0.0, 0.0, 0.0, 0.0); + } + + if (!hasColorTransform) + { + return color * openfl_Alphav; + } + + color = vec4(color.rgb / color.a, color.a); + color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0); + return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav); + } + ") + @:glFragmentSource(" + #pragma header + + void main(void) + { + gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); + }") + public function new() + { + super(); + } +} \ No newline at end of file diff --git a/source/flixel/system/ui/FlxSoundTray.hx b/source/flixel/system/ui/FlxSoundTray.hx index c632a348ce..ea2956c7a0 100644 --- a/source/flixel/system/ui/FlxSoundTray.hx +++ b/source/flixel/system/ui/FlxSoundTray.hx @@ -22,6 +22,8 @@ import flixel.system.FlxAssets.FlxSoundAsset; import funkin.Paths; +import openfl.display.animation.AnimatedSprite; + /** * The flixel sound tray, the little volume meter that pops down sometimes. * Accessed via `FlxG.game.soundTray` or `FlxG.sound.soundTray`. @@ -43,6 +45,7 @@ class FlxSoundTray extends Sprite private var _localTimer:Float; private var _requestedY:Float; + var splashSprite:AnimatedSprite; var _volumeSprite:Bitmap; @:keep @@ -54,13 +57,24 @@ class FlxSoundTray extends Sprite scaleY = _defaultScale; screenCenter(); - final splashSprite:Bitmap = new Bitmap(Assets.getBitmapData(Paths.file("images/soundtray/volume-back.png"))); - splashSprite.smoothing = true; - _width = splashSprite.width; - - final disBg:Bitmap = new Bitmap(new BitmapData(200, 68, false, FlxColor.GRAY)); - _volumeSprite = new Bitmap(new BitmapData(1, 68, false, FlxColor.WHITE)); - disBg.x = _volumeSprite.x = 78; + //final splashSprite:Bitmap = new Bitmap(Assets.getBitmapData(Paths.file("images/soundtray/volume-back.png"))); + //splashSprite.smoothing = true; + + //splashSprite = AnimatedSprite.fromFramesCollection(Paths.getSparrowAtlas("soundtray/volume")); + var key = "soundtray/volume"; + splashSprite = AnimatedSprite.fromFramesCollection(flixel.graphics.frames.FlxAtlasFrames.fromSparrow(Paths.image(key, null, false), Paths.file('images/$key.xml'))); + splashSprite.y = -1; + splashSprite.animation.addByPrefix("splash", "volume back", 24, false); + splashSprite.animation.play("splash", true); + splashSprite.animation.curAnim.play(true); + splashSprite.animation.curAnim.finished = false; + splashSprite.animation.curAnim.looped = false; + _width = 382; //splashSprite.width; + + final disBg:Bitmap = new Bitmap(new BitmapData(233, 51, false, FlxColor.GRAY)); + _volumeSprite = new Bitmap(new BitmapData(1, 51, false, FlxColor.WHITE)); + disBg.x = _volumeSprite.x = 74; + disBg.y = _volumeSprite.y = 7; addChild(disBg); addChild(_volumeSprite); addChild(splashSprite); @@ -73,6 +87,8 @@ class FlxSoundTray extends Sprite public function update(MS:Float):Void { if (active) { + _volumeSprite.width = MathUtil.fpsLerp(_volumeSprite.width, FlxG.sound.muted ? 0 : (237 * FlxG.sound.volume), MathUtil.getFPSRatio(.65)); //.15 + if (_localTimer >= timeToExist) _requestedY = -height; y = MathUtil.fpsLerp(y, _requestedY, MathUtil.getFPSRatio(.25)); //.15 @@ -93,9 +109,7 @@ class FlxSoundTray extends Sprite if (sound != null) { final volumeSound = FlxG.sound.load(sound); volumeSound.onComplete = __soundOnComplete.bind(volumeSound); - #if FLX_PITCH - volumeSound.pitch = flixel.math.FlxMath.lerp(0.75, 1.25, FlxG.sound.volume); - #end + #if FLX_PITCH volumeSound.pitch = flixel.math.FlxMath.lerp(0.75, 1.25, FlxG.sound.volume); #end volumeSound.play(); FlxG.sound.list.remove(volumeSound); //due to stateSwitch bug } @@ -104,10 +118,14 @@ class FlxSoundTray extends Sprite if (shouldShow) { visible = true; active = true; + splashSprite.animation.curAnim.finished = false; + splashSprite.animation.curAnim.looped = false; + splashSprite.animation.curAnim.play(true); _localTimer = _requestedY = 0; } - _volumeSprite.width = 10 * globalVolume; + //_volumeSprite.width = 237 * (globalVolume/20); + //_volumeSprite.width = 237 * FlxG.sound.volume; } diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 6ecd2df3d2..e187a3dbce 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -158,7 +158,7 @@ class Conductor function get_stepLengthMs():Float { - return beatLengthMs / timeSignatureNumerator; + return beatLengthMs / Constants.STEPS_PER_BEAT; } /** @@ -227,7 +227,7 @@ class Conductor function get_instrumentalOffsetSteps():Float { - var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator; + var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / Constants.STEPS_PER_BEAT; return instrumentalOffset / startingStepLengthMs; } @@ -573,7 +573,7 @@ class Conductor } } - var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; + var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / Constants.STEPS_PER_BEAT; var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs; resultStep += resultFractionalStep; @@ -612,7 +612,7 @@ class Conductor } } - var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; + var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / Constants.STEPS_PER_BEAT; resultMs += (stepTime - lastTimeChange.beatTime * Constants.STEPS_PER_BEAT) * lastStepLengthMs; return resultMs; @@ -650,7 +650,7 @@ class Conductor } } - var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; + var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / Constants.STEPS_PER_BEAT; resultMs += (beatTime - lastTimeChange.beatTime) * lastStepLengthMs * Constants.STEPS_PER_BEAT; return resultMs; diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 9fb3b16f80..7d8c1c2ba0 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -396,8 +396,7 @@ class PolymodHandler Polymod.clearScripts(); // Forcibly reload Polymod so it finds any new files. - // TODO: Replace this with loadEnabledMods(). - funkin.modding.PolymodHandler.loadAllMods(); + funkin.modding.PolymodHandler.loadEnabledMods(); // Reload scripted classes so stages and modules will update. Polymod.registerAllScriptClasses(); diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index a36a23331f..25cfb42160 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -325,7 +325,7 @@ class GameOverSubState extends MusicBeatSubState FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); PlayState.instance.needsReset = true; - if (PlayState.instance.isMinimalMode || boyfriend == null || resetState) {} + if (PlayState.instance.isMinimalMode || boyfriend == null || resetState || this.isChartingMode) {} else { // Readd Boyfriend to the stage. diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 1827c8d6b9..033950c447 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1454,7 +1454,7 @@ class PlayState extends MusicBeatSubState // activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING); } - if (!startingSong && FlxG.sound.music != null + /*if (!startingSong && FlxG.sound.music != null && (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100 || Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100)) { @@ -1462,7 +1462,7 @@ class PlayState extends MusicBeatSubState if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)); trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)); resyncVocals(); - } + }*/ // Only bop camera if zoom level is below 135% if (Preferences.zoomCamera diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index d02567adcc..f229c2d93d 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -62,6 +62,7 @@ class AnimateAtlasCharacter extends BaseCharacter animation = new FlxAnimationController(this); offset = new FlxCallbackPoint(offsetCallback); + frameOffset = new FlxCallbackPoint(frameOffsetCallback); origin = new FlxCallbackPoint(originCallback); scale = new FlxCallbackPoint(scaleCallback); scrollFactor = new FlxCallbackPoint(scrollFactorCallback); @@ -109,10 +110,20 @@ class AnimateAtlasCharacter extends BaseCharacter var loop:Bool = animData.looped; this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop); + applyAnimationOffsets(correctName); animFinished = false; } + override function applyAnimationOffsets(name:String):Void + { + var offsets = getAnimationData(name).offsets; + if (offsets != null && !(offsets[0] == 0 && offsets[1] == 0)) + this.animOffsets = [offsets[0], offsets[1]]; + else + this.animOffsets = [0, 0]; + } + public override function loopCurrentAnim():Void { if (mainSprite == null) return; @@ -287,6 +298,7 @@ class AnimateAtlasCharacter extends BaseCharacter { // normally don't have to destroy FlxPoints, but these are FlxCallbackPoints! offset = FlxDestroyUtil.destroy(offset); + frameOffset = FlxDestroyUtil.destroy(frameOffset); origin = FlxDestroyUtil.destroy(origin); scale = FlxDestroyUtil.destroy(scale); scrollFactor = FlxDestroyUtil.destroy(scrollFactor); @@ -427,6 +439,9 @@ class AnimateAtlasCharacter extends BaseCharacter inline function offsetTransform(sprite:FlxSprite, offset:FlxPoint):Void sprite.offset.copyFrom(offset); + inline function frameOffsetTransform(sprite:FlxSprite, frameOffset:FlxPoint):Void + sprite.frameOffset.copyFrom(frameOffset); + inline function originTransform(sprite:FlxSprite, origin:FlxPoint):Void sprite.origin.copyFrom(origin); @@ -444,6 +459,9 @@ class AnimateAtlasCharacter extends BaseCharacter inline function offsetCallback(offset:FlxPoint):Void transformChildren(offsetTransform, offset); + inline function frameOffsetCallback(frameOffset:FlxPoint):Void + transformChildren(frameOffsetTransform, frameOffset); + inline function originCallback(origin:FlxPoint):Void transformChildren(originTransform, origin); diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index fc4bcc17d9..a16b9c7b11 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -7,6 +7,7 @@ import funkin.ui.MusicBeatSubState; import funkin.audio.FunkinSound; import funkin.ui.TextMenuList; import funkin.ui.debug.charting.ChartEditorState; +import funkin.ui.debug.modding.ModMenuState; import funkin.ui.MusicBeatSubState; import funkin.util.logging.CrashHandler; import flixel.addons.transition.FlxTransitionableState; @@ -58,10 +59,9 @@ class DebugMenuSubState extends MusicBeatSubState // createItem("Input Offset Testing", openInputOffsetTesting); createItem("ANIMATION EDITOR", openAnimationEditor); // createItem("STAGE EDITOR", openStageEditor); - // createItem("TEST STICKERS", testStickers); - #if sys - createItem("OPEN CRASH LOG FOLDER", openLogFolder); - #end + createItem("TEST STICKERS", testStickers); + #if desktop createItem("MOD MENU", openModMenu); #end + #if sys createItem("OPEN CRASH LOG FOLDER", openLogFolder); #end FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y + 500)); } @@ -119,6 +119,14 @@ class DebugMenuSubState extends MusicBeatSubState trace('Stage Editor'); } + #if desktop + function openModMenu() + { + FlxG.switchState(() -> new ModMenuState()); + trace('Mod Menu'); + } + #end + #if sys function openLogFolder() { diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 514799cd78..776595d657 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -299,6 +299,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState None => [] ]; + /** + * Hitsounds are played with a strange offset and this should not happen. + */ + public static final HITSOUND_OFFSET:Float = -30; // -60 VANILLA + /** * INSTANCE DATA */ @@ -6289,10 +6294,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Check for notes between the old and new song positions. - if (noteData.time < oldSongPosition) // Note is in the past. + if (noteData.time < (oldSongPosition - HITSOUND_OFFSET)) // Note is in the past. continue; - if (noteData.time > newSongPosition) // Note is in the future. + if (noteData.time > (newSongPosition - HITSOUND_OFFSET)) // Note is in the future. return; // Assume all notes are also in the future. // Note was just hit. diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx index 7c87a2d9f1..a1809c4e03 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx @@ -94,43 +94,43 @@ class ChartEditorNoteSprite extends FlxSprite function fetchNoteStyle(noteStyleId:String):NoteStyle return NoteStyleRegistry.instance.fetchEntry(noteStyleId) ?? NoteStyleRegistry.instance.fetchDefault(); - @:access(funkin.play.notes.notestyle.NoteStyle) - @:nullSafety(Off) - static function addNoteStyleFrames(noteStyle:NoteStyle):Void - { - var prefix:String = noteStyle.id.toTitleCase(); - - var frameCollection:FlxAtlasFrames = Paths.getSparrowAtlas(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary()); - for (frame in frameCollection.frames) - { - // cloning the frame because else - // we will fuck up the frame data used in game - var clonedFrame:FlxFrame = frame.copyTo(); - clonedFrame.name = '$prefix${clonedFrame.name}'; - noteFrameCollection.pushFrame(clonedFrame); - } - } - - - @:access(funkin.play.notes.notestyle.NoteStyle) - @:nullSafety(Off) - function addNoteStyleAnimations(noteStyle:NoteStyle):Void - { - var prefix:String = noteStyle.id.toTitleCase(); - var suffix:String = noteStyle.id.toTitleCase(); - - var leftData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.LEFT); - this.animation.addByPrefix('tapLeft$suffix', '$prefix${leftData.prefix}', leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY); - - var downData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.DOWN); - this.animation.addByPrefix('tapDown$suffix', '$prefix${downData.prefix}', downData.frameRate, downData.looped, downData.flipX, downData.flipY); - - var upData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.UP); - this.animation.addByPrefix('tapUp$suffix', '$prefix${upData.prefix}', upData.frameRate, upData.looped, upData.flipX, upData.flipY); - - var rightData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.RIGHT); - this.animation.addByPrefix('tapRight$suffix', '$prefix${rightData.prefix}', rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY); - } + @:access(funkin.play.notes.notestyle.NoteStyle) + @:nullSafety(Off) + static function addNoteStyleFrames(noteStyle:NoteStyle):Void + { + var prefix:String = noteStyle.id.toTitleCase(); + + var frameCollection:FlxAtlasFrames = Paths.getSparrowAtlas(noteStyle.getNoteAssetPath(), noteStyle.getNoteAssetLibrary()); + for (frame in frameCollection.frames) + { + // cloning the frame because else + // we will fuck up the frame data used in game + var clonedFrame:FlxFrame = frame.copyTo(); + clonedFrame.name = '$prefix${clonedFrame.name}'; + noteFrameCollection.pushFrame(clonedFrame); + } + } + + + @:access(funkin.play.notes.notestyle.NoteStyle) + @:nullSafety(Off) + function addNoteStyleAnimations(noteStyle:NoteStyle):Void + { + var prefix:String = noteStyle.id.toTitleCase(); + var suffix:String = noteStyle.id.toTitleCase(); + + var leftData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.LEFT); + this.animation.addByPrefix('tapLeft$suffix', '$prefix${leftData.prefix}', leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY); + + var downData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.DOWN); + this.animation.addByPrefix('tapDown$suffix', '$prefix${downData.prefix}', downData.frameRate, downData.looped, downData.flipX, downData.flipY); + + var upData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.UP); + this.animation.addByPrefix('tapUp$suffix', '$prefix${upData.prefix}', upData.frameRate, upData.looped, upData.flipX, upData.flipY); + + var rightData:AnimationData = noteStyle.fetchNoteAnimationData(NoteDirection.RIGHT); + this.animation.addByPrefix('tapRight$suffix', '$prefix${rightData.prefix}', rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY); + } @:nullSafety(Off) static function buildEmptyFrameCollection():Void @@ -184,19 +184,19 @@ class ChartEditorNoteSprite extends FlxSprite this.y += origin.y; } } - function get_noteStyle():Null - { - return this.noteStyle ?? this.parentState.currentSongNoteStyle; + function get_noteStyle():Null + { + return this.noteStyle ?? this.parentState.currentSongNoteStyle; } - function set_noteStyle(value:Null):Null - { - this.noteStyle = value; - this.playNoteAnimation(); - return value; - } + function set_noteStyle(value:Null):Null + { + this.noteStyle = value; + this.playNoteAnimation(); + return value; + } - @:nullSafety(Off) + @:nullSafety(Off) public function playNoteAnimation():Void { if (this.noteData == null) return; diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorNotificationHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorNotificationHandler.hx index 10da776944..bd947af145 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorNotificationHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorNotificationHandler.hx @@ -9,146 +9,158 @@ import haxe.ui.notifications.NotificationData.NotificationActionData; class ChartEditorNotificationHandler { - public static function setupNotifications(state:ChartEditorState):Void - { - // Setup notifications. - @:privateAccess - NotificationManager.GUTTER_SIZE = 20; - } - - /** - * Send a notification with a checkmark indicating success. - * @param state The current state of the chart editor. - */ - public static function success(state:ChartEditorState, title:String, body:String):Notification - { - return sendNotification(state, title, body, NotificationType.Success); - } - - /** - * Send a notification with a warning icon. - * @param state The current state of the chart editor. - */ - public static function warning(state:ChartEditorState, title:String, body:String):Notification - { - return sendNotification(state, title, body, NotificationType.Warning); - } - - /** - * Send a notification with a warning icon. - * @param state The current state of the chart editor. - */ - public static inline function warn(state:ChartEditorState, title:String, body:String):Notification - { - return warning(state, title, body); - } - - /** - * Send a notification with a cross indicating an error. - * @param state The current state of the chart editor. - */ - public static function error(state:ChartEditorState, title:String, body:String):Notification - { - return sendNotification(state, title, body, NotificationType.Error); - } - - /** - * Send a notification with a cross indicating failure. - * @param state The current state of the chart editor. - */ - public static inline function failure(state:ChartEditorState, title:String, body:String):Notification - { - return error(state, title, body); - } - - /** - * Send a notification with an info icon. - * @param state The current state of the chart editor. - */ - public static function info(state:ChartEditorState, title:String, body:String):Notification - { - return sendNotification(state, title, body, NotificationType.Info); - } - - /** - * Send a notification with an info icon and one or more actions. - * @param state The current state of the chart editor. - * @param title The title of the notification. - * @param body The body of the notification. - * @param actions The actions to add to the notification. - * @return The notification that was sent. - */ - public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array):Notification - { - return sendNotification(state, title, body, NotificationType.Info, actions); - } - - /** - * Clear all active notifications. - * @param state The current state of the chart editor. - */ - public static function clearNotifications(state:ChartEditorState):Void - { - NotificationManager.instance.clearNotifications(); - } - - /** - * Clear a specific notification. - * @param state The current state of the chart editor. - * @param notif The notification to clear. - */ - public static function clearNotification(state:ChartEditorState, notif:Notification):Void - { - NotificationManager.instance.removeNotification(notif); - } - - static function sendNotification(state:ChartEditorState, title:String, body:String, ?type:NotificationType, - ?actions:Array):Notification - { - var actionNames:Array = actions == null ? [] : actions.map(action -> action.text); - - var notif = NotificationManager.instance.addNotification( - { - title: title, - body: body, - type: type ?? NotificationType.Default, - expiryMs: Constants.NOTIFICATION_DISMISS_TIME, - actions: actions - }); - - if (actions != null && actions.length > 0) - { - // TODO: Tell Ian that this is REALLY dumb. - var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox); - actionsContainer.walkComponents(function(component) { - if (Std.isOfType(component, Button)) - { - var button:Button = cast component; - var action:Null = actions.find(action -> action.text == button.text); - if (action != null && action.callback != null) - { - button.onClick = function(_) { - // Don't allow actions to be clicked while the playtest is open. - if (state.subState != null) return; - action.callback(action); - }; - } - } - return true; // Continue walking. - }); - } - - return notif; - #if false - // TODO: Implement notifications on Mac OS OR... make sure the null is handled properly on mac? - return null; - trace('WARNING: Notifications are not supported on Mac OS.'); - #end - } + public static function setupNotifications(state:ChartEditorState):Void + { + // Setup notifications. + @:privateAccess + NotificationManager.GUTTER_SIZE = 20; + } + + /** + * Send a notification with a checkmark indicating success. + * @param state The current state of the chart editor. + */ + public static function success(state:ChartEditorState, title:String, body:String):Notification + { + return sendNotification(state, title, body, NotificationType.Success); + } + + /** + * Send a notification with a warning icon. + * @param state The current state of the chart editor. + */ + public static function warning(state:ChartEditorState, title:String, body:String):Notification + { + return sendNotification(state, title, body, NotificationType.Warning); + } + + /** + * Send a notification with a warning icon. + * @param state The current state of the chart editor. + */ + public static inline function warn(state:ChartEditorState, title:String, body:String):Notification + { + return warning(state, title, body); + } + + /** + * Send a notification with a cross indicating an error. + * @param state The current state of the chart editor. + */ + public static function error(state:ChartEditorState, title:String, body:String):Notification + { + return sendNotification(state, title, body, NotificationType.Error); + } + + /** + * Send a notification with a cross indicating failure. + * @param state The current state of the chart editor. + */ + public static inline function failure(state:ChartEditorState, title:String, body:String):Notification + { + return error(state, title, body); + } + + /** + * Send a notification with an info icon. + * @param state The current state of the chart editor. + */ + public static function info(state:ChartEditorState, title:String, body:String):Notification + { + return sendNotification(state, title, body, NotificationType.Info); + } + + /** + * Send a notification with an info icon and one or more actions. + * @param state The current state of the chart editor. + * @param title The title of the notification. + * @param body The body of the notification. + * @param actions The actions to add to the notification. + * @return The notification that was sent. + */ + public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array):Notification + { + return sendNotification(state, title, body, NotificationType.Info, actions); + } + + /** + * Clear all active notifications. + * @param state The current state of the chart editor. + */ + public static function clearNotifications(state:ChartEditorState):Void + { + NotificationManager.instance.clearNotifications(); + } + + /** + * Clear a specific notification. + * @param state The current state of the chart editor. + * @param notif The notification to clear. + */ + public static function clearNotification(state:ChartEditorState, notif:Notification):Void + { + NotificationManager.instance.removeNotification(notif); + } + + static function sendNotification(state:ChartEditorState, title:String, body:String, ?type:NotificationType, + ?actions:Array):Notification + { + var actionNames:Array = actions == null ? [] : actions.map(action -> action.text); + + var notif = NotificationManager.instance.addNotification( + { + title: title, + body: body, + type: type ?? NotificationType.Default, + expiryMs: Constants.NOTIFICATION_DISMISS_TIME, + actions: actions + }); + + final notifSound = FlxG.sound.load(Paths.sound("notif")); + notifSound.onComplete = __soundOnComplete.bind(notifSound); + notifSound.play(); + FlxG.sound.list.remove(notifSound); //due to stateSwitch bug + + + if (actions != null && actions.length > 0) + { + // TODO: Tell Ian that this is REALLY dumb. + var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox); + actionsContainer.walkComponents(function(component) { + if (Std.isOfType(component, Button)) + { + var button:Button = cast component; + var action:Null = actions.find(action -> action.text == button.text); + if (action != null && action.callback != null) + { + button.onClick = function(_) { + // Don't allow actions to be clicked while the playtest is open. + if (state.subState != null) return; + action.callback(action); + }; + } + } + return true; // Continue walking. + }); + } + + return notif; + #if false + // TODO: Implement notifications on Mac OS OR... make sure the null is handled properly on mac? + return null; + trace('WARNING: Notifications are not supported on Mac OS.'); + #end + } + + @:noCompletion static function __soundOnComplete(sound:flixel.sound.FlxSound) + { + FlxG.sound.list.add(sound); + sound.onComplete = null; + } } typedef NotificationAction = { - text:String, - callback:Void->Void + text:String, + callback:Void->Void } diff --git a/source/funkin/ui/debug/modding/ModMenuState.hx b/source/funkin/ui/debug/modding/ModMenuState.hx new file mode 100644 index 0000000000..ffaa2b05ff --- /dev/null +++ b/source/funkin/ui/debug/modding/ModMenuState.hx @@ -0,0 +1,184 @@ +package funkin.ui.debug.modding; + +import funkin.ui.debug.modding.components.ModBox; +import funkin.ui.mainmenu.MainMenuState; +import funkin.graphics.FunkinCamera; +import funkin.audio.FunkinSound; +import funkin.input.Cursor; +import funkin.modding.PolymodHandler; +import funkin.save.Save; +import haxe.ui.backend.flixel.UIState; +import polymod.Polymod.ModMetadata; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.FlxGraphic; +import flixel.math.FlxRect; +import flixel.FlxG; +import openfl.display.BitmapData; +import flixel.FlxG; + +/** + * A state for enabling and reordering mods. + */ +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/mod-menu/main-view.xml")) +class ModMenuState extends UIState // UIState derives from MusicBeatState +{ + var uiCamera:FunkinCamera; + var mods:Array; + + public function new() + { + super(); + + menubarItemReloadMods.onClick = _ -> { + saveMods(); + reloadMods(); + }; + + menubarItemQuit.onClick = _ -> quitModState(); + } + + override function create():Void + { + super.create(); + + mods = []; + + reloadMods(); + + Cursor.show(); + + uiCamera = new FunkinCamera('modStateUI'); + FlxG.cameras.reset(uiCamera); + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + + handleCursor(); + + if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.R) + { + saveMods(); + reloadMods(); + } + + if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q) + quitModState(); + } + + override function destroy():Void + { + super.destroy(); + + Cursor.hide(); + } + + function addMod(id:String, name:String, description:String, icon:FlxFrame, enabled:Bool):Void + { + var mod:ModBox = new ModBox(id, name, description, icon); + + mod.onClick = function(_) { + if (mod.parentComponent == disabledModsBox) + { + disabledModsBox.removeComponent(mod, false); + enabledModsBox.addComponentAt(mod, 0); + } + else + { + enabledModsBox.removeComponent(mod, false); + disabledModsBox.addComponentAt(mod, 0); + } + } + + mods.push(mod); + + enabled ? enabledModsBox.addComponent(mod) : disabledModsBox.addComponent(mod); + } + + function handleCursor():Void + { + if (FlxG.mouse.justPressed) + FunkinSound.playOnce(Paths.sound("chartingSounds/ClickDown")); + + if (FlxG.mouse.justReleased) + FunkinSound.playOnce(Paths.sound("chartingSounds/ClickUp")); + } + + function reloadMods():Void + { + while (mods.length > 0) + { + mods[0].parentComponent.removeComponent(mods[0]); + mods.shift(); + } + + #if desktop + var detectedMods:Array = PolymodHandler.getAllMods(); + var enabledModIds:Array = Save.instance.enabledModIds.clone(); + enabledModIds.reverse(); + + // Sort detectedMods based on the order of enabledModIds + detectedMods.sort((a, b) -> { + var indexA = enabledModIds.indexOf(a.id); + var indexB = enabledModIds.indexOf(b.id); + + if (indexA == -1 && indexB == -1) return 0; + if (indexA == -1) return 1; + if (indexB == -1) return -1; + + return indexA - indexB; + }); + + trace('ModState: Detected ${detectedMods.length} mods'); + + for (mod in detectedMods) + addMod(mod.id, mod.title, mod.description, getModIcon(mod.iconPath), enabledModIds.contains(mod.id)); + #end + } + + function saveMods():Void + { + var enabledModIds:Array = []; + + for (enabledMod in enabledModsBox.childComponents) + { + if (Std.isOfType(enabledMod, ModBox)) + { + var modBox:ModBox = cast(enabledMod, ModBox); + enabledModIds.push(modBox.modId); + } + } + + enabledModIds.reverse(); + Save.instance.enabledModIds = enabledModIds; + } + + function quitModState():Void + { + saveMods(); + + // stopping the music so that reloading the assets + // will use the overwritten music + if (FlxG.sound.music != null) + FlxG.sound.music.stop(); + + PolymodHandler.forceReloadAssets(); + FlxG.switchState(() -> new MainMenuState()); + } + + function getModIcon(iconPath:String):FlxFrame + { + if (iconPath == null || iconPath == "") + return null; + + if (sys.FileSystem.exists(iconPath)) + { + var iconBitmap:BitmapData = BitmapData.fromFile(iconPath); + var iconGraphic:FlxGraphic = FlxGraphic.fromBitmapData(iconBitmap, false, iconPath); + return iconGraphic.imageFrame.frame; + } + + return null; + } +} \ No newline at end of file diff --git a/source/funkin/ui/debug/modding/components/ModBox.hx b/source/funkin/ui/debug/modding/components/ModBox.hx new file mode 100644 index 0000000000..02fd67fa05 --- /dev/null +++ b/source/funkin/ui/debug/modding/components/ModBox.hx @@ -0,0 +1,80 @@ +package funkin.ui.debug.modding.components; + +import haxe.ui.containers.HBox; +import haxe.ui.events.MouseEvent; +import haxe.ui.events.Events; +import flixel.graphics.frames.FlxFrame; + +/** + * HaxeUI component for the mod menu + */ +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/mod-menu/components/modbox.xml")) +@:composite(ModBoxEvents) +class ModBox extends HBox +{ + public var modId(default, null):String; + public var modName(default, null):String; + public var modDescription(default, null):String; + public var modIconFrame(default, null):FlxFrame; + + public function new(modId:String, modName:String, modDescription:String, modIconFrame:FlxFrame) + { + super(); + + this.modId = modId; + this.modName = modName; + this.modDescription = modDescription; + this.modIconFrame = modIconFrame; + this.tooltip = modDescription; + this.modLabel.value = modName; + this.modIcon.resource = modIconFrame; + } +} + +/** + * Composite class for handling mouse events + */ +class ModBoxEvents extends Events +{ + var _modBox:ModBox; + + public function new(modbox:ModBox) + { + super(modbox); + _modBox = modbox; + } + + public override function register():Void + { + if (!hasEvent(MouseEvent.MOUSE_OVER, onMouseOver)) + registerEvent(MouseEvent.MOUSE_OVER, onMouseOver); + + if (!hasEvent(MouseEvent.MOUSE_OUT, onMouseOut)) + registerEvent(MouseEvent.MOUSE_OUT, onMouseOut); + + if (!hasEvent(MouseEvent.CLICK, onClick)) + registerEvent(MouseEvent.CLICK, onClick); + } + + public override function unregister():Void + { + unregisterEvent(MouseEvent.MOUSE_OVER, onMouseOver); + unregisterEvent(MouseEvent.MOUSE_OUT, onMouseOut); + unregisterEvent(MouseEvent.CLICK, onClick); + } + + function onMouseOver(event:MouseEvent):Void + { + _modBox.addClass(":hover", true, true); + } + + function onMouseOut(event:MouseEvent):Void + { + _modBox.removeClass(":hover", true, true); + } + + function onClick(event:MouseEvent):Void + { + _modBox.removeClass(":hover", true, true); + } +} \ No newline at end of file diff --git a/source/funkin/ui/freeplay/Album.hx b/source/funkin/ui/freeplay/Album.hx index 9bae88de00..22f6d225a7 100644 --- a/source/funkin/ui/freeplay/Album.hx +++ b/source/funkin/ui/freeplay/Album.hx @@ -11,90 +11,90 @@ import flixel.graphics.FlxGraphic; */ class Album implements IRegistryEntry { - /** - * The internal ID for this album. - */ - public final id:String; + /** + * The internal ID for this album. + */ + public final id:String; - /** - * The full data for an album. - */ - public final _data:AlbumData; + /** + * The full data for an album. + */ + public final _data:AlbumData; - public function new(id:String) - { - this.id = id; - this._data = _fetchData(id); + public function new(id:String) + { + this.id = id; + this._data = _fetchData(id); - if (_data == null) - { - throw 'Could not parse album data for id: $id'; - } - } + if (_data == null) + { + throw 'Could not parse album data for id: $id'; + } + } - /** - * Return the name of the album. - * @ - */ - public function getAlbumName():String - { - return _data.name; - } + /** + * Return the name of the album. + * @ + */ + public function getAlbumName():String + { + return _data.name; + } - /** - * Return the artists of the album. - * @return The list of artists - */ - public function getAlbumArtists():Array - { - return _data.artists; - } + /** + * Return the artists of the album. + * @return The list of artists + */ + public function getAlbumArtists():Array + { + return _data.artists; + } - /** - * Get the asset key for the album art. - * @return The asset key - */ - public function getAlbumArtAssetKey():String - { - return _data.albumArtAsset; - } + /** + * Get the asset key for the album art. + * @return The asset key + */ + public function getAlbumArtAssetKey():String + { + return _data.albumArtAsset; + } - /** - * Get the album art as a graphic, ready to apply to a sprite. - * @return The built graphic - */ - public function getAlbumArtGraphic():FlxGraphic - { - return FlxG.bitmap.add(Paths.image(getAlbumArtAssetKey())); - } + /** + * Get the album art as a graphic, ready to apply to a sprite. + * @return The built graphic + */ + public function getAlbumArtGraphic():FlxGraphic + { + return FlxG.bitmap.add(Paths.image(getAlbumArtAssetKey())); + } - /** - * Get the asset key for the album title. - */ - public function getAlbumTitleAssetKey():String - { - return _data.albumTitleAsset; - } + /** + * Get the asset key for the album title. + */ + public function getAlbumTitleAssetKey():String + { + return _data.albumTitleAsset; + } - public function hasAlbumTitleAnimations() - { - return _data.albumTitleAnimations.length > 0; - } + public function hasAlbumTitleAnimations() + { + return _data.albumTitleAnimations.length > 0; + } - public function getAlbumTitleAnimations():Array - { - return _data.albumTitleAnimations; - } + public function getAlbumTitleAnimations():Array + { + return _data.albumTitleAnimations; + } - public function toString():String - { - return 'Album($id)'; - } + public function toString():String + { + return 'Album($id)'; + } - public function destroy():Void {} + public function destroy():Void {} - static function _fetchData(id:String):Null - { - return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id)); - } + static function _fetchData(id:String):Null + { + return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id)); + } } diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx index 49c5887225..aa5ea132ca 100644 --- a/source/funkin/ui/freeplay/AlbumRoll.hx +++ b/source/funkin/ui/freeplay/AlbumRoll.hx @@ -19,160 +19,161 @@ import openfl.utils.Assets; */ class AlbumRoll extends FlxSpriteGroup { - /** - * The ID of the album to display. - * Modify this value to automatically update the album art and title. - */ - public var albumId(default, set):Null; - - function set_albumId(value:Null):Null - { - if (this.albumId != value) - { - this.albumId = value; - updateAlbum(); - } - - return value; - } - - var newAlbumArt:FlxAtlasSprite; - - var difficultyStars:DifficultyStars; - var _exitMovers:Null; - - var albumData:Album; - - final animNames:Map = [ - "volume1-active" => "ENTRANCE", - "volume2-active" => "ENTRANCE VOL2", - "volume3-active" => "ENTRANCE VOL3", - "volume1-trans" => "VOL1 TRANS", - "volume2-trans" => "VOL2 TRANS", - "volume3-trans" => "VOL3 TRANS", - "volume1-idle" => "VOL1 STILL", - "volume2-idle" => "VOL2 STILL", - "volume3-idle" => "VOL3 STILL", - ]; - - public function new() - { - super(); - - newAlbumArt = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum")); - newAlbumArt.visible = false; - newAlbumArt.onAnimationFinish.add(onAlbumFinish); - - add(newAlbumArt); - - difficultyStars = new DifficultyStars(140, 39); - difficultyStars.visible = false; - add(difficultyStars); - } - - function onAlbumFinish(animName:String):Void - { - // Play the idle animation for the current album. - newAlbumArt.playAnimation(animNames.get('$albumId-idle'), false, false, true); - - // End on the last frame and don't continue until playAnimation is called again. - // newAlbumArt.anim.pause(); - } - - /** - * Load the album data by ID and update the textures. - */ - function updateAlbum():Void - { - if (albumId == null) - { - this.visible = false; - difficultyStars.stars.visible = false; - return; - } - else - { - this.visible = true; - } - - albumData = AlbumRegistry.instance.fetchEntry(albumId); - - if (albumData == null) - { - FlxG.log.warn('Could not find album data for album ID: ${albumId}'); - - return; - }; - - applyExitMovers(); - - refresh(); - } - - public function refresh():Void - { - sort(SortUtil.byZIndex, FlxSort.ASCENDING); - } - - /** - * Apply exit movers for the album roll. - * @param exitMovers The exit movers to apply. - */ - public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData):Void - { - if (exitMovers == null) - { - exitMovers = _exitMovers; - } - else - { - _exitMovers = exitMovers; - } - - if (exitMovers == null) return; - - exitMovers.set([newAlbumArt, difficultyStars], - { - x: FlxG.width, - speed: 0.4, - wait: 0 - }); - } - - var titleTimer:Null = null; - - /** - * Play the intro animation on the album art. - */ - public function playIntro():Void - { - newAlbumArt.visible = true; - newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false); - - difficultyStars.visible = false; - new FlxTimer().start(0.75, function(_) { - // showTitle(); - showStars(); - }); - } - - public function skipIntro():Void - { - newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false); - } - - public function setDifficultyStars(?difficulty:Int):Void - { - if (difficulty == null) return; - difficultyStars.difficulty = difficulty; - } - - /** - * Make the album stars visible. - */ - public function showStars():Void - { - difficultyStars.visible = true; // true; - difficultyStars.flameCheck(); - } + /** + * The ID of the album to display. + * Modify this value to automatically update the album art and title. + */ + public var albumId(default, set):Null; + + function set_albumId(value:Null):Null + { + if (this.albumId != value) + { + this.albumId = value; + updateAlbum(); + } + + return value; + } + + var newAlbumArt:FlxAtlasSprite; + + var difficultyStars:DifficultyStars; + var _exitMovers:Null; + + var albumData:Album; + + final animNames:Map = [ + "volume1-active" => "ENTRANCE", + "volume2-active" => "ENTRANCE VOL2", + "volume3-active" => "ENTRANCE VOL3", + "volume1-trans" => "VOL1 TRANS", + "volume2-trans" => "VOL2 TRANS", + "volume3-trans" => "VOL3 TRANS", + "volume1-idle" => "VOL1 STILL", + "volume2-idle" => "VOL2 STILL", + "volume3-idle" => "VOL3 STILL", + ]; + + public function new() + { + super(); + + newAlbumArt = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum")); + newAlbumArt.visible = false; + newAlbumArt.onAnimationFinish.add(onAlbumFinish); + newAlbumArt.offset.set(-650, -350); + + add(newAlbumArt); + + difficultyStars = new DifficultyStars(140, 39); + difficultyStars.visible = false; + add(difficultyStars); + } + + function onAlbumFinish(animName:String):Void + { + // Play the idle animation for the current album. + newAlbumArt.playAnimation(animNames.get('$albumId-idle'), false, false, true); + + // End on the last frame and don't continue until playAnimation is called again. + // newAlbumArt.anim.pause(); + } + + /** + * Load the album data by ID and update the textures. + */ + function updateAlbum():Void + { + if (albumId == null) + { + this.visible = false; + difficultyStars.stars.visible = false; + return; + } + else + { + this.visible = true; + } + + albumData = AlbumRegistry.instance.fetchEntry(albumId); + + if (albumData == null) + { + FlxG.log.warn('Could not find album data for album ID: ${albumId}'); + + return; + }; + + applyExitMovers(); + + refresh(); + } + + public function refresh():Void + { + sort(SortUtil.byZIndex, FlxSort.ASCENDING); + } + + /** + * Apply exit movers for the album roll. + * @param exitMovers The exit movers to apply. + */ + public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData):Void + { + if (exitMovers == null) + { + exitMovers = _exitMovers; + } + else + { + _exitMovers = exitMovers; + } + + if (exitMovers == null) return; + + exitMovers.set([newAlbumArt, difficultyStars], + { + x: FlxG.width, + speed: 0.4, + wait: 0 + }); + } + + var titleTimer:Null = null; + + /** + * Play the intro animation on the album art. + */ + public function playIntro():Void + { + newAlbumArt.visible = true; + newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false); + + difficultyStars.visible = false; + new FlxTimer().start(0.75, function(_) { + // showTitle(); + showStars(); + }); + } + + public function skipIntro():Void + { + newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false); + } + + public function setDifficultyStars(?difficulty:Int):Void + { + if (difficulty == null) return; + difficultyStars.difficulty = difficulty; + } + + /** + * Make the album stars visible. + */ + public function showStars():Void + { + difficultyStars.visible = true; // true; + difficultyStars.flameCheck(); + } } diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 344dc699f6..cc2b61991d 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -629,26 +629,6 @@ class SongMenuItem extends FlxSpriteGroup { if (impactThing != null) impactThing.angle = capsule.angle; - // if (FlxG.keys.justPressed.I) - // { - // newText.y -= 1; - // trace(this.x - newText.x, this.y - newText.y); - // } - // if (FlxG.keys.justPressed.J) - // { - // newText.x -= 1; - // trace(this.x - newText.x, this.y - newText.y); - // } - // if (FlxG.keys.justPressed.L) - // { - // newText.x += 1; - // trace(this.x - newText.x, this.y - newText.y); - // } - // if (FlxG.keys.justPressed.K) - // { - // newText.y += 1; - // trace(this.x - newText.x, this.y - newText.y); - // } if (doJumpIn) { frameInTicker += elapsed; diff --git a/source/funkin/ui/options/ModMenu.hx b/source/funkin/ui/options/ModMenu.hx deleted file mode 100644 index 32f86223d0..0000000000 --- a/source/funkin/ui/options/ModMenu.hx +++ /dev/null @@ -1,155 +0,0 @@ -package funkin.ui.options; - -import funkin.modding.PolymodHandler; -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; -import flixel.FlxCamera; -import flixel.FlxObject; -import flixel.FlxSprite; -import flixel.text.FlxText; -import flixel.util.FlxColor; -import flixel.effects.FlxFlicker; -import polymod.Polymod; -import funkin.ui.AtlasText.AtlasFont; -import funkin.graphics.FunkinCamera; -import funkin.ui.options.OptionsState.Page; -import funkin.input.Controls; -import funkin.audio.FunkinSound; -import funkin.ui.TextMenuList.TextMenuItem; - -class ModMenu extends Page -{ - var grpMods:FlxTypedGroup; - var enabledMods:Array = []; - var detectedMods:Array = []; - - var curSelected:Int = 0; - - public function new():Void - { - super(); - - grpMods = new FlxTypedGroup(); - add(grpMods); - - refreshModList(); - } - - override function update(elapsed:Float) - { - if (FlxG.keys.justPressed.R) { - refreshModList(); - FunkinSound.playOnce(Paths.sound('cancelMenu')); - trace('Refreshed Mod List!'); - trace('ModMenu: Detected ${detectedMods.length} mods'); - } - - selections(); - - if (controls.UI_UP_P) { - selections(-1); - FunkinSound.playOnce(Paths.sound('scrollMenu')); - } - - if (controls.UI_DOWN_P) { - selections(1); - FunkinSound.playOnce(Paths.sound('scrollMenu')); - } - - if (FlxG.keys.justPressed.SPACE || FlxG.keys.justPressed.ENTER) { - grpMods.members[curSelected].modEnabled = !grpMods.members[curSelected].modEnabled; - if (grpMods.members[curSelected].modEnabled) { - FlxFlicker.flicker(grpMods.members[curSelected], 1.1, 0.0625, true, true); - FunkinSound.playOnce(Paths.sound('confirmMenu')); - trace('Enabled a mod!'); - } else if (!grpMods.members[curSelected].modEnabled) { - FunkinSound.playOnce(Paths.sound('cancelMenu')); - trace('Disabled a mod!'); - } - } - - if (controls.BACK) { - FunkinSound.playOnce(Paths.sound('cancelMenu')); - FlxG.switchState(() -> new OptionsState()); - } - - if (FlxG.keys.justPressed.I && curSelected != 0) - { - FunkinSound.playOnce(Paths.sound('cancelMenu')); - var oldOne = grpMods.members[curSelected - 1]; - grpMods.members[curSelected - 1] = grpMods.members[curSelected]; - grpMods.members[curSelected] = oldOne; - selections(-1); - } - - if (FlxG.keys.justPressed.K && curSelected < grpMods.members.length - 1) - { - FunkinSound.playOnce(Paths.sound('cancelMenu')); - var oldOne = grpMods.members[curSelected + 1]; - grpMods.members[curSelected + 1] = grpMods.members[curSelected]; - grpMods.members[curSelected] = oldOne; - selections(1); - } - super.update(elapsed); - } - - function selections(change:Int = 0):Void - { - curSelected += change; - - if (curSelected >= detectedMods.length) curSelected = 0; - if (curSelected < 0) curSelected = detectedMods.length - 1; - - for (txt in 0...grpMods.length) - grpMods.members[txt].color = (txt == curSelected) ? FlxColor.YELLOW : FlxColor.WHITE; - - organizeByY(); - } - - function refreshModList():Void - { - while (grpMods.members.length > 0) - { - grpMods.remove(grpMods.members[0], true); - } - - #if desktop - detectedMods = PolymodHandler.getAllMods(); - - trace('ModMenu: Detected ${detectedMods.length} mods'); - - for (index in 0...detectedMods.length) - { - var modMetadata:ModMetadata = detectedMods[index]; - var modName:String = modMetadata.title; - var txt:ModMenuItem = new ModMenuItem(0, 10 + (40 * index), 0, modName, 48); - txt.setFormat(Paths.font('vcr.ttf'), 48, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); - txt.text = modName; - grpMods.add(txt); - } - #end - } - - function organizeByY():Void - { - for (i in 0...grpMods.length) - grpMods.members[i].y = 10 + (40 * i); - } -} - -class ModMenuItem extends FlxText -{ - public var modEnabled:Bool = false; - public var daMod:String; - - public function new(x:Float, y:Float, w:Float, str:String, size:Int) - { - super(x, y, w, str, size); - } - - override function update(elapsed:Float) - { - alpha = modEnabled ? 1 : .5; - super.update(elapsed); - } -} \ No newline at end of file diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index 98f1ae223d..8606bbf2b7 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -40,7 +40,6 @@ class OptionsState extends MusicBeatState var options = addPage(Options, new OptionsMenu()); var preferences = addPage(Preferences, new PreferencesMenu()); - var mods = addPage(Mods, new ModMenu()); var controls = addPage(Controls, new ControlsMenu()); if (options.hasMultipleOptions()) @@ -194,7 +193,6 @@ class OptionsMenu extends Page add(items = new TextMenuList()); createItem("PREFERENCES", () -> switchPage(Preferences)); createItem("CONTROLS", () -> switchPage(Controls)); - createItem("MODS", () -> switchPage(Mods)); createItem("INPUT OFFSETS", () -> FlxG.state.openSubState(new LatencyState())); createItem("EXIT", exit); } diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx index 07a4a42806..3d8035cda5 100644 --- a/source/funkin/ui/transition/StickerSubState.hx +++ b/source/funkin/ui/transition/StickerSubState.hx @@ -98,15 +98,13 @@ class StickerSubState extends MusicBeatSubState add(grpStickers); // makes the stickers on the most recent camera, which is more often than not... a UI camera!! - // grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + //grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; grpStickers.cameras = FlxG.cameras.list; if (oldStickers != null) { for (sticker in oldStickers) - { grpStickers.add(sticker); - } degenStickers(); } @@ -116,16 +114,9 @@ class StickerSubState extends MusicBeatSubState public function degenStickers():Void { + //grpStickers.cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; grpStickers.cameras = FlxG.cameras.list; - /* - if (dipshit != null) - { - FlxG.removeChild(dipshit); - dipshit = null; - } - */ - if (grpStickers.members == null || grpStickers.members.length == 0) { switchingState = false; @@ -159,9 +150,7 @@ class StickerSubState extends MusicBeatSubState var stickerInfo:StickerInfo = new StickerInfo('stickers-set-1'); var stickers:Map> = new Map>(); for (stickerSets in stickerInfo.getPack("all")) - { stickers.set(stickerSets, stickerInfo.getStickers(stickerSets)); - } var xPos:Float = -100; var yPos:Float = -100; @@ -259,11 +248,6 @@ class StickerSubState extends MusicBeatSubState override public function update(elapsed:Float):Void { super.update(elapsed); - - // if (FlxG.keys.justPressed.ANY) - // { - // regenStickers(); - // } } var switchingState:Bool = false; diff --git a/source/openfl/display/animation/AnimatedSprite.hx b/source/openfl/display/animation/AnimatedSprite.hx new file mode 100644 index 0000000000..8436648394 --- /dev/null +++ b/source/openfl/display/animation/AnimatedSprite.hx @@ -0,0 +1,78 @@ +package openfl.display.animation; + +import openfl.display.animation.AnimationController.Frame; +import flixel.FlxSprite; +import flixel.graphics.frames.FlxFrame; +import flixel.graphics.frames.FlxFramesCollection; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Sprite; +import openfl.events.Event; + +class AnimatedSprite extends Sprite { + public var animation:AnimationController; + public var frames(get, set):Array; + public var bitmap:Bitmap = new Bitmap(); + + private var curBitmap:BitmapData; + private var focusLost:Bool = false; + + public function new(?frames:Array) { + super(); + animation = new AnimationController(this); + addChild(bitmap); + addEventListener(Event.ENTER_FRAME, updateBitmap); + addEventListener(Event.DEACTIVATE, function(_) { + focusLost = true; + }); + addEventListener(Event.ACTIVATE, function(_) { + focusLost = false; + }); + + if (frames != null) + this.frames = frames; + } + + public function updateBitmap(_) { + //if (!focusLost) { + if (animation != null && animation.curAnim != null) { + if (curBitmap != frames[animation.curAnim.curIndex].bitmap) { + curBitmap = frames[animation.curAnim.curIndex].bitmap; + bitmap.bitmapData = curBitmap; + } + } + //} + } + + public function set_frames(Frames:Array):Array { + animation.frames = Frames; + return Frames; + } + + public function get_frames():Array { + return animation.frames; + } + + public static function fromFramesCollection(framesCollection:FlxFramesCollection):AnimatedSprite { + var frames:Array = []; + for (frame in framesCollection.frames) + frames.push(new Frame(frame.name, frame.paint())); + return new AnimatedSprite(frames); + } + + /*public static function fromSparrow(image:String, path:String):AnimatedSprite { // forgive me for these + return AnimatedSprite.fromFramesCollection(image, path); + } + + public static function fromSpriteSheetPacker(image:String, path:String):AnimatedSprite { + return AnimatedSprite.fromFramesCollection(image, path); + } + + public static function fromTexturePackerXml(image:String, path:String):AnimatedSprite { + return AnimatedSprite.fromFramesCollection(image, path); + } + + public static function fromTexturePackerJson(image:String, path:String):AnimatedSprite { + return AnimatedSprite.fromFramesCollection(image, path); + }*/ +} diff --git a/source/openfl/display/animation/Animation.hx b/source/openfl/display/animation/Animation.hx new file mode 100644 index 0000000000..3a3b756cc7 --- /dev/null +++ b/source/openfl/display/animation/Animation.hx @@ -0,0 +1,101 @@ +package openfl.display.animation; + +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Sprite; +import openfl.utils.Timer; + +class Animation { + public var frameRate(default, set):Int; + public var curFrame(default, set):Int = 0; + public var maxFrames:Int; + public var frames:Array; + public var curIndex:Int = 0; + + public var finished:Bool = true; + public var looped:Bool = true; + public var paused:Bool = true; + + public var name:String; + public var onFinish:Void->Void; + + private var delayTimer:Timer; + + public function new(Name:String, Frames:Array, FrameRate:Int = 0, Looped:Bool = true) + { + delayTimer = new Timer(1 / frameRate * 1000); + delayTimer.addEventListener('timer', updateFrame); + delayTimer.stop(); + + name = Name; + frames = Frames; + frameRate = FrameRate; + looped = Looped; + } + + public function updateFrame(_) { + if (finished || paused) + return; + if (looped && curFrame == frames.length - 1) + curFrame = 0; + else + curFrame++; + } + + public function play(Force:Bool = false, StartFrame:Int = 0) { + if (!Force) { + resume(); + finished = false; + return; + } + + resume(); + if (StartFrame < 0) + StartFrame = 0; + else if (StartFrame > frames.length - 1) + StartFrame = frames.length; + curFrame = StartFrame; + } + + public function stop() { + finished = true; + pause(); + } + + public function finish() { + stop(); + curFrame = 0; + } + + public function pause() { + delayTimer.stop(); + paused = true; + } + + public function resume() { + delayTimer.start(); + paused = false; + } + + public function set_frameRate(frameRate:Int):Int { + delayTimer.delay = 1 / frameRate * 1000; + return frameRate; + } + + public function set_curFrame(Frame:Int):Int { + if (Frame >= 0) { + if (!looped && Frame > frames.length - 1) { + finished = true; + curFrame = frames.length - 1; + } else + curFrame = Frame; + } else + curFrame = 0; + curIndex = frames[curFrame]; + + if (finished && onFinish != null) + onFinish(); + + return Frame; + } +} \ No newline at end of file diff --git a/source/openfl/display/animation/AnimationController.hx b/source/openfl/display/animation/AnimationController.hx new file mode 100644 index 0000000000..ec9b0c1d3f --- /dev/null +++ b/source/openfl/display/animation/AnimationController.hx @@ -0,0 +1,173 @@ +package openfl.display.animation; + +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Sprite; + + +class Frame { + public var bitmap:BitmapData; + public var name:String; + + public function new(Name:String, bitmap:BitmapData){ + name = Name; + this.bitmap = bitmap; + } + + public static function sort(frames:Array, prefixLength:Int, postPrefixLength:Int){ + haxe.ds.ArraySort.sort(frames, sortByName.bind(_, _, prefixLength, postPrefixLength)); + } + public static function sortByName(frame1:Frame, frame2:Frame, prefixLength:Int, postPrefixLength:Int){ + var name1:String = frame1.name; + var name2:String = frame2.name; + var num1:Null = Std.parseInt(name1.substring(prefixLength, name1.length - postPrefixLength)); + var num2:Null = Std.parseInt(name2.substring(prefixLength, name2.length - postPrefixLength)); + if (num1 == null) + num1 = 0; + if (num2 == null) + num2 = 0; + + return num1 - num2; + } +} +class AnimationController { + public var parent:Sprite; + public var curAnim:Animation; + public var curFrame:Int = 0; + + public var name(get, set):String; + public var paused(get, set):Bool; + public var finished(get, set):Bool; + + public var frames:Array; + public var animations:Map; + + public function new(parent:Sprite){ + this.parent = parent; + animations = new Map(); + } + + public function exists(name:String) + return animations.exists(name); + public function clear(){ + curAnim = null; + animations = new Map(); + } + public function add(Name:String, Frames:Array, FrameRate:Int = 24, Looped:Bool = true){ + var framesArray:Array = Frames; + var i:Int = framesArray.length-1; + while (i >= 0){ + if (framesArray[i] >= frames.length){ + if (framesArray == Frames) framesArray = Frames.copy(); + framesArray.splice(i, 1); + } + i--; + } + if (framesArray.length > 0) + animations.set(Name, new Animation(Name, framesArray, FrameRate, Looped)); + } + public function remove(Name:String){ + if (animations.exists(Name)) + animations.remove(Name); + } + + public function getFrameIndex(Frame:Frame):Int + return frames.indexOf(Frame); + + function findFrame(Prefix:String, Index:Int, PostFix:String):Int{ + for (i in 0...frames.length){ + if (StringTools.startsWith(frames[i].name, Prefix) && StringTools.endsWith(frames[i].name, PostFix)){ + var index:Null = Std.parseInt(frames[i].name.substring(Prefix.length, frames[i].name.length - PostFix.length)); + if (index != null && index == Index) + return i; + } + } + return -1; + } + + function indicesHelper(addTo:Array, Prefix:String, Indices:Array, Postfix:String){ + for (i in Indices){ + var toAdd:Int = findFrame(Prefix, i, Postfix); + if (toAdd != -1) + addTo.push(toAdd); + } + } + function prefixHelper(addTo:Array, Frames:Array, Prefix:String){ + var name = Frames[0].name; + var postIndex:Int = name.indexOf(".", Prefix.length); + var postFix:String = name.substring(postIndex == -1 ? name.length : postIndex, name.length); + Frame.sort(Frames, Prefix.length, postFix.length); + + for (animFrame in Frames) + addTo.push(getFrameIndex(animFrame)); + } + function findByPrefix(Frames:Array, Prefix:String){ + for (frame in frames) + if (frame.name != null && StringTools.startsWith(frame.name, Prefix)) + Frames.push(frame); + } + + + public function addByPrefix(Name:String, Prefix:String, FrameRate:Int = 24, Looped:Bool = true){ + if (frames != null){ + var Frames:Array = new Array(); + findByPrefix(Frames, Prefix); + + if (Frames.length > 0){ + var frameIndices:Array = new Array(); + prefixHelper(frameIndices, Frames, Prefix); + + if (frameIndices.length > 0) + animations.set(Name, new Animation(Name, frameIndices, FrameRate, Looped)); + } + } + } + public function addByIndices(Name:String, Prefix:String, Indices:Array, PostFix:String, FrameRate:Int = 24, Looped:Bool = true){ + if (frames != null) { + var frameIndices:Array = new Array(); + indicesHelper(frameIndices, Prefix, Indices, PostFix); + + if (frameIndices.length > 0) + animations.set(Name, new Animation(Name, frameIndices, FrameRate, Looped)); + } + } + + public function play(Name:String, Force:Bool = false){ + if (Name == null){ + if (curAnim == null) + curAnim.stop(); + curAnim = null; + } + if (Name == null || animations.get(Name) == null) + return; + + curAnim = animations.get(Name); + curAnim.play(Force); + } + + public function get_name():String + return if (curAnim == null) null; else curAnim.name; + public function get_paused():Bool + return if (curAnim == null) false; else curAnim.paused; + public function get_finished():Bool + return if (curAnim == null) true; else curAnim.finished; + + public function set_finished(v:Bool):Bool{ + if (curAnim != null && v) + curAnim.finish(); + return v; + } + public function set_paused(v):Bool{ + if (curAnim != null){ + if (v) + curAnim.pause(); + else + curAnim.resume(); + } + return v; + } + public function set_name(name:String):String{ + play(name); + return name; + } +} \ No newline at end of file