diff --git a/README.md b/README.md index 8f919d231..39c098af5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This game was made with love to Newgrounds and it's community. Extra love to Tom **PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME** -To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game]() guide. +To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game](/docs/compiling.md) guide. # Contributing diff --git a/assets b/assets index 160acbd8a..599169f47 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 160acbd8a854a9f677ef7587396340e79a5ea6ca +Subproject commit 599169f4786d1feb62a089d25112035977647805 diff --git a/docs/COMPILING.md b/docs/COMPILING.md new file mode 100644 index 000000000..7f9c0cdb8 --- /dev/null +++ b/docs/COMPILING.md @@ -0,0 +1,19 @@ +# Compiling Friday Night Funkin' + +0. Setup + - Download Haxe from [Haxe.org](https://haxe.org) +1. Cloning the Repository: Make sure when you clone, you clone the submodules to get the assets repo: + - `git clone --recurse-submodules https://github.com/FunkinCrew/funkin-secret.git` + - If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way. +2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`) +3. Install all haxelibs of the current branch by running `hmm install` +4. Platform setup + - For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe) + - When prompted, select "Individual Components" and make sure to download the following: + - MSVC v143 VS 2022 C++ x64/x86 build tools + - Windows 10/11 SDK + - Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/) + - Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/) + - HTML5: Compiles without any extra setup +5. If you are targeting for native, you likely need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` +6. `lime test PLATFORM` ! diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 27208689b..3b93bab64 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -10,3 +10,6 @@ - `Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.` - This is a bug specific to HTML5. Simply perform the steps listed (don't forget to restart the IDE too). + +- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege` + - This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch. diff --git a/hmm.json b/hmm.json index b529a520d..e1acf6c87 100644 --- a/hmm.json +++ b/hmm.json @@ -11,7 +11,7 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "25c84b29665329f7c6366342542a3978f29300ee", + "ref": "4d054bd10b05bb1309a0ba3427ffa5378e0b4b99", "url": "https://github.com/FunkinCrew/flixel" }, { diff --git a/source/funkin/graphics/FunkinCamera.hx b/source/funkin/graphics/FunkinCamera.hx new file mode 100644 index 000000000..f80e799ea --- /dev/null +++ b/source/funkin/graphics/FunkinCamera.hx @@ -0,0 +1,257 @@ +package funkin.graphics; + +import flash.geom.ColorTransform; +import flixel.FlxCamera; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFrame; +import flixel.math.FlxMatrix; +import flixel.math.FlxRect; +import flixel.system.FlxAssets.FlxShader; +import funkin.graphics.shaders.RuntimeCustomBlendShader; +import funkin.graphics.framebuffer.BitmapDataUtil; +import funkin.graphics.framebuffer.FixedBitmapData; +import openfl.Lib; +import openfl.display.BitmapData; +import openfl.display.BlendMode; +import openfl.display3D.textures.TextureBase; +import openfl.filters.BitmapFilter; +import openfl.filters.ShaderFilter; + +/** + * A FlxCamera with additional powerful features: + * - Grab the camera screen as a `BitmapData` and use it as a texture + * - Support `sprite.blend = DARKEN/HARDLIGHT/LIGHTEN/OVERLAY` to apply visual effects using certain sprites + * - NOTE: Several other blend modes work without FunkinCamera. Some still do not work. + * - NOTE: Framerate-independent camera tweening is fixed in Flixel 6.x. Rest in peace, SwagCamera. + */ +@:access(openfl.display.DisplayObject) +@:access(openfl.display.BitmapData) +@:access(openfl.display3D.Context3D) +@:access(openfl.display3D.textures.TextureBase) +@:access(flixel.graphics.FlxGraphic) +@:access(flixel.graphics.frames.FlxFrame) +class FunkinCamera extends FlxCamera +{ + final grabbed:Array = []; + final texturePool:Array = []; + + final bgTexture:TextureBase; + final bgBitmap:BitmapData; + final bgFrame:FlxFrame; + + final customBlendShader:RuntimeCustomBlendShader; + final customBlendFilter:ShaderFilter; + + var filtersApplied:Bool = false; + var bgItemCount:Int = 0; + + public var shouldDraw:Bool = true; + + public function new(x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0) + { + super(x, y, width, height, zoom); + bgTexture = pickTexture(width, height); + bgBitmap = FixedBitmapData.fromTexture(bgTexture); + bgFrame = new FlxFrame(new FlxGraphic('', null)); + bgFrame.parent.bitmap = bgBitmap; + bgFrame.frame = new FlxRect(); + customBlendShader = new RuntimeCustomBlendShader(); + customBlendFilter = new ShaderFilter(customBlendShader); + } + + /** + * Grabs the camera screen and returns it as a `BitmapData`. The returned bitmap + * will not be referred by the camera so, changing it will not affect the scene. + * The returned bitmap **will be reused in the next frame**, so the content is available + * only in the current frame. + * @param applyFilters if this is `true`, the camera's filters will be applied to the grabbed bitmap, + * and the camera's filters will be disabled until the beginning of the next frame + * @param isolate if this is `true`, sprites to be rendered will only be rendered to the grabbed bitmap, + * and the grabbed bitmap will not include any previously rendered sprites + * @return the grabbed bitmap data + */ + public function grabScreen(applyFilters:Bool, isolate:Bool = false):BitmapData + { + final texture = pickTexture(width, height); + final bitmap = FixedBitmapData.fromTexture(texture); + squashTo(bitmap, applyFilters, isolate); + grabbed.push(bitmap); + return bitmap; + } + + /** + * Applies the filter immediately to the camera. This will be done independently from + * the camera's filters. This method can only be called after the first `grabScreen` + * in the frame. + * @param filter the filter + */ + public function applyFilter(filter:BitmapFilter):Void + { + if (grabbed.length == 0) + { + FlxG.log.error('grab screen before you can apply a filter!'); + return; + } + BitmapDataUtil.applyFilter(bgBitmap, filter); + } + + function squashTo(bitmap:BitmapData, applyFilters:Bool, isolate:Bool, clearScreen:Bool = false):Void + { + if (applyFilters && isolate) + { + FlxG.log.error('cannot apply filters while isolating!'); + } + if (filtersApplied && applyFilters) + { + FlxG.log.warn('filters already applied!'); + } + static final matrix = new FlxMatrix(); + + // resize the background bitmap if needed + if (bgTexture.__width != width || bgTexture.__height != height) + { + BitmapDataUtil.resizeTexture(bgTexture, width, height); + bgBitmap.__resize(width, height); + bgFrame.parent.bitmap = bgBitmap; + } + + // grab the bitmap + renderSkipping(isolate ? bgItemCount : 0); + bitmap.fillRect(bitmap.rect, 0); + matrix.setTo(1, 0, 0, 1, flashSprite.x, flashSprite.y); + if (applyFilters) + { + bitmap.draw(flashSprite, matrix); + flashSprite.filters = null; + filtersApplied = true; + } + else + { + final tmp = flashSprite.filters; + flashSprite.filters = null; + bitmap.draw(flashSprite, matrix); + flashSprite.filters = tmp; + } + + if (!isolate) + { + // also copy to the background bitmap + bgBitmap.fillRect(bgBitmap.rect, 0); + bgBitmap.draw(bitmap); + } + + if (clearScreen) + { + // clear graphics data + super.clearDrawStack(); + canvas.graphics.clear(); + } + + // render the background bitmap + bgFrame.frame.set(0, 0, width, height); + matrix.setTo(viewWidth / width, 0, 0, viewHeight / height, viewMarginLeft, viewMarginTop); + drawPixels(bgFrame, matrix); + + // count background draw items for future isolation + bgItemCount = 0; + { + var item = _headOfDrawStack; + while (item != null) + { + item = item.next; + bgItemCount++; + } + } + } + + function renderSkipping(count:Int):Void + { + var item = _headOfDrawStack; + while (item != null) + { + if (--count < 0) item.render(this); + item = item.next; + } + } + + override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, + ?shader:FlxShader):Void + { + if (!shouldDraw) return; + + if ( switch blend + { + case DARKEN | HARDLIGHT | LIGHTEN | OVERLAY: true; + case _: false; + }) + { + // squash the screen + grabScreen(false); + // render without blend + super.drawPixels(frame, pixels, matrix, transform, null, smoothing, shader); + // get the isolated bitmap + final isolated = grabScreen(false, true); + // apply fullscreen blend + customBlendShader.blend = blend; + customBlendShader.source = isolated; + customBlendShader.updateViewInfo(FlxG.width, FlxG.height, this); + applyFilter(customBlendFilter); + } + else + { + super.drawPixels(frame, pixels, matrix, transform, blend, smoothing, shader); + } + } + + override function destroy():Void + { + super.destroy(); + disposeTextures(); + } + + override function clearDrawStack():Void + { + super.clearDrawStack(); + // also clear grabbed bitmaps + for (bitmap in grabbed) + { + texturePool.push(bitmap.__texture); + bitmap.dispose(); // this doesn't release the texture + } + grabbed.clear(); + // clear filters applied flag + filtersApplied = false; + bgItemCount = 0; + } + + function pickTexture(width:Int, height:Int):TextureBase + { + // zero-sized textures will be problematic + width = width < 1 ? 1 : width; + height = height < 1 ? 1 : height; + if (texturePool.length > 0) + { + final res = texturePool.pop(); + BitmapDataUtil.resizeTexture(res, width, height); + return res; + } + return Lib.current.stage.context3D.createTexture(width, height, BGRA, true); + } + + function disposeTextures():Void + { + trace('disposing textures'); + for (bitmap in grabbed) + { + bitmap.dispose(); + } + grabbed.clear(); + for (texture in texturePool) + { + texture.dispose(); + } + texturePool.resize(0); + bgTexture.dispose(); + bgBitmap.dispose(); + } +} diff --git a/source/funkin/graphics/framebuffer/BitmapDataUtil.hx b/source/funkin/graphics/framebuffer/BitmapDataUtil.hx new file mode 100644 index 000000000..7b49705e0 --- /dev/null +++ b/source/funkin/graphics/framebuffer/BitmapDataUtil.hx @@ -0,0 +1,123 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxG; +import openfl.Lib; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Sprite; +import openfl.display3D.Context3DTextureFormat; +import openfl.display3D.textures.TextureBase; +import openfl.filters.BitmapFilter; + +/** + * Provides cool stuff for `BitmapData`s that have a hardware texture internally. + */ +@:access(openfl.display.BitmapData) +@:access(openfl.display3D.textures.TextureBase) +@:access(openfl.display3D.Context3D) +class BitmapDataUtil +{ + static function getCache():{sprite:Sprite, bitmap:Bitmap} + { + static var cache:{sprite:Sprite, bitmap:Bitmap} = null; + if (cache == null) + { + final sprite = new Sprite(); + final bitmap = new Bitmap(); + sprite.addChild(bitmap); + cache = + { + sprite: sprite, + bitmap: bitmap + } + } + return cache; + } + + /** + * Applies a bitmap filter to a bitmap immediately. The bitmap filter may refer + * the bitmap itself as a shader input. + * @param bitmap the bitmap data + * @param filter the bitmap filter + */ + public static function applyFilter(bitmap:BitmapData, filter:BitmapFilter):Void + { + hardwareCheck(bitmap); + final cache = getCache(); + cache.bitmap.bitmapData = bitmap; + cache.sprite.filters = [filter]; + bitmap.draw(cache.sprite); + } + + /** + * Creates a bitmap with a hardware texture. + * @param width the width + * @param height the height + * @param format the format if the internal texture + * @return the bitmap + */ + public static function create(width:Int, height:Int, format:Context3DTextureFormat = BGRA):FixedBitmapData + { + final texture = Lib.current.stage.context3D.createTexture(width, height, format, true); + return FixedBitmapData.fromTexture(texture); + } + + /** + * Resizes the bitmap. + * @param bitmap the bitmap data + * @param width the width + * @param height the height + */ + public static function resize(bitmap:BitmapData, width:Int, height:Int):Void + { + hardwareCheck(bitmap); + if (bitmap.width == width && bitmap.height == height) return; + bitmap.width = width; + bitmap.height = height; + resizeTexture(bitmap.__texture, width, height); + } + + /** + * Resizes the texture. + * @param texture the texture + * @param width the width + * @param height the height + */ + public static function resizeTexture(texture:TextureBase, width:Int, height:Int):Void + { + if (texture.__width == width && texture.__height == height) return; + texture.__width = width; + texture.__height = height; + final context = texture.__context; + final gl = context.gl; + context.__bindGLTexture2D(texture.__textureID); + gl.texImage2D(gl.TEXTURE_2D, 0, texture.__internalFormat, width, height, 0, texture.__format, gl.UNSIGNED_BYTE, null); + context.__bindGLTexture2D(null); + } + + /** + * Copies the content of `src` to `dst`. The destination bitmap `dst` will be resized + * so that it has the same size as `src`. + * @param dst the destination bitmap + * @param src the source bitmap + */ + public static function copy(dst:BitmapData, src:BitmapData):Void + { + hardwareCheck(dst); + hardwareCheck(src); + final cache = getCache(); + cache.bitmap.bitmapData = src; + cache.sprite.filters = null; + resize(dst, src.width, src.height); + dst.fillRect(dst.rect, 0); + dst.draw(cache.sprite); + } + + static function hardwareCheck(bitmap:BitmapData):Void + { + if (bitmap.readable) + { + FlxG.log.error('do not use `BitmapDataUtil` for non-GPU bitmaps!'); + } + } +} diff --git a/source/funkin/graphics/framebuffer/FixedBitmapData.hx b/source/funkin/graphics/framebuffer/FixedBitmapData.hx new file mode 100644 index 000000000..00b39ce1c --- /dev/null +++ b/source/funkin/graphics/framebuffer/FixedBitmapData.hx @@ -0,0 +1,42 @@ +package funkin.graphics.framebuffer; + +import openfl.display.BitmapData; +import openfl.display.DisplayObject; +import openfl.display.DisplayObjectContainer; +import openfl.display.IBitmapDrawable; +import openfl.display.OpenGLRenderer; +import openfl.display3D.textures.TextureBase; + +/** + * `BitmapData` is kinda broken so I fixed it. + */ +@:access(openfl.display3D.textures.TextureBase) +@:access(openfl.display.OpenGLRenderer) +class FixedBitmapData extends BitmapData +{ + override function __drawGL(source:IBitmapDrawable, renderer:OpenGLRenderer):Void + { + if (Std.isOfType(source, DisplayObject)) + { + final object:DisplayObjectContainer = cast source; + renderer.__stage = object.stage; + } + super.__drawGL(source, renderer); + } + + /** + * Never use `BitmapData.fromTexture`, always use this. + * @param texture the texture + * @return the bitmap data + */ + public static function fromTexture(texture:TextureBase):FixedBitmapData + { + if (texture == null) return null; + final bitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0); + bitmapData.readable = false; + bitmapData.__texture = texture; + bitmapData.__textureContext = texture.__textureContext; + bitmapData.image = null; + return bitmapData; + } +} diff --git a/source/funkin/graphics/framebuffer/FrameBuffer.hx b/source/funkin/graphics/framebuffer/FrameBuffer.hx new file mode 100644 index 000000000..e99f72b77 --- /dev/null +++ b/source/funkin/graphics/framebuffer/FrameBuffer.hx @@ -0,0 +1,132 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxSprite; +import flixel.FlxCamera; +import flixel.util.FlxColor; +import openfl.Lib; +import openfl.display.BitmapData; +import openfl.display3D.textures.TextureBase; +import openfl.geom.Matrix; +import openfl.geom.Rectangle; + +/** + * A single frame buffer. Used by `FrameBufferManager`. + */ +class FrameBuffer +{ + /** + * The bitmap data of the frame buffer. + */ + public var bitmap(default, null):BitmapData = null; + + var texture:TextureBase; + final camera:FlxCamera; + final spriteCopies:Array = []; + + public function new() + { + camera = new FlxCamera(); + camera.antialiasing = false; + camera.bgColor = FlxColor.TRANSPARENT; + @:privateAccess camera.flashSprite.stage = Lib.current.stage; + } + + /** + * Creates a frame buffer with the given size. + * @param width the width + * @param height the height + * @param bgColor the background color + */ + public function create(width:Int, height:Int, bgColor:FlxColor):Void + { + dispose(); + texture = Lib.current.stage.context3D.createTexture(width, height, BGRA, true); + bitmap = FixedBitmapData.fromTexture(texture); + camera.bgColor = bgColor; + } + + /** + * Makes the internal camera follows the target camera. + * @param target the target camera + */ + public function follow(target:FlxCamera):Void + { + camera.x = target.x; + camera.y = target.y; + camera.width = target.width; + camera.height = target.height; + camera.scroll.x = target.scroll.x; + camera.scroll.y = target.scroll.y; + camera.setScale(target.scaleX, target.scaleY); + } + + /** + * Locks the frame buffer and clears the buffer. + */ + @:access(flixel.FlxCamera) + public function lock():Void + { + camera.clearDrawStack(); + camera.canvas.graphics.clear(); + camera.fill(camera.bgColor.to24Bit(), camera.useBgAlphaBlending, camera.bgColor.alphaFloat); + #if FLX_DEBUG + camera.debugLayer.graphics.clear(); + #end + } + + /** + * Renders all sprite copies. + */ + @:access(flixel.FlxCamera) + public function render():Void + { + for (spriteCopy in spriteCopies) + { + spriteCopy.render(camera); + } + camera.render(); + } + + /** + * Unlocks the frame buffer and makes the bitmap ready to use. + */ + public function unlock():Void + { + bitmap.fillRect(new Rectangle(0, 0, bitmap.width, bitmap.height), 0); + bitmap.draw(camera.flashSprite, new Matrix(1, 0, 0, 1, camera.flashSprite.x, camera.flashSprite.y)); + } + + /** + * Diposes stuff. Call `create` again if you want to reuse the instance. + */ + public function dispose():Void + { + if (texture != null) + { + texture.dispose(); + texture = null; + bitmap.dispose(); + bitmap = null; + } + spriteCopies.resize(0); + } + + /** + * Adds a sprite copy to the frame buffer. + * @param spriteCopy the sprite copy + */ + public function addSpriteCopy(spriteCopy:SpriteCopy):Void + { + spriteCopies.push(spriteCopy); + } + + /** + * Adds the sprite to the frame buffer. The sprite will only be seen from + * the frame buffer. + * @param sprite the sprite + */ + public function moveSprite(sprite:FlxSprite):Void + { + sprite.cameras = [camera]; + } +} diff --git a/source/funkin/graphics/framebuffer/FrameBufferManager.hx b/source/funkin/graphics/framebuffer/FrameBufferManager.hx new file mode 100644 index 000000000..4d484fb8f --- /dev/null +++ b/source/funkin/graphics/framebuffer/FrameBufferManager.hx @@ -0,0 +1,127 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.util.FlxColor; +import openfl.display.BitmapData; + +/** + * Manages frame buffers and gives access to each frame buffer. + */ +class FrameBufferManager +{ + final camera:FlxCamera; + final frameBufferMap:Map = []; + + /** + * Creates a frame buffer manager that targets `camera`. + * @param camera the target camera. + */ + public function new(camera:FlxCamera) + { + this.camera = camera; + } + + /** + * Creates a new frame buffer with a name. + * @param name the name + * @param bgColor the background color + * @return the bitmap data of the frame buffer. the bitmap data instance + * will not be changed through frame buffer updates. + */ + public function createFrameBuffer(name:String, bgColor:FlxColor):BitmapData + { + if (frameBufferMap.exists(name)) + { + FlxG.log.warn('frame buffer "$name" already exists'); + frameBufferMap[name].dispose(); + frameBufferMap.remove(name); + } + final fb = new FrameBuffer(); + fb.create(camera.width, camera.height, bgColor); + frameBufferMap[name] = fb; + return fb.bitmap; + } + + /** + * Adds a copy of the sprite to the frame buffer. + * @param name the name of the frame buffer + * @param sprite the sprite + * @param color if this is not `null`, the sprite will be filled with the color. + * if this is `null`, the sprite will keep its original color. + */ + public function copySpriteTo(name:String, sprite:FlxSprite, color:Null = null):Void + { + if (!frameBufferMap.exists(name)) + { + FlxG.log.warn('frame buffer "$name" does not exist'); + return; + } + frameBufferMap[name].addSpriteCopy(new SpriteCopy(sprite, color)); + } + + /** + * Adds the sprite to the frame buffer. The sprite will only be seen from the frame buffer. + * @param name the name of the frame buffer + * @param sprite the sprite + */ + public function moveSpriteTo(name:String, sprite:FlxSprite):Void + { + if (!frameBufferMap.exists(name)) + { + FlxG.log.warn('frame buffer "$name" does not exist'); + return; + } + frameBufferMap[name].moveSprite(sprite); + } + + /** + * Call this before drawing anything. + */ + public function lock():Void + { + for (_ => fb in frameBufferMap) + { + fb.follow(camera); + fb.lock(); + } + } + + /** + * Unlocks the frame buffers. This updates the bitmap data of each frame buffer. + */ + public function unlock():Void + { + for (_ => fb in frameBufferMap) + { + fb.render(); + } + for (_ => fb in frameBufferMap) + { + fb.unlock(); + } + } + + /** + * Returns the bitmap data of the frame buffer + * @param name the name of the frame buffer + * @return the bitmap data + */ + public function getFrameBuffer(name:String):BitmapData + { + return frameBufferMap[name].bitmap; + } + + /** + * Disposes all frame buffers. The instance can be reused. + */ + public function dispose():Void + { + for (_ => fb in frameBufferMap) + { + fb.dispose(); + } + frameBufferMap.clear(); + } +} diff --git a/source/funkin/graphics/framebuffer/SpriteCopy.hx b/source/funkin/graphics/framebuffer/SpriteCopy.hx new file mode 100644 index 000000000..ea7de69dc --- /dev/null +++ b/source/funkin/graphics/framebuffer/SpriteCopy.hx @@ -0,0 +1,59 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxCamera; +import flixel.FlxSprite; +import flixel.util.FlxColor; + +/** + * A copy of a `FlxSprite` with a specified color. Used to render the sprite to a frame buffer. + */ +class SpriteCopy +{ + final sprite:FlxSprite; + final color:Null; + + public function new(sprite:FlxSprite, color:Null) + { + this.sprite = sprite; + this.color = color; + } + + /** + * Renders the copy to the camera. + * @param camera the camera + */ + @:access(flixel.FlxSprite) + public function render(camera:FlxCamera):Void + { + if (color == null) + { + final tmpCameras = sprite._cameras; + sprite._cameras = [camera]; + sprite.draw(); + sprite._cameras = tmpCameras; + } + else + { + final rMult = sprite.colorTransform.redMultiplier; + final gMult = sprite.colorTransform.greenMultiplier; + final bMult = sprite.colorTransform.blueMultiplier; + final aMult = sprite.colorTransform.alphaMultiplier; + final rOff = Std.int(sprite.colorTransform.redOffset); + final gOff = Std.int(sprite.colorTransform.greenOffset); + final bOff = Std.int(sprite.colorTransform.blueOffset); + final aOff = Std.int(sprite.colorTransform.alphaOffset); + final tmpCameras = sprite._cameras; + final tmpShader = sprite.shader; + + sprite._cameras = [camera]; + sprite.shader = null; + + sprite.setColorTransform(0, 0, 0, 1, color.red, color.green, color.blue, 0); + sprite.draw(); + + sprite._cameras = tmpCameras; + sprite.shader = tmpShader; + sprite.setColorTransform(rMult, gMult, bMult, aMult, rOff, gOff, bOff, aOff); + } + } +} diff --git a/source/funkin/graphics/shaders/PuddleShader.hx b/source/funkin/graphics/shaders/PuddleShader.hx new file mode 100644 index 000000000..352568737 --- /dev/null +++ b/source/funkin/graphics/shaders/PuddleShader.hx @@ -0,0 +1,12 @@ +package funkin.graphics.shaders; + +import flixel.addons.display.FlxRuntimeShader; +import openfl.Assets; + +class PuddleShader extends FlxRuntimeShader +{ + public function new() + { + super(Assets.getText(Paths.frag('puddle'))); + } +} diff --git a/source/funkin/graphics/shaders/RuntimeCustomBlendShader.hx b/source/funkin/graphics/shaders/RuntimeCustomBlendShader.hx new file mode 100644 index 000000000..a07124d23 --- /dev/null +++ b/source/funkin/graphics/shaders/RuntimeCustomBlendShader.hx @@ -0,0 +1,29 @@ +package funkin.graphics.shaders; + +import openfl.display.BitmapData; +import openfl.display.BlendMode; +import openfl.utils.Assets; + +class RuntimeCustomBlendShader extends RuntimePostEffectShader +{ + public var source(default, set):BitmapData; + + function set_source(value:BitmapData):BitmapData + { + this.setBitmapData("source", value); + return source = value; + } + + public var blend(default, set):BlendMode; + + function set_blend(value:BlendMode):BlendMode + { + this.setInt("blendMode", cast value); + return blend = value; + } + + public function new() + { + super(Assets.getText("assets/shaders/customBlend.frag")); + } +} diff --git a/source/funkin/graphics/shaders/RuntimePostEffectShader.hx b/source/funkin/graphics/shaders/RuntimePostEffectShader.hx new file mode 100644 index 000000000..9f49da075 --- /dev/null +++ b/source/funkin/graphics/shaders/RuntimePostEffectShader.hx @@ -0,0 +1,105 @@ +package funkin.graphics.shaders; + +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.addons.display.FlxRuntimeShader; +import lime.graphics.opengl.GLProgram; +import lime.utils.Log; + +class RuntimePostEffectShader extends FlxRuntimeShader +{ + @:glVertexHeader(' + // normalized screen coord + // (0, 0) is the top left of the window + // (1, 1) is the bottom right of the window + varying vec2 screenCoord; + ', true) + @:glVertexBody(' + screenCoord = vec2( + openfl_TextureCoord.x > 0.0 ? 1.0 : 0.0, + openfl_TextureCoord.y > 0.0 ? 1.0 : 0.0 + ); + ') + @:glFragmentHeader(' + // normalized screen coord + // (0, 0) is the top left of the window + // (1, 1) is the bottom right of the window + varying vec2 screenCoord; + + // equals (FlxG.width, FlxG.height) + uniform vec2 uScreenResolution; + + // equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom) + uniform vec4 uCameraBounds; + + // screen coord -> world coord conversion + // returns world coord in px + vec2 screenToWorld(vec2 screenCoord) { + float left = uCameraBounds.x; + float top = uCameraBounds.y; + float right = uCameraBounds.z; + float bottom = uCameraBounds.w; + vec2 scale = vec2(right - left, bottom - top); + vec2 offset = vec2(left, top); + return screenCoord * scale + offset; + } + + // world coord -> screen coord conversion + // returns normalized screen coord + vec2 worldToScreen(vec2 worldCoord) { + float left = uCameraBounds.x; + float top = uCameraBounds.y; + float right = uCameraBounds.z; + float bottom = uCameraBounds.w; + vec2 scale = vec2(right - left, bottom - top); + vec2 offset = vec2(left, top); + return (worldCoord - offset) / scale; + } + + // internally used to get the maximum `openfl_TextureCoordv` + vec2 bitmapCoordScale() { + return openfl_TextureCoordv / screenCoord; + } + + // internally used to compute bitmap coord + vec2 screenToBitmap(vec2 screenCoord) { + return screenCoord * bitmapCoordScale(); + } + + // samples the frame buffer using a screen coord + vec4 sampleBitmapScreen(vec2 screenCoord) { + return texture2D(bitmap, screenToBitmap(screenCoord)); + } + + // samples the frame buffer using a world coord + vec4 sampleBitmapWorld(vec2 worldCoord) { + return sampleBitmapScreen(worldToScreen(worldCoord)); + } + ', true) + public function new(fragmentSource:String = null, glVersion:String = null) + { + super(fragmentSource, null, glVersion); + uScreenResolution.value = [FlxG.width, FlxG.height]; + } + + // basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good + public function updateViewInfo(screenWidth:Float, screenHeight:Float, camera:FlxCamera):Void + { + uScreenResolution.value = [screenWidth, screenHeight]; + uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom]; + } + + override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram + { + try + { + final res = super.__createGLProgram(vertexSource, fragmentSource); + return res; + } + catch (error) + { + Log.warn(error); // prevent the app from dying immediately + return null; + } + } +} diff --git a/source/funkin/graphics/shaders/RuntimeRainShader.hx b/source/funkin/graphics/shaders/RuntimeRainShader.hx new file mode 100644 index 000000000..05bc68f72 --- /dev/null +++ b/source/funkin/graphics/shaders/RuntimeRainShader.hx @@ -0,0 +1,144 @@ +package funkin.graphics.shaders; + +import flixel.system.FlxAssets.FlxShader; +import openfl.display.BitmapData; +import openfl.display.ShaderParameter; +import openfl.display.ShaderParameterType; +import openfl.utils.Assets; + +typedef Light = +{ + var position:Array; + var color:Array; + var radius:Float; +} + +class RuntimeRainShader extends RuntimePostEffectShader +{ + static final MAX_LIGHTS:Int = 8; + + public var lights:Array< + { + position:ShaderParameter, + color:ShaderParameter, + radius:ShaderParameter, + }>; + + public var time(default, set):Float = 1; + + function set_time(value:Float):Float + { + this.setFloat('uTime', value); + return time = value; + } + + // The scale of the rain depends on the world coordinate system, so higher resolution makes + // the raindrops smaller. This parameter can be used to adjust the total scale of the scene. + // The size of the raindrops is proportional to the value of this parameter. + public var scale(default, set):Float = 1; + + function set_scale(value:Float):Float + { + this.setFloat('uScale', value); + return scale = value; + } + + // The intensity of the rain. Zero means no rain and one means the maximum amount of rain. + public var intensity(default, set):Float = 0.5; + + function set_intensity(value:Float):Float + { + this.setFloat('uIntensity', value); + return intensity = value; + } + + // the y coord of the puddle, used to mirror things + public var puddleY(default, set):Float = 0; + + function set_puddleY(value:Float):Float + { + this.setFloat('uPuddleY', value); + return puddleY = value; + } + + // the y scale of the puddle, the less this value the more the puddle effects squished + public var puddleScaleY(default, set):Float = 0; + + function set_puddleScaleY(value:Float):Float + { + this.setFloat('uPuddleScaleY', value); + return puddleScaleY = value; + } + + public var blurredScreen(default, set):BitmapData; + + function set_blurredScreen(value:BitmapData):BitmapData + { + this.setBitmapData('uBlurredScreen', value); + return blurredScreen = value; + } + + public var mask(default, set):BitmapData; + + function set_mask(value:BitmapData):BitmapData + { + this.setBitmapData('uMask', value); + return mask = value; + } + + public var lightMap(default, set):BitmapData; + + function set_lightMap(value:BitmapData):BitmapData + { + this.setBitmapData('uLightMap', value); + return lightMap = value; + } + + public var numLights(default, set):Int = 0; + + function set_numLights(value:Int):Int + { + this.setInt('numLights', value); + return numLights = value; + } + + public function new() + { + super(Assets.getText(Paths.frag('rain'))); + } + + public function update(elapsed:Float):Void + { + time += elapsed; + } + + override function __processGLData(source:String, storageType:String):Void + { + super.__processGLData(source, storageType); + if (storageType == 'uniform') + { + lights = [ + for (i in 0...MAX_LIGHTS) + { + position: addFloatUniform('lights[$i].position', 2), + color: addFloatUniform('lights[$i].color', 3), + radius: addFloatUniform('lights[$i].radius', 1), + } + ]; + } + } + + @:access(openfl.display.ShaderParameter) + function addFloatUniform(name:String, length:Int):ShaderParameter + { + final res = new ShaderParameter(); + res.name = name; + res.type = [null, FLOAT, FLOAT2, FLOAT3, FLOAT4][length]; + res.__arrayLength = 1; + res.__isFloat = true; + res.__isUniform = true; + res.__length = length; + __paramFloat.push(res); + return res; + } +} diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 5ca6b03db..02055d4ed 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -6,6 +6,7 @@ import funkin.util.Constants; import funkin.Paths; import funkin.Preferences; import flixel.FlxG; // This one in particular causes a compile error if you're using macros. +import flixel.system.debug.watch.Tracker; // These are great. using Lambda; diff --git a/source/funkin/modding/base/ScriptedFlxAtlasSprite.hx b/source/funkin/modding/base/ScriptedFlxAtlasSprite.hx new file mode 100644 index 000000000..e9ffd450d --- /dev/null +++ b/source/funkin/modding/base/ScriptedFlxAtlasSprite.hx @@ -0,0 +1,8 @@ +package funkin.modding.base; + +/** + * A script that can be tied to an FlxAtlasSprite + * Create a scripted class that extends FlxAtlasSprite to use this. + */ +@:hscriptClass +class ScriptedFlxAtlasSprite extends funkin.graphics.adobeanimate.FlxAtlasSprite implements HScriptedClass {} diff --git a/source/funkin/modding/base/ScriptedFunkinSprite.hx b/source/funkin/modding/base/ScriptedFunkinSprite.hx new file mode 100644 index 000000000..dd8d15007 --- /dev/null +++ b/source/funkin/modding/base/ScriptedFunkinSprite.hx @@ -0,0 +1,8 @@ +package funkin.modding.base; + +/** + * A script that can be tied to an FlxSprite. + * Create a scripted class that extends FlxSprite to use this. + */ +@:hscriptClass +class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements HScriptedClass {} diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 10e59f078..023b8d5be 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -38,7 +38,8 @@ class PauseSubState extends MusicBeatSubState var practiceText:FlxText; - var exitingToMenu:Bool = false; + public var exitingToMenu:Bool = false; + var bg:FlxSprite; var metaDataGrp:FlxTypedGroup; @@ -234,7 +235,7 @@ class PauseSubState extends MusicBeatSubState if (PlayStatePlaylist.isStoryMode) { PlayStatePlaylist.reset(); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState())); + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); } else { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 74347765e..be4fab254 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,68 +1,79 @@ package funkin.play; -import funkin.ui.SwagCamera; -import flixel.addons.transition.FlxTransitionableSubState; -import funkin.ui.debug.charting.ChartEditorState; -import haxe.Int64; -import funkin.play.notes.notestyle.NoteStyle; -import funkin.data.notestyle.NoteStyleData; -import funkin.data.notestyle.NoteStyleRegistry; import flixel.addons.display.FlxPieDial; -import flixel.addons.transition.Transition; +import flixel.addons.display.FlxPieDial; import flixel.addons.transition.FlxTransitionableState; +import flixel.addons.transition.FlxTransitionableState; +import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.Transition; +import flixel.addons.transition.Transition; import flixel.FlxCamera; import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; -import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; -import funkin.play.components.ComboMilestone; import flixel.math.FlxPoint; -import funkin.play.components.HealthIcon; -import funkin.ui.MusicBeatSubState; import flixel.math.FlxRect; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.ui.FlxBar; import flixel.util.FlxColor; -import funkin.api.newgrounds.NGio; import flixel.util.FlxTimer; +import funkin.api.newgrounds.NGio; import funkin.audio.VoicesGroup; -import funkin.save.Save; +import funkin.audio.VoicesGroup; +import funkin.data.dialogue.ConversationRegistry; +import funkin.data.event.SongEventRegistry; +import funkin.data.notestyle.NoteStyleData; +import funkin.data.notestyle.NoteStyleRegistry; +import funkin.data.notestyle.NoteStyleRegistry; +import funkin.data.song.SongData.SongCharacterData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongRegistry; +import funkin.data.stage.StageRegistry; import funkin.Highscore.Tallies; import funkin.input.PreciseInputManager; import funkin.modding.events.ScriptEvent; -import funkin.ui.mainmenu.MainMenuState; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.character.BaseCharacter; import funkin.play.character.CharacterData.CharacterDataParser; +import funkin.play.components.ComboMilestone; +import funkin.play.components.HealthIcon; +import funkin.play.components.PopUpStuff; +import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.Conversation; -import funkin.data.dialogue.ConversationRegistry; import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VideoCutscene; -import funkin.data.event.SongEventRegistry; -import funkin.play.notes.NoteSprite; import funkin.play.notes.NoteDirection; +import funkin.play.notes.NoteSplash; +import funkin.play.notes.NoteSprite; +import funkin.play.notes.NoteSprite; +import funkin.play.notes.notestyle.NoteStyle; +import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.Strumline; import funkin.play.notes.SustainTrail; import funkin.play.scoring.Scoring; import funkin.play.song.Song; -import funkin.data.song.SongRegistry; -import funkin.data.stage.StageRegistry; -import funkin.data.song.SongData.SongEventData; -import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongCharacterData; import funkin.play.stage.Stage; -import funkin.ui.transition.LoadingState; -import funkin.play.components.PopUpStuff; -import funkin.ui.options.PreferencesMenu; +import funkin.save.Save; +import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.debug.stage.StageOffsetSubState; +import funkin.ui.mainmenu.MainMenuState; +import funkin.ui.MusicBeatSubState; +import funkin.ui.options.PreferencesMenu; import funkin.ui.story.StoryMenuState; +import funkin.graphics.FunkinCamera; +import funkin.ui.transition.LoadingState; import funkin.util.SerializerUtil; -import funkin.util.SortUtil; +import haxe.Int64; import lime.ui.Haptic; +import openfl.display.BitmapData; +import openfl.geom.Rectangle; +import openfl.Lib; #if discord_rpc import Discord.DiscordClient; #end @@ -445,6 +456,17 @@ class PlayState extends MusicBeatSubState return this.subState != null; } + var isExitingViaPauseMenu(get, never):Bool; + + function get_isExitingViaPauseMenu():Bool + { + if (this.subState == null) return false; + if (!Std.isOfType(this.subState, PauseSubState)) return false; + + var pauseSubState:PauseSubState = cast this.subState; + return pauseSubState.exitingToMenu; + } + /** * Data for the current difficulty for the current song. * Includes chart data, scroll speed, and other information. @@ -885,7 +907,7 @@ class PlayState extends MusicBeatSubState // TODO: Add a song event for Handle GF dance speed. // Handle player death. - if (!isInCutscene && !disableKeys && !_exiting) + if (!isInCutscene && !disableKeys) { // RESET = Quick Game Over Screen if (controls.RESET) @@ -1282,7 +1304,7 @@ class PlayState extends MusicBeatSubState */ function initCameras():Void { - camGame = new SwagCamera(); + camGame = new FunkinCamera(); camGame.bgColor = BACKGROUND_COLOR; // Show a pink background behind the stage. camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; // Show the game scene behind the camera. @@ -1739,7 +1761,7 @@ class PlayState extends MusicBeatSubState */ function resyncVocals():Void { - if (_exiting || vocals == null) return; + if (vocals == null) return; // Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.) if (!FlxG.sound.music.playing) return; @@ -1965,7 +1987,7 @@ class PlayState extends MusicBeatSubState // Mute vocals and play miss animation, but don't penalize. vocals.playerVolume = 0; - currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true); + if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true); } } } @@ -2519,8 +2541,8 @@ class PlayState extends MusicBeatSubState { FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); - transIn = FlxTransitionableState.defaultTransIn; - transOut = FlxTransitionableState.defaultTransOut; + // transIn = FlxTransitionableState.defaultTransIn; + // transOut = FlxTransitionableState.defaultTransOut; // TODO: Rework week unlock logic. // StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index caa576bcf..9ffeefcfd 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -352,11 +352,11 @@ class ResultState extends MusicBeatSubState { if (params.storyMode) { - FlxG.switchState(() -> new StoryMenuState()); + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker))); } else { - FlxG.switchState(() -> new FreeplayState()); + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new FreeplayState(null, sticker))); } } diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index ac6c3705e..af5765b25 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -1,10 +1,17 @@ package funkin.play.stage; +import openfl.display.BlendMode; +import funkin.graphics.framebuffer.FrameBufferManager; +import flixel.util.FlxColor; +import funkin.graphics.framebuffer.SpriteCopy; +import funkin.graphics.FunkinCamera; +import flixel.FlxCamera; import flixel.FlxSprite; import flixel.group.FlxSpriteGroup; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxShader; import flixel.util.FlxSort; +import openfl.display.BitmapData; import flixel.util.FlxColor; import funkin.modding.IScriptedClass; import funkin.modding.events.ScriptEvent; @@ -46,6 +53,13 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements return _data?.cameraZoom ?? 1.0; } + var frameBufferMan:FrameBufferManager; + + /** + * The texture that has the mask information. Used for shader effects. + */ + public var maskTexture:BitmapData; + var namedProps:Map = new Map(); var characters:Map = new Map(); var boppers:Array = new Array(); @@ -75,6 +89,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements */ public function onCreate(event:ScriptEvent):Void { + if (frameBufferMan != null) frameBufferMan.dispose(); + frameBufferMan = new FrameBufferManager(FlxG.camera); + setupFrameBuffers(); + buildStage(); this.refresh(); @@ -701,6 +719,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements { debugIconGroup = null; } + + if (frameBufferMan != null) + { + frameBufferMan.dispose(); + } } /** @@ -724,13 +747,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements } } - public function onUpdate(event:UpdateScriptEvent) - { - if (FlxG.keys.justPressed.F3) - { - debugIconGroup.visible = !debugIconGroup.visible; - } - } + public function onUpdate(event:UpdateScriptEvent) {} public override function kill() { @@ -753,6 +770,53 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements return Sprite; } + override function draw():Void + { + if (frameBufferMan != null) + { + frameBufferMan.lock(); + } + super.draw(); + if (frameBufferMan != null) + { + frameBufferMan.unlock(); + } + frameBuffersUpdated(); + } + + /** + * Called when the frame buffer manager is ready. + * Create frame buffers inside this method. + */ + function setupFrameBuffers():Void {} + + /** + * Called when all the frame buffers are updated. If you need any + * frame buffers before `grabScreen()`, make sure you + * grab the screen inside this method since it immediately uses the + * frame buffers. + */ + function frameBuffersUpdated():Void {} + + /** + * Grabs the current screen and returns it as a bitmap data. You can sefely modify it. + * @param applyFilters if this is `true`, the filters set to the camera will be applied to the resulting bitmap + * @return the grabbed screen + */ + function grabScreen(applyFilters:Bool):BitmapData + { + if (Std.isOfType(FlxG.camera, FunkinCamera)) + { + final cam:FunkinCamera = cast FlxG.camera; + return cam.grabScreen(applyFilters); + } + else + { + FlxG.log.error('cannot grab the screen: the main camera is not grabbable'); + return null; + } + } + static function _fetchData(id:String):Null { return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id)); diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 17c6e7fad..9a6f1a323 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -16,7 +16,7 @@ import funkin.input.Controls; /** * MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState. */ -class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandler +class MusicBeatSubState extends FlxSubState implements IEventHandler { public var leftWatermarkText:FlxText = null; public var rightWatermarkText:FlxText = null; diff --git a/source/funkin/ui/SwagCamera.hx b/source/funkin/ui/SwagCamera.hx deleted file mode 100644 index 70791d38f..000000000 --- a/source/funkin/ui/SwagCamera.hx +++ /dev/null @@ -1,103 +0,0 @@ -package funkin.ui; - -import flixel.FlxCamera; -import flixel.FlxSprite; -import flixel.math.FlxPoint; -import funkin.util.MathUtil; - -class SwagCamera extends FlxCamera -{ - /** - * properly follow framerate - * most of this just copied from FlxCamera, - * only lines 96 and 97 are changed - */ - override public function updateFollow():Void - { - // Either follow the object closely, - // or double check our deadzone and update accordingly. - if (deadzone == null) - { - target.getMidpoint(_point); - _point.addPoint(targetOffset); - focusOn(_point); - } - else - { - var edge:Float; - var targetX:Float = target.x + targetOffset.x; - var targetY:Float = target.y + targetOffset.y; - - if (style == SCREEN_BY_SCREEN) - { - if (targetX >= (scroll.x + width)) - { - _scrollTarget.x += width; - } - else if (targetX < scroll.x) - { - _scrollTarget.x -= width; - } - - if (targetY >= (scroll.y + height)) - { - _scrollTarget.y += height; - } - else if (targetY < scroll.y) - { - _scrollTarget.y -= height; - } - } - else - { - edge = targetX - deadzone.x; - if (_scrollTarget.x > edge) - { - _scrollTarget.x = edge; - } - edge = targetX + target.width - deadzone.x - deadzone.width; - if (_scrollTarget.x < edge) - { - _scrollTarget.x = edge; - } - - edge = targetY - deadzone.y; - if (_scrollTarget.y > edge) - { - _scrollTarget.y = edge; - } - edge = targetY + target.height - deadzone.y - deadzone.height; - if (_scrollTarget.y < edge) - { - _scrollTarget.y = edge; - } - } - - if ((target is FlxSprite)) - { - if (_lastTargetPosition == null) - { - _lastTargetPosition = FlxPoint.get(target.x, target.y); // Creates this point. - } - _scrollTarget.x += (target.x - _lastTargetPosition.x) * followLead.x; - _scrollTarget.y += (target.y - _lastTargetPosition.y) * followLead.y; - - _lastTargetPosition.x = target.x; - _lastTargetPosition.y = target.y; - } - - if (followLerp >= 60 / FlxG.updateFramerate) - { - scroll.copyFrom(_scrollTarget); // no easing - } - else - { - // THIS THE PART THAT ACTUALLY MATTERS LOL - scroll.x = MathUtil.coolLerp(scroll.x, _scrollTarget.x, followLerp); - scroll.y = MathUtil.coolLerp(scroll.y, _scrollTarget.y, followLerp); - // scroll.x += (_scrollTarget.x - scroll.x) * MathUtil.cameraLerp(followLerp); - // scroll.y += (_scrollTarget.y - scroll.y) * MathUtil.cameraLerp(followLerp); - } - } - } -} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 78b445318..6c64f952b 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -8,6 +8,7 @@ import flixel.FlxSprite; import flixel.FlxSubState; import flixel.graphics.FlxGraphic; import flixel.group.FlxGroup.FlxTypedGroup; +import funkin.graphics.FunkinCamera; import flixel.group.FlxSpriteGroup; import flixel.input.keyboard.FlxKey; import flixel.input.mouse.FlxMouseEvent; @@ -2094,7 +2095,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState loadPreferences(); - uiCamera = new FlxCamera(); + uiCamera = new FunkinCamera(); FlxG.cameras.reset(uiCamera); buildDefaultSongData(); @@ -5380,7 +5381,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Kill and replace the UI camera so it doesn't get destroyed during the state transition. uiCamera.kill(); FlxG.cameras.remove(uiCamera, false); - FlxG.cameras.reset(new FlxCamera()); + FlxG.cameras.reset(new FunkinCamera()); this.persistentUpdate = false; this.persistentDraw = false; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 9cbab2cb5..e7c615313 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -14,6 +14,7 @@ import flixel.group.FlxSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; import flixel.math.FlxMath; +import funkin.graphics.FunkinCamera; import flixel.math.FlxPoint; import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.text.FlxText; @@ -573,7 +574,7 @@ class FreeplayState extends MusicBeatSubState var swag:Alphabet = new Alphabet(1, 0, "swag"); - var funnyCam = new FlxCamera(0, 0, FlxG.width, FlxG.height); + var funnyCam = new FunkinCamera(0, 0, FlxG.width, FlxG.height); funnyCam.bgColor = FlxColor.TRANSPARENT; FlxG.cameras.add(funnyCam); diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 3da041ada..8842c37de 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -13,6 +13,7 @@ import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.touch.FlxTouch; import flixel.text.FlxText; import flixel.tweens.FlxEase; +import funkin.graphics.FunkinCamera; import flixel.tweens.FlxTween; import funkin.ui.MusicBeatState; import flixel.util.FlxTimer; @@ -152,7 +153,7 @@ class MainMenuState extends MusicBeatState function resetCamStuff() { - FlxG.cameras.reset(new SwagCamera()); + FlxG.cameras.reset(new FunkinCamera()); FlxG.camera.follow(camFollow, null, 0.06); } diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index ea04e1208..bda071846 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -4,6 +4,7 @@ import funkin.util.InputUtil; import flixel.FlxCamera; import flixel.FlxObject; import flixel.FlxSprite; +import funkin.graphics.FunkinCamera; import flixel.group.FlxGroup; import flixel.input.actions.FlxActionInput; import flixel.input.gamepad.FlxGamepadInputID; @@ -47,7 +48,7 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page { super(); - menuCamera = new FlxCamera(); + menuCamera = new FunkinCamera(); FlxG.cameras.add(menuCamera, false); menuCamera.bgColor = 0x0; camera = menuCamera; diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx index b4b3c7db8..c23c3f165 100644 --- a/source/funkin/ui/options/PreferencesMenu.hx +++ b/source/funkin/ui/options/PreferencesMenu.hx @@ -6,6 +6,7 @@ import flixel.FlxSprite; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import funkin.ui.AtlasText.AtlasFont; import funkin.ui.options.OptionsState.Page; +import funkin.graphics.FunkinCamera; import funkin.ui.TextMenuList.TextMenuItem; class PreferencesMenu extends Page @@ -20,7 +21,7 @@ class PreferencesMenu extends Page { super(); - menuCamera = new SwagCamera(); + menuCamera = new FunkinCamera(); FlxG.cameras.add(menuCamera, false); menuCamera.bgColor = 0x0; camera = menuCamera; diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx index fa36cfd50..e94eed7d5 100644 --- a/source/funkin/ui/transition/StickerSubState.hx +++ b/source/funkin/ui/transition/StickerSubState.hx @@ -116,10 +116,19 @@ class StickerSubState extends MusicBeatSubState { grpStickers.cameras = FlxG.cameras.list; - if (dipshit != null) + /* + if (dipshit != null) + { + FlxG.removeChild(dipshit); + dipshit = null; + } + */ + + if (grpStickers.members == null || grpStickers.members.length == 0) { - FlxG.removeChild(dipshit); - dipshit = null; + switchingState = false; + close(); + return; } for (ind => sticker in grpStickers.members) @@ -232,18 +241,23 @@ class StickerSubState extends MusicBeatSubState if (ind == grpStickers.members.length - 1) { switchingState = true; + FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; - dipshit = new Sprite(); - var scrn:BitmapData = new BitmapData(FlxG.width, FlxG.height, true, 0x00000000); - var mat:Matrix = new Matrix(); - scrn.draw(grpStickers.cameras[0].canvas, mat); + // I think this grabs the screen and puts it under the stickers? + // Leaving this commented out rather than stripping it out because it's cool... + /* + dipshit = new Sprite(); + var scrn:BitmapData = new BitmapData(FlxG.width, FlxG.height, true, 0x00000000); + var mat:Matrix = new Matrix(); + scrn.draw(grpStickers.cameras[0].canvas, mat); - var bitmap:Bitmap = new Bitmap(scrn); + var bitmap:Bitmap = new Bitmap(scrn); - dipshit.addChild(bitmap); - FlxG.addChildBelowMouse(dipshit); + dipshit.addChild(bitmap); + // FlxG.addChildBelowMouse(dipshit); + */ FlxG.switchState(() -> targetState(this)); } diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx index 17b2dd129..defd9797d 100644 --- a/source/funkin/util/plugins/WatchPlugin.hx +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -22,6 +22,17 @@ class WatchPlugin extends FlxBasic { super.update(elapsed); + var stateClassName = Type.getClassName(Type.getClass(FlxG.state)); + FlxG.watch.addQuick("currentState", stateClassName); + var subStateClassNames = []; + var subState = FlxG.state.subState; + while (subState != null) + { + subStateClassNames.push(Type.getClassName(Type.getClass(subState))); + subState = subState.subState; + } + FlxG.watch.addQuick("currentSubStates", subStateClassNames.join(", ")); + FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0);