mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-30 03:25:47 -05:00
Merge branch 'rewrite/master' into input-offsets
This commit is contained in:
commit
6b80e292e5
59 changed files with 1625 additions and 408 deletions
6
.github/workflows/build-shit.yml
vendored
6
.github/workflows/build-shit.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
|||
sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev
|
||||
- name: build game
|
||||
run: |
|
||||
haxelib run lime build html5 -release --times
|
||||
haxelib run lime build html5 -release --times -DGITHUB_BUILD
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
- name: build game
|
||||
run: |
|
||||
haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER
|
||||
haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD
|
||||
dir
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
|
||||
|
@ -110,7 +110,7 @@ jobs:
|
|||
key: ${{ runner.os }}-build-mac-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build macos -release --times
|
||||
haxelib run lime build macos -release --times -DGITHUB_BUILD
|
||||
ls
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache"
|
||||
|
|
16
Project.xml
16
Project.xml
|
@ -91,7 +91,8 @@
|
|||
NOT USING A DIRECT THING TO THE ASSET!!!
|
||||
-->
|
||||
<assets path="assets/fonts" embed="true" />
|
||||
|
||||
<!-- If compiled via github actions, enable force debug -->
|
||||
<set name="FORCE_DEBUG_VERSION" if="GITHUB_BUILD"/>
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
||||
|
@ -118,15 +119,15 @@
|
|||
<!--Disable the Flixel core focus lost screen-->
|
||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||
<!--Disable the Flixel core debugger. Automatically gets set whenever you compile in release mode!-->
|
||||
<haxedef name="FLX_NO_DEBUG" unless="debug" />
|
||||
<haxedef name="FLX_NO_DEBUG" unless="debug || FORCE_DEBUG_VERSION" />
|
||||
<!--Enable this for Nape release builds for a serious peformance improvement-->
|
||||
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
|
||||
|
||||
<!--
|
||||
Hide deprecation warnings until they're fixed.
|
||||
TODO: REMOVE THIS!!!!
|
||||
-->
|
||||
<haxeflag name="-w" value="-WDeprecated" />
|
||||
-->
|
||||
|
||||
<!-- Haxe 4.3.0+: Enable pretty syntax errors and stuff. -->
|
||||
<haxedef name="message.reporting" value="pretty" />
|
||||
|
@ -211,15 +212,8 @@
|
|||
<haxedef name="openfl-enable-handle-error" />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<!-- TODO: Add a flag to Github Actions to turn this on or something. -->
|
||||
|
||||
<!-- Forces the version string to include the Git hash even on release builds (which are used for performance reasons). -->
|
||||
<haxedef name="FORCE_DEBUG_VERSION" />
|
||||
</section>
|
||||
|
||||
<!-- Run a script before and after building. -->
|
||||
<postbuild haxe="source/Prebuild.hx"/> -->
|
||||
<prebuild haxe="source/Prebuild.hx"/> -->
|
||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
||||
|
||||
<!-- Enable this on platforms which do not support dropping files onto the window. -->
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 594853037cbea06caa5c141b0d9ed3736818e592
|
||||
Subproject commit 599169f4786d1feb62a089d25112035977647805
|
19
docs/COMPILING.md
Normal file
19
docs/COMPILING.md
Normal file
|
@ -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` !
|
|
@ -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.
|
||||
|
|
15
hmm.json
15
hmm.json
|
@ -11,7 +11,7 @@
|
|||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "07c6018008801972d12275690fc144fcc22e3de6",
|
||||
"ref": "4d054bd10b05bb1309a0ba3427ffa5378e0b4b99",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
|
@ -88,8 +88,10 @@
|
|||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
"type": "haxelib",
|
||||
"version": "1.2.4"
|
||||
"type": "git",
|
||||
"dir": "hxcpp-debug-server",
|
||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||
},
|
||||
{
|
||||
"name": "hxp",
|
||||
|
@ -152,6 +154,13 @@
|
|||
"ref": "0b53e478bc375ec51b760b650201ac7a965d2ef4",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22605ff44f01971d599641790d6bae4869f7d9f4",
|
||||
"url": "https://github.com/FunkinCrew/thx.core"
|
||||
},
|
||||
{
|
||||
"name": "thx.semver",
|
||||
"type": "haxelib",
|
||||
|
|
|
@ -16,7 +16,7 @@ import flixel.addons.transition.FlxTransitionableState;
|
|||
* var in:TransitionData = new TransitionData(...); // add your data where "..." is
|
||||
* var out:TransitionData = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(new FooState(in,out));
|
||||
* FlxG.switchState(() -> new FooState(in,out));
|
||||
* ```
|
||||
*
|
||||
* Method 2:
|
||||
|
@ -25,7 +25,7 @@ import flixel.addons.transition.FlxTransitionableState;
|
|||
* FlxTransitionableSubState.defaultTransIn = new TransitionData(...);
|
||||
* FlxTransitionableSubState.defaultTransOut = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(new FooState());
|
||||
* FlxG.switchState(() -> new FooState());
|
||||
* ```
|
||||
*/
|
||||
class FlxTransitionableSubState extends FlxSubState
|
||||
|
|
|
@ -89,7 +89,7 @@ class InitState extends FlxState
|
|||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
|
||||
|
@ -141,16 +141,14 @@ class InitState extends FlxState
|
|||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
#end
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// TODO: Disable this so we know to fix warnings.
|
||||
if (false)
|
||||
{
|
||||
#if FORCE_DEBUG_VERSION
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
}
|
||||
#end
|
||||
|
||||
//
|
||||
|
@ -247,21 +245,21 @@ class InitState extends FlxState
|
|||
#elseif LEVEL // -DLEVEL=week1 -DDIFFICULTY=hard
|
||||
startLevel(defineLevel(), defineDifficulty());
|
||||
#elseif FREEPLAY // -DFREEPLAY
|
||||
FlxG.switchState(new FreeplayState());
|
||||
FlxG.switchState(() -> new funkin.ui.freeplay.FreeplayState());
|
||||
#elseif DIALOGUE // -DDIALOGUE
|
||||
FlxG.switchState(new funkin.ui.debug.dialogue.ConversationDebugState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.dialogue.ConversationDebugState());
|
||||
#elseif ANIMATE // -DANIMATE
|
||||
FlxG.switchState(new funkin.ui.debug.anim.FlxAnimateTest());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.anim.FlxAnimateTest());
|
||||
#elseif WAVEFORM // -DWAVEFORM
|
||||
FlxG.switchState(new funkin.ui.debug.WaveformTestState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.WaveformTestState());
|
||||
#elseif CHARTING // -DCHARTING
|
||||
FlxG.switchState(new funkin.ui.debug.charting.ChartEditorState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
|
||||
#elseif STAGEBUILD // -DSTAGEBUILD
|
||||
FlxG.switchState(new funkin.ui.debug.stage.StageBuilderState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
||||
#elseif ANIMDEBUG // -DANIMDEBUG
|
||||
FlxG.switchState(new funkin.ui.debug.anim.DebugBoundingState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
|
||||
#elseif LATENCY // -DLATENCY
|
||||
FlxG.switchState(new funkin.LatencyState());
|
||||
FlxG.switchState(() -> new funkin.LatencyState());
|
||||
#else
|
||||
startGameNormally();
|
||||
#end
|
||||
|
@ -277,7 +275,7 @@ class InitState extends FlxState
|
|||
|
||||
if (params.chart.shouldLoadChart)
|
||||
{
|
||||
FlxG.switchState(new ChartEditorState(
|
||||
FlxG.switchState(() -> new ChartEditorState(
|
||||
{
|
||||
fnfcTargetPath: params.chart.chartPath,
|
||||
}));
|
||||
|
@ -285,7 +283,7 @@ class InitState extends FlxState
|
|||
else
|
||||
{
|
||||
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
FlxG.switchState(new TitleState());
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +306,7 @@ class InitState extends FlxState
|
|||
// TODO: Do this in the loading state.
|
||||
songData.cacheCharts(true);
|
||||
|
||||
LoadingState.loadAndSwitchState(new funkin.play.PlayState(
|
||||
LoadingState.loadAndSwitchState(() -> new funkin.play.PlayState(
|
||||
{
|
||||
targetSong: songData,
|
||||
targetDifficulty: difficultyId,
|
||||
|
@ -338,7 +336,7 @@ class InitState extends FlxState
|
|||
|
||||
var targetSong:funkin.play.song.Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
|
||||
LoadingState.loadAndSwitchState(new funkin.play.PlayState(
|
||||
LoadingState.loadAndSwitchState(() -> new funkin.play.PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: difficultyId,
|
||||
|
|
|
@ -936,6 +936,28 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return SongNoteData.buildDirectionName(this.data, strumlineSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* The strumline index of the note, if applicable.
|
||||
* Strips the direction from the data.
|
||||
*
|
||||
* 0 = player, 1 = opponent, etc.
|
||||
*/
|
||||
public function getStrumlineIndex(strumlineSize:Int = 4):Int
|
||||
{
|
||||
return Math.floor(this.data / strumlineSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the note is one that Boyfriend should try to hit (i.e. it's on his side).
|
||||
* TODO: The name of this function is a little misleading; what about mines?
|
||||
* @param strumlineSize Defaults to 4.
|
||||
* @return True if it's Boyfriend's note.
|
||||
*/
|
||||
public function getMustHitNote(strumlineSize:Int = 4):Bool
|
||||
{
|
||||
return getStrumlineIndex(strumlineSize) == 0;
|
||||
}
|
||||
|
||||
@:jignored
|
||||
var _stepTime:Null<Float> = null;
|
||||
|
||||
|
@ -1024,28 +1046,6 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The strumline index of the note, if applicable.
|
||||
* Strips the direction from the data.
|
||||
*
|
||||
* 0 = player, 1 = opponent, etc.
|
||||
*/
|
||||
public inline function getStrumlineIndex(strumlineSize:Int = 4):Int
|
||||
{
|
||||
return Math.floor(this.data / strumlineSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the note is one that Boyfriend should try to hit (i.e. it's on his side).
|
||||
* TODO: The name of this function is a little misleading; what about mines?
|
||||
* @param strumlineSize Defaults to 4.
|
||||
* @return True if it's Boyfriend's note.
|
||||
*/
|
||||
public inline function getMustHitNote(strumlineSize:Int = 4):Bool
|
||||
{
|
||||
return getStrumlineIndex(strumlineSize) == 0;
|
||||
}
|
||||
|
||||
@:jignored
|
||||
public var isHoldNote(get, never):Bool;
|
||||
|
||||
|
|
257
source/funkin/graphics/FunkinCamera.hx
Normal file
257
source/funkin/graphics/FunkinCamera.hx
Normal file
|
@ -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<BitmapData> = [];
|
||||
final texturePool:Array<TextureBase> = [];
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
123
source/funkin/graphics/framebuffer/BitmapDataUtil.hx
Normal file
123
source/funkin/graphics/framebuffer/BitmapDataUtil.hx
Normal file
|
@ -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!');
|
||||
}
|
||||
}
|
||||
}
|
42
source/funkin/graphics/framebuffer/FixedBitmapData.hx
Normal file
42
source/funkin/graphics/framebuffer/FixedBitmapData.hx
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
132
source/funkin/graphics/framebuffer/FrameBuffer.hx
Normal file
132
source/funkin/graphics/framebuffer/FrameBuffer.hx
Normal file
|
@ -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<SpriteCopy> = [];
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
127
source/funkin/graphics/framebuffer/FrameBufferManager.hx
Normal file
127
source/funkin/graphics/framebuffer/FrameBufferManager.hx
Normal file
|
@ -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<String, FrameBuffer> = [];
|
||||
|
||||
/**
|
||||
* 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<FlxColor> = 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();
|
||||
}
|
||||
}
|
59
source/funkin/graphics/framebuffer/SpriteCopy.hx
Normal file
59
source/funkin/graphics/framebuffer/SpriteCopy.hx
Normal file
|
@ -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<FlxColor>;
|
||||
|
||||
public function new(sprite:FlxSprite, color:Null<FlxColor>)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
12
source/funkin/graphics/shaders/PuddleShader.hx
Normal file
12
source/funkin/graphics/shaders/PuddleShader.hx
Normal file
|
@ -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')));
|
||||
}
|
||||
}
|
29
source/funkin/graphics/shaders/RuntimeCustomBlendShader.hx
Normal file
29
source/funkin/graphics/shaders/RuntimeCustomBlendShader.hx
Normal file
|
@ -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"));
|
||||
}
|
||||
}
|
105
source/funkin/graphics/shaders/RuntimePostEffectShader.hx
Normal file
105
source/funkin/graphics/shaders/RuntimePostEffectShader.hx
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
144
source/funkin/graphics/shaders/RuntimeRainShader.hx
Normal file
144
source/funkin/graphics/shaders/RuntimeRainShader.hx
Normal file
|
@ -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<Float>;
|
||||
var color:Array<Float>;
|
||||
var radius:Float;
|
||||
}
|
||||
|
||||
class RuntimeRainShader extends RuntimePostEffectShader
|
||||
{
|
||||
static final MAX_LIGHTS:Int = 8;
|
||||
|
||||
public var lights:Array<
|
||||
{
|
||||
position:ShaderParameter<Float>,
|
||||
color:ShaderParameter<Float>,
|
||||
radius:ShaderParameter<Float>,
|
||||
}>;
|
||||
|
||||
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<Float>
|
||||
{
|
||||
final res = new ShaderParameter<Float>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
8
source/funkin/modding/base/ScriptedFlxAtlasSprite.hx
Normal file
8
source/funkin/modding/base/ScriptedFlxAtlasSprite.hx
Normal file
|
@ -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 {}
|
8
source/funkin/modding/base/ScriptedFunkinSprite.hx
Normal file
8
source/funkin/modding/base/ScriptedFunkinSprite.hx
Normal file
|
@ -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 {}
|
|
@ -90,6 +90,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
{
|
||||
animationSuffix = "";
|
||||
musicSuffix = "";
|
||||
blueBallSuffix = "";
|
||||
}
|
||||
|
||||
override public function create()
|
||||
|
@ -207,11 +208,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
else if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
FlxG.switchState(new StoryMenuState());
|
||||
FlxG.switchState(() -> new StoryMenuState());
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(new FreeplayState());
|
||||
FlxG.switchState(() -> new FreeplayState());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,11 +66,11 @@ class GitarooPause extends MusicBeatState
|
|||
{
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
FlxG.switchState(new PlayState(previousParams));
|
||||
FlxG.switchState(() -> new PlayState(previousParams));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ class PauseSubState extends MusicBeatSubState
|
|||
|
||||
var practiceText:FlxText;
|
||||
|
||||
var exitingToMenu:Bool = false;
|
||||
public var exitingToMenu:Bool = false;
|
||||
|
||||
var bg:FlxSprite;
|
||||
var metaDataGrp:FlxTypedGroup<FlxSprite>;
|
||||
|
||||
|
@ -171,7 +172,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
var downP = controls.UI_DOWN_P;
|
||||
var accepted = controls.ACCEPT;
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
{
|
||||
|
@ -237,11 +238,11 @@ class PauseSubState extends MusicBeatSubState
|
|||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, STORY));
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, FREEPLAY));
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker)));
|
||||
}
|
||||
case 'Adjust Input Offsets':
|
||||
openSubState(new LatencyState());
|
||||
|
|
|
@ -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
|
||||
|
@ -83,10 +94,16 @@ typedef PlayStateParams =
|
|||
*/
|
||||
?targetDifficulty:String,
|
||||
/**
|
||||
* The character to play as.
|
||||
* @default `bf`, or the first character in the song's character list.
|
||||
* The variation to play on.
|
||||
* @default `Constants.DEFAULT_VARIATION` .
|
||||
*/
|
||||
?targetCharacter:String,
|
||||
?targetVariation:String,
|
||||
/**
|
||||
* The instrumental to play with.
|
||||
* Significant if the `targetSong` supports alternate instrumentals.
|
||||
* @default `null`
|
||||
*/
|
||||
?targetInstrumental:String,
|
||||
/**
|
||||
* Whether the song should start in Practice Mode.
|
||||
* @default `false`
|
||||
|
@ -147,9 +164,9 @@ class PlayState extends MusicBeatSubState
|
|||
public var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
/**
|
||||
* The player character being used for this level, as a character ID.
|
||||
* The currently selected variation.
|
||||
*/
|
||||
public var currentPlayerId:String = 'bf';
|
||||
public var currentVariation:String = Constants.DEFAULT_VARIATION;
|
||||
|
||||
/**
|
||||
* The currently active Stage. This is the object containing all the props.
|
||||
|
@ -439,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.
|
||||
|
@ -448,7 +476,7 @@ class PlayState extends MusicBeatSubState
|
|||
function get_currentChart():SongDifficulty
|
||||
{
|
||||
if (currentSong == null || currentDifficulty == null) return null;
|
||||
return currentSong.getDifficulty(currentDifficulty);
|
||||
return currentSong.getDifficulty(currentDifficulty, currentVariation);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -506,7 +534,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Apply parameters.
|
||||
currentSong = params.targetSong;
|
||||
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
|
||||
if (params.targetCharacter != null) currentPlayerId = params.targetCharacter;
|
||||
if (params.targetVariation != null) currentVariation = params.targetVariation;
|
||||
isPracticeMode = params.practiceMode ?? false;
|
||||
isMinimalMode = params.minimalMode ?? false;
|
||||
startTimestamp = params.startTimestamp ?? 0.0;
|
||||
|
@ -638,7 +666,7 @@ class PlayState extends MusicBeatSubState
|
|||
rightWatermarkText.cameras = [camHUD];
|
||||
|
||||
// Initialize some debug stuff.
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Display the version number (and git commit hash) in the bottom right corner.
|
||||
this.rightWatermarkText.text = Constants.VERSION;
|
||||
|
||||
|
@ -684,9 +712,9 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
message = 'The was a critical error selecting a difficulty for this song. Click OK to return to the main menu.';
|
||||
}
|
||||
else if (currentSong.getDifficulty(currentDifficulty) == null)
|
||||
else if (currentChart == null)
|
||||
{
|
||||
message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty. Click OK to return to the main menu.';
|
||||
message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.';
|
||||
}
|
||||
|
||||
// Display a popup. This blocks the application until the user clicks OK.
|
||||
|
@ -699,7 +727,7 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -828,11 +856,11 @@ class PlayState extends MusicBeatSubState
|
|||
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
||||
if (!isSubState && event.gitaroo)
|
||||
{
|
||||
FlxG.switchState(new GitarooPause(
|
||||
FlxG.switchState(() -> new GitarooPause(
|
||||
{
|
||||
targetSong: currentSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetCharacter: currentPlayerId,
|
||||
targetVariation: currentVariation,
|
||||
}));
|
||||
}
|
||||
else
|
||||
|
@ -879,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)
|
||||
|
@ -907,7 +935,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Disable updates, preventing animations in the background from playing.
|
||||
persistentUpdate = false;
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
if (FlxG.keys.pressed.THREE)
|
||||
{
|
||||
// TODO: Change the key or delete this?
|
||||
|
@ -918,7 +946,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
#end
|
||||
persistentDraw = false;
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
}
|
||||
#end
|
||||
|
||||
|
@ -1276,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.
|
||||
|
@ -1368,7 +1396,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Add the stage to the scene.
|
||||
this.add(currentStage);
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
FlxG.console.registerObject('stage', currentStage);
|
||||
#end
|
||||
}
|
||||
|
@ -1389,7 +1417,7 @@ class PlayState extends MusicBeatSubState
|
|||
trace('Song difficulty could not be loaded.');
|
||||
}
|
||||
|
||||
var currentCharacterData:SongCharacterData = currentChart.characters; // Switch the character we are playing as by manipulating currentPlayerId.
|
||||
var currentCharacterData:SongCharacterData = currentChart.characters; // Switch the variation we are playing on by manipulating targetVariation.
|
||||
|
||||
//
|
||||
// GIRLFRIEND
|
||||
|
@ -1458,7 +1486,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
currentStage.addCharacter(girlfriend, GF);
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
FlxG.console.registerObject('gf', girlfriend);
|
||||
#end
|
||||
}
|
||||
|
@ -1467,7 +1495,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
currentStage.addCharacter(boyfriend, BF);
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
FlxG.console.registerObject('bf', boyfriend);
|
||||
#end
|
||||
}
|
||||
|
@ -1478,7 +1506,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Camera starts at dad.
|
||||
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
FlxG.console.registerObject('dad', dad);
|
||||
#end
|
||||
}
|
||||
|
@ -1733,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;
|
||||
|
@ -1959,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2229,7 +2257,7 @@ class PlayState extends MusicBeatSubState
|
|||
#end
|
||||
|
||||
// Eject button
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
||||
|
@ -2247,13 +2275,13 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
disableKeys = true;
|
||||
persistentUpdate = false;
|
||||
FlxG.switchState(new ChartEditorState(
|
||||
FlxG.switchState(() -> new ChartEditorState(
|
||||
{
|
||||
targetSongId: currentSong.id,
|
||||
}));
|
||||
}
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// 1: End the song immediately.
|
||||
if (FlxG.keys.justPressed.ONE) endSong();
|
||||
|
||||
|
@ -2267,7 +2295,7 @@ class PlayState extends MusicBeatSubState
|
|||
// 9: Toggle the old icon.
|
||||
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// PAGEUP: Skip forward two sections.
|
||||
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||
|
@ -2513,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;
|
||||
|
@ -2589,14 +2617,16 @@ class PlayState extends MusicBeatSubState
|
|||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
|
||||
LoadingState.loadAndSwitchState(() -> {
|
||||
var nextPlayState:PlayState = new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetCharacter: currentPlayerId,
|
||||
targetVariation: currentVariation,
|
||||
});
|
||||
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
|
||||
LoadingState.loadAndSwitchState(nextPlayState);
|
||||
return nextPlayState;
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -2605,14 +2635,16 @@ class PlayState extends MusicBeatSubState
|
|||
// Load and cache the song's charts.
|
||||
// TODO: Do this in the loading state.
|
||||
targetSong.cacheCharts(true);
|
||||
LoadingState.loadAndSwitchState(() -> {
|
||||
var nextPlayState:PlayState = new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetCharacter: currentPlayerId,
|
||||
targetVariation: currentVariation,
|
||||
});
|
||||
nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
|
||||
LoadingState.loadAndSwitchState(nextPlayState);
|
||||
return nextPlayState;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2650,14 +2682,17 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
// Stop the music.
|
||||
FlxG.sound.music.pause();
|
||||
vocals.stop();
|
||||
if (vocals != null) vocals.stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.sound.music.pause();
|
||||
if (vocals != null)
|
||||
{
|
||||
vocals.pause();
|
||||
remove(vocals);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove reference to stage and remove sprites from it to save memory.
|
||||
if (currentStage != null)
|
||||
|
@ -2768,7 +2803,7 @@ class PlayState extends MusicBeatSubState
|
|||
FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
}
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
/**
|
||||
* Jumps forward or backward a number of sections in the song.
|
||||
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,11 +148,12 @@ class HealthIcon extends FlxSprite
|
|||
{
|
||||
if (characterId == 'bf-old')
|
||||
{
|
||||
characterId = PlayState.instance.currentPlayerId;
|
||||
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterId = 'bf-old';
|
||||
loadCharacter(characterId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -184,7 +184,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
|
||||
difficulty.characters = metadata.playData.characters;
|
||||
|
||||
difficulties.set(diffId, difficulty);
|
||||
var variationSuffix = (metadata.variation != Constants.DEFAULT_VARIATION) ? '-${metadata.variation}' : '';
|
||||
difficulties.set('$diffId$variationSuffix', difficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,13 +219,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
for (diffId in chartNotes.keys())
|
||||
{
|
||||
// Retrieve the cached difficulty data.
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
var variationSuffix = (variation != Constants.DEFAULT_VARIATION) ? '-$variation' : '';
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get('$diffId$variationSuffix');
|
||||
if (difficulty == null)
|
||||
{
|
||||
trace('Fabricated new difficulty for $diffId.');
|
||||
difficulty = new SongDifficulty(this, diffId, variation);
|
||||
var metadata = _metadata.get(variation);
|
||||
difficulties.set(diffId, difficulty);
|
||||
difficulties.set('$diffId$variationSuffix', difficulty);
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
|
@ -254,41 +256,80 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
/**
|
||||
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||
* @param diffId The difficulty ID, such as `easy` or `hard`.
|
||||
* @param variation The variation ID to fetch the difficulty for. Or you can use `variations`.
|
||||
* @param variations A list of variations to fetch the difficulty for. Looks for the first variation that exists.
|
||||
* @return The difficulty data.
|
||||
*/
|
||||
public inline function getDifficulty(?diffId:String):Null<SongDifficulty>
|
||||
public function getDifficulty(?diffId:String, ?variation:String, ?variations:Array<String>):Null<SongDifficulty>
|
||||
{
|
||||
if (diffId == null) diffId = listDifficulties()[0];
|
||||
if (diffId == null) diffId = listDifficulties(variation)[0];
|
||||
if (variation == null) variation = Constants.DEFAULT_VARIATION;
|
||||
if (variations == null) variations = [variation];
|
||||
|
||||
return difficulties.get(diffId);
|
||||
for (currentVariation in variations)
|
||||
{
|
||||
var variationSuffix = (currentVariation != Constants.DEFAULT_VARIATION) ? '-$currentVariation' : '';
|
||||
|
||||
if (difficulties.exists('$diffId$variationSuffix'))
|
||||
{
|
||||
return difficulties.get('$diffId$variationSuffix');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFirstValidVariation(?diffId:String, ?possibleVariations:Array<String>):Null<String>
|
||||
{
|
||||
if (variations == null) possibleVariations = variations;
|
||||
if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0];
|
||||
|
||||
for (variation in variations)
|
||||
{
|
||||
if (difficulties.exists('$diffId-$variation')) return variation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all the difficulties in this song.
|
||||
* @param variationId Optionally filter by variation.
|
||||
* @param variationId Optionally filter by a single variation.
|
||||
* @param variationIds Optionally filter by multiple variations.
|
||||
* @return The list of difficulties.
|
||||
*/
|
||||
public function listDifficulties(?variationId:String):Array<String>
|
||||
public function listDifficulties(?variationId:String, ?variationIds:Array<String>):Array<String>
|
||||
{
|
||||
if (variationId == '') variationId = null;
|
||||
if (variationIds == null) variationIds = [];
|
||||
if (variationId != null) variationIds.push(variationId);
|
||||
|
||||
var diffFiltered:Array<String> = difficulties.keys().array().filter(function(diffId:String):Bool {
|
||||
if (variationId == null) return true;
|
||||
// The difficulties array contains entries like 'normal', 'nightmare-erect', and 'normal-pico',
|
||||
// so we have to map it to the actual difficulty names.
|
||||
// We also filter out difficulties that don't match the variation or that don't exist.
|
||||
|
||||
var diffFiltered:Array<String> = difficulties.keys().array().map(function(diffId:String):Null<String> {
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null) return false;
|
||||
return difficulty.variation == variationId;
|
||||
});
|
||||
if (difficulty == null) return null;
|
||||
if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null;
|
||||
return difficulty.difficulty;
|
||||
}).nonNull().unique();
|
||||
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
||||
|
||||
return diffFiltered;
|
||||
}
|
||||
|
||||
public function hasDifficulty(diffId:String, ?variationId:String):Bool
|
||||
public function hasDifficulty(diffId:String, ?variationId:String, ?variationIds:Array<String>):Bool
|
||||
{
|
||||
if (variationId == '') variationId = null;
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
return variationId == null ? (difficulty != null) : (difficulty != null && difficulty.variation == variationId);
|
||||
if (variationIds == null) variationIds = [];
|
||||
if (variationId != null) variationIds.push(variationId);
|
||||
|
||||
for (targetVariation in variationIds)
|
||||
{
|
||||
var variationSuffix = (targetVariation != Constants.DEFAULT_VARIATION) ? '-$targetVariation' : '';
|
||||
if (difficulties.exists('$diffId$variationSuffix')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,12 +486,14 @@ class SongDifficulty
|
|||
{
|
||||
if (instrumental != '' && characters.altInstrumentals.contains(instrumental))
|
||||
{
|
||||
FlxG.sound.cache(Paths.inst(this.song.id, instrumental));
|
||||
var instId = '-$instrumental';
|
||||
FlxG.sound.cache(Paths.inst(this.song.id, instId));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to default instrumental.
|
||||
FlxG.sound.cache(Paths.inst(this.song.id, characters.instrumental));
|
||||
var instId = (characters.instrumental ?? '') != '' ? '-${characters.instrumental}' : '';
|
||||
FlxG.sound.cache(Paths.inst(this.song.id, instId));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -492,7 +535,8 @@ class SongDifficulty
|
|||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
|
||||
// Automatically resolve voices by removing suffixes.
|
||||
// For example, if `Voices-bf-car.ogg` does not exist, check for `Voices-bf.ogg`.
|
||||
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
|
||||
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
|
||||
|
||||
var playerId:String = characters.player;
|
||||
var voicePlayer:String = Paths.voices(this.song.id, '-$playerId$suffix');
|
||||
|
@ -504,6 +548,19 @@ class SongDifficulty
|
|||
// Try again.
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
if (voicePlayer == null)
|
||||
{
|
||||
// Try again without $suffix.
|
||||
playerId = characters.player;
|
||||
voicePlayer = Paths.voices(this.song.id, '-${playerId}');
|
||||
while (voicePlayer != null && !Assets.exists(voicePlayer))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
playerId = playerId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voicePlayer = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
|
||||
}
|
||||
}
|
||||
|
||||
var opponentId:String = characters.opponent;
|
||||
var voiceOpponent:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
|
@ -514,6 +571,19 @@ class SongDifficulty
|
|||
// Try again.
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
}
|
||||
if (voiceOpponent == null)
|
||||
{
|
||||
// Try again without $suffix.
|
||||
opponentId = characters.opponent;
|
||||
voiceOpponent = Paths.voices(this.song.id, '-${opponentId}');
|
||||
while (voiceOpponent != null && !Assets.exists(voiceOpponent))
|
||||
{
|
||||
// Remove the last suffix.
|
||||
opponentId = opponentId.split('-').slice(0, -1).join('-');
|
||||
// Try again.
|
||||
voiceOpponent = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
|
||||
}
|
||||
}
|
||||
|
||||
var result:Array<String> = [];
|
||||
if (voicePlayer != null) result.push(voicePlayer);
|
||||
|
|
|
@ -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<String, StageProp> = new Map<String, StageProp>();
|
||||
var characters:Map<String, BaseCharacter> = new Map<String, BaseCharacter>();
|
||||
var boppers:Array<Bopper> = new Array<Bopper>();
|
||||
|
@ -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<StageData>
|
||||
{
|
||||
return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id));
|
||||
|
|
|
@ -75,7 +75,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
function handleFunctionControls():Void
|
||||
{
|
||||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
|
|
@ -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;
|
||||
|
@ -60,7 +60,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
|
|||
else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1);
|
||||
|
||||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// This can now be used in EVERY STATE YAY!
|
||||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,7 +90,9 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
|
||||
function openChartEditor()
|
||||
{
|
||||
FlxG.switchState(new ChartEditorState());
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
|
||||
FlxG.switchState(() -> new ChartEditorState());
|
||||
}
|
||||
|
||||
function openInputOffsetTesting()
|
||||
|
@ -101,7 +103,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
|
||||
function openAnimationEditor()
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.debug.anim.DebugBoundingState());
|
||||
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
|
||||
trace('Animation Editor');
|
||||
}
|
||||
|
||||
|
|
|
@ -364,7 +364,7 @@ class DebugBoundingState extends FlxState
|
|||
|
||||
if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
|
||||
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
MouseUtil.mouseCamDrag();
|
||||
MouseUtil.mouseWheelZoom();
|
||||
|
|
|
@ -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();
|
||||
|
@ -3183,7 +3184,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
handleTestKeybinds();
|
||||
handleHelpKeybinds();
|
||||
|
||||
#if debug
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
handleQuickWatch();
|
||||
#end
|
||||
|
||||
|
@ -5097,7 +5098,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
stopWelcomeMusic();
|
||||
// TODO: PR Flixel to make onComplete nullable.
|
||||
if (audioInstTrack != null) audioInstTrack.onComplete = null;
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
resetWindowTitle();
|
||||
|
||||
|
@ -5363,8 +5364,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: selectedDifficulty,
|
||||
// TODO: Add this.
|
||||
// targetCharacter: targetCharacter,
|
||||
targetVariation: selectedVariation,
|
||||
practiceMode: playtestPracticeMode,
|
||||
minimalMode: minimal,
|
||||
startTimestamp: startTimestamp,
|
||||
|
@ -5381,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;
|
||||
|
|
|
@ -138,7 +138,7 @@ class CopyItemsCommand implements ChartEditorCommand
|
|||
state.txtCopyNotif.y = FlxG.mouse.y - 16;
|
||||
FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5,
|
||||
{
|
||||
type: FlxTween.ONESHOT,
|
||||
type: FlxTweenType.ONESHOT,
|
||||
ease: FlxEase.quadOut,
|
||||
onComplete: function(_) {
|
||||
state.txtCopyNotif.visible = false;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.ui.debug.charting.handlers;
|
||||
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
|
|
|
@ -72,11 +72,10 @@ class ChartEditorImportExportHandler
|
|||
{
|
||||
state.loadInstFromAsset(Paths.inst(songId, '-$variation'), variation);
|
||||
}
|
||||
}
|
||||
|
||||
for (difficultyId in song.listDifficulties())
|
||||
for (difficultyId in song.listDifficulties(variation))
|
||||
{
|
||||
var diff:Null<SongDifficulty> = song.getDifficulty(difficultyId);
|
||||
var diff:Null<SongDifficulty> = song.getDifficulty(difficultyId, variation);
|
||||
if (diff == null) continue;
|
||||
|
||||
var instId:String = diff.variation == Constants.DEFAULT_VARIATION ? '' : diff.variation;
|
||||
|
@ -96,6 +95,7 @@ class ChartEditorImportExportHandler
|
|||
trace('[WARN] Strange quantity of voice paths for difficulty ${difficultyId}: ${voiceList.length}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
state.currentWorkingFilePath = null; // New file, so no path.
|
||||
|
|
|
@ -290,7 +290,7 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
|
|||
// waveformMusic.waveform.forceUpdate = true;
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var waveformData1 = playerVoice.waveformData;
|
||||
var waveformData2 = opponentVoice.waveformData;
|
||||
var waveformData2 = opponentVoice?.waveformData ?? playerVoice.waveformData; // this null check is for songs that only have 1 vocals file!
|
||||
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
|
||||
var waveformData = waveformData1.merge(waveformData2).merge(waveformData3);
|
||||
trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds');
|
||||
|
|
|
@ -276,8 +276,13 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
|||
|
||||
// Build opponent waveform.
|
||||
// waveformOpponent.waveform.forceUpdate = true;
|
||||
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
|
||||
// so we null check
|
||||
if (opponentVoice != null)
|
||||
{
|
||||
waveformOpponent.waveform.waveformData = opponentVoice.waveformData;
|
||||
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
|
||||
}
|
||||
|
||||
// Build instrumental waveform.
|
||||
// waveformInstrumental.waveform.forceUpdate = true;
|
||||
|
|
|
@ -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;
|
||||
|
@ -54,8 +55,26 @@ import funkin.util.MathUtil;
|
|||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
|
||||
/**
|
||||
* Parameters used to initialize the FreeplayState.
|
||||
*/
|
||||
typedef FreeplayStateParams =
|
||||
{
|
||||
?character:String,
|
||||
};
|
||||
|
||||
class FreeplayState extends MusicBeatSubState
|
||||
{
|
||||
//
|
||||
// Params
|
||||
//
|
||||
|
||||
/**
|
||||
* The current character for this FreeplayState.
|
||||
* You can't change this without transitioning to a new FreeplayState.
|
||||
*/
|
||||
final currentCharacter:String;
|
||||
|
||||
/**
|
||||
* For the audio preview, the duration of the fade-in effect.
|
||||
*/
|
||||
|
@ -116,6 +135,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
var ostName:FlxText;
|
||||
var difficultyStars:DifficultyStars;
|
||||
|
||||
var displayedVariations:Array<String>;
|
||||
|
||||
var dj:DJBoyfriend;
|
||||
|
||||
var letterSort:LetterSort;
|
||||
|
@ -124,12 +145,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var stickerSubState:StickerSubState;
|
||||
|
||||
//
|
||||
static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||
static var rememberedSongId:Null<String> = null;
|
||||
|
||||
public function new(?stickers:StickerSubState = null)
|
||||
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
||||
{
|
||||
currentCharacter = params?.character ?? Constants.DEFAULT_CHARACTER;
|
||||
|
||||
if (stickers != null)
|
||||
{
|
||||
stickerSubState = stickers;
|
||||
|
@ -172,6 +194,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
// Add a null entry that represents the RANDOM option
|
||||
songs.push(null);
|
||||
|
||||
// TODO: This makes custom variations disappear from Freeplay. Figure out a better solution later.
|
||||
// Default character (BF) shows default and Erect variations. Pico shows only Pico variations.
|
||||
displayedVariations = (currentCharacter == "bf") ? [Constants.DEFAULT_VARIATION, "erect"] : [currentCharacter];
|
||||
|
||||
// programmatically adds the songs via LevelRegistry and SongRegistry
|
||||
for (levelId in LevelRegistry.instance.listBaseGameLevelIds())
|
||||
{
|
||||
|
@ -179,9 +205,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
var song:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
songs.push(new FreeplaySongData(levelId, songId, song));
|
||||
// Only display songs which actually have available charts for the current character.
|
||||
var availableDifficultiesForSong = song.listDifficulties(displayedVariations);
|
||||
if (availableDifficultiesForSong.length == 0) continue;
|
||||
|
||||
for (difficulty in song.listDifficulties())
|
||||
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
|
||||
for (difficulty in availableDifficultiesForSong)
|
||||
{
|
||||
diffIdsTotal.pushUnique(difficulty);
|
||||
}
|
||||
|
@ -300,6 +329,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
x: -dj.width * 1.6,
|
||||
speed: 0.5
|
||||
});
|
||||
// TODO: Replace this.
|
||||
if (currentCharacter == "pico") dj.visible = false;
|
||||
add(dj);
|
||||
|
||||
var bgDad:FlxSprite = new FlxSprite(pinkBack.width * 0.75, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
|
||||
|
@ -543,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);
|
||||
|
||||
|
@ -869,6 +900,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
changeDiff(1);
|
||||
}
|
||||
|
||||
// TODO: DEBUG REMOVE THIS
|
||||
if (FlxG.keys.justPressed.P)
|
||||
{
|
||||
var newParams:FreeplayStateParams =
|
||||
{
|
||||
character: currentCharacter == "bf" ? "pico" : "bf",
|
||||
};
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(newParams, sticker)));
|
||||
}
|
||||
|
||||
if (controls.BACK && !typing.hasFocus)
|
||||
{
|
||||
FlxTween.globalManager.clear();
|
||||
|
@ -920,7 +961,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1086,13 +1127,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
return;
|
||||
}
|
||||
var targetDifficulty:String = currentDifficulty;
|
||||
|
||||
// TODO: Implement Pico into the interface properly.
|
||||
var targetCharacter:String = 'bf';
|
||||
if (FlxG.keys.pressed.P)
|
||||
{
|
||||
targetCharacter = 'pico';
|
||||
}
|
||||
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficulty);
|
||||
|
||||
PlayStatePlaylist.campaignId = cap.songData.levelId;
|
||||
|
||||
|
@ -1106,11 +1141,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
Paths.setCurrentLevel(cap.songData.levelId);
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
LoadingState.loadAndSwitchState(() -> new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: targetDifficulty,
|
||||
targetCharacter: targetCharacter,
|
||||
targetVariation: targetVariation,
|
||||
}), true);
|
||||
});
|
||||
}
|
||||
|
@ -1275,31 +1310,33 @@ class FreeplaySongData
|
|||
public var songRating(default, null):Int = 0;
|
||||
|
||||
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||
public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
|
||||
|
||||
function set_currentDifficulty(value:String):String
|
||||
{
|
||||
if (currentDifficulty == value) return value;
|
||||
|
||||
currentDifficulty = value;
|
||||
updateValues();
|
||||
updateValues(displayedVariations);
|
||||
return value;
|
||||
}
|
||||
|
||||
public function new(levelId:String, songId:String, song:Song)
|
||||
public function new(levelId:String, songId:String, song:Song, ?displayedVariations:Array<String>)
|
||||
{
|
||||
this.levelId = levelId;
|
||||
this.songId = songId;
|
||||
this.song = song;
|
||||
if (displayedVariations != null) this.displayedVariations = displayedVariations;
|
||||
|
||||
updateValues();
|
||||
updateValues(displayedVariations);
|
||||
}
|
||||
|
||||
function updateValues():Void
|
||||
function updateValues(displayedVariations:Array<String>):Void
|
||||
{
|
||||
this.songDifficulties = song.listDifficulties();
|
||||
this.songDifficulties = song.listDifficulties(displayedVariations);
|
||||
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty);
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, displayedVariations);
|
||||
if (songDifficulty == null) return;
|
||||
this.songName = songDifficulty.songName;
|
||||
this.songCharacter = songDifficulty.characters.opponent;
|
||||
|
|
|
@ -45,7 +45,7 @@ class HaxeUISubState extends MusicBeatSubState
|
|||
super.update(elapsed);
|
||||
|
||||
// Force quit.
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// Refresh the component.
|
||||
if (FlxG.keys.justPressed.F5)
|
||||
|
|
|
@ -8,10 +8,12 @@ import flixel.FlxState;
|
|||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.effects.FlxFlicker;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
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;
|
||||
|
@ -94,7 +96,7 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
|
||||
menuItems.enabled = true; // can move on intro
|
||||
createMenuItem('storymode', 'mainmenu/storymode', function() startExitState(new StoryMenuState()));
|
||||
createMenuItem('storymode', 'mainmenu/storymode', function() startExitState(() -> new StoryMenuState()));
|
||||
createMenuItem('freeplay', 'mainmenu/freeplay', function() {
|
||||
persistentDraw = true;
|
||||
persistentUpdate = false;
|
||||
|
@ -110,7 +112,7 @@ class MainMenuState extends MusicBeatState
|
|||
#end
|
||||
|
||||
createMenuItem('options', 'mainmenu/options', function() {
|
||||
startExitState(new funkin.ui.options.OptionsState());
|
||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
|
@ -151,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);
|
||||
}
|
||||
|
||||
|
@ -255,7 +257,7 @@ class MainMenuState extends MusicBeatState
|
|||
openSubState(prompt);
|
||||
}
|
||||
|
||||
function startExitState(state:FlxState)
|
||||
function startExitState(state:NextState)
|
||||
{
|
||||
menuItems.enabled = false; // disable for exit
|
||||
var duration = 0.4;
|
||||
|
@ -313,7 +315,7 @@ class MainMenuState extends MusicBeatState
|
|||
if (controls.BACK && menuItems.enabled && !menuItems.busy)
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
FlxG.switchState(new TitleState());
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -103,7 +103,7 @@ class OptionsState extends MusicBeatState
|
|||
{
|
||||
currentPage.enabled = false;
|
||||
// TODO: Animate this transition?
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -150,7 +150,8 @@ class Level implements IRegistryEntry<LevelData>
|
|||
|
||||
if (firstSong != null)
|
||||
{
|
||||
for (difficulty in firstSong.listDifficulties())
|
||||
// Don't display alternate characters in Story Mode.
|
||||
for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, "erect"]))
|
||||
{
|
||||
difficulties.push(difficulty);
|
||||
}
|
||||
|
@ -168,7 +169,7 @@ class Level implements IRegistryEntry<LevelData>
|
|||
|
||||
for (difficulty in difficulties)
|
||||
{
|
||||
if (!song.hasDifficulty(difficulty))
|
||||
if (!song.hasDifficulty(difficulty, [Constants.DEFAULT_VARIATION, "erect"]))
|
||||
{
|
||||
difficulties.remove(difficulty);
|
||||
}
|
||||
|
|
|
@ -397,7 +397,7 @@ class StoryMenuState extends MusicBeatState
|
|||
{
|
||||
FlxG.sound.play(Paths.sound('cancelMenu'));
|
||||
exitingMenu = true;
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,7 +565,7 @@ class StoryMenuState extends MusicBeatState
|
|||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
|
||||
LoadingState.loadAndSwitchState(new PlayState(
|
||||
LoadingState.loadAndSwitchState(() -> new PlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
|
|
|
@ -105,6 +105,6 @@ class AttractState extends MusicBeatState
|
|||
vid.destroy();
|
||||
vid = null;
|
||||
|
||||
FlxG.switchState(new TitleState());
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class OutdatedSubState extends MusicBeatState
|
|||
if (controls.BACK)
|
||||
{
|
||||
leftState = true;
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
import funkin.audio.visualize.SpectogramSprite;
|
||||
import funkin.graphics.shaders.ColorSwap;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
|
@ -213,7 +214,7 @@ class TitleState extends MusicBeatState
|
|||
*/
|
||||
function moveToAttract():Void
|
||||
{
|
||||
FlxG.switchState(new AttractState());
|
||||
FlxG.switchState(() -> new AttractState());
|
||||
}
|
||||
|
||||
function playMenuMusic():Void
|
||||
|
@ -294,7 +295,7 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
if (touch.justPressed)
|
||||
{
|
||||
FlxG.switchState(new FreeplayState());
|
||||
FlxG.switchState(() -> new FreeplayState());
|
||||
pressedEnter = true;
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +314,7 @@ class TitleState extends MusicBeatState
|
|||
// If you spam Enter, we should skip the transition.
|
||||
if (pressedEnter && transitioning && skippedIntro)
|
||||
{
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
if (pressedEnter && !transitioning && skippedIntro)
|
||||
|
@ -328,7 +329,7 @@ class TitleState extends MusicBeatState
|
|||
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
|
||||
transitioning = true;
|
||||
|
||||
var targetState:FlxState = new MainMenuState();
|
||||
var targetState:NextState = () -> new MainMenuState();
|
||||
|
||||
new FlxTimer().start(2, function(tmr:FlxTimer) {
|
||||
// These assets are very unlikely to be used for the rest of gameplay, so it unloads them from cache/memory
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.ui.transition;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
@ -21,12 +20,13 @@ import lime.utils.AssetManifest;
|
|||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.filters.ShaderFilter;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
|
||||
class LoadingState extends MusicBeatState
|
||||
{
|
||||
inline static var MIN_TIME = 1.0;
|
||||
|
||||
var target:FlxState;
|
||||
var target:NextState;
|
||||
var stopMusic = false;
|
||||
var callbacks:MultiCallback;
|
||||
var danceLeft = false;
|
||||
|
@ -34,7 +34,7 @@ class LoadingState extends MusicBeatState
|
|||
var loadBar:FlxSprite;
|
||||
var funkay:FlxSprite;
|
||||
|
||||
function new(target:FlxState, stopMusic:Bool)
|
||||
function new(target:NextState, stopMusic:Bool)
|
||||
{
|
||||
super();
|
||||
this.target = target;
|
||||
|
@ -172,12 +172,12 @@ class LoadingState extends MusicBeatState
|
|||
return Paths.inst(PlayState.instance.currentSong.id);
|
||||
}
|
||||
|
||||
inline static public function loadAndSwitchState(nextState:FlxState, shouldStopMusic = false):Void
|
||||
inline static public function loadAndSwitchState(nextState:NextState, shouldStopMusic = false):Void
|
||||
{
|
||||
FlxG.switchState(getNextState(nextState, shouldStopMusic));
|
||||
}
|
||||
|
||||
static function getNextState(nextState:FlxState, shouldStopMusic = false):FlxState
|
||||
static function getNextState(nextState:NextState, shouldStopMusic = false):NextState
|
||||
{
|
||||
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||
|
||||
|
@ -186,7 +186,7 @@ class LoadingState extends MusicBeatState
|
|||
// && (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath()))
|
||||
// && isLibraryLoaded('shared');
|
||||
//
|
||||
if (true) return new LoadingState(nextState, shouldStopMusic);
|
||||
if (true) return () -> new LoadingState(nextState, shouldStopMusic);
|
||||
#end
|
||||
if (shouldStopMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
|
||||
|
@ -332,7 +332,7 @@ class MultiCallback
|
|||
public function getUnfired():Array<Void->Void>
|
||||
return unfired.array();
|
||||
|
||||
public static function coolSwitchState(state:FlxState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
|
||||
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
|
||||
{
|
||||
var screenShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("shaderTransitionStuff/coolDots"));
|
||||
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
|
||||
|
@ -343,9 +343,9 @@ class MultiCallback
|
|||
ease: FlxEase.quadInOut,
|
||||
onComplete: function(twn) {
|
||||
screenShit.destroy();
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(state);
|
||||
}
|
||||
});
|
||||
FlxG.camera.setFilters([new ShaderFilter(screenWipeShit)]);
|
||||
FlxG.camera.filters = [new ShaderFilter(screenWipeShit)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import funkin.ui.freeplay.FreeplayState;
|
|||
import openfl.geom.Matrix;
|
||||
import openfl.display.Sprite;
|
||||
import openfl.display.Bitmap;
|
||||
import flixel.FlxState;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
@ -30,7 +31,12 @@ class StickerSubState extends MusicBeatSubState
|
|||
// yes... a damn OpenFL sprite!!!
|
||||
public var dipshit:Sprite;
|
||||
|
||||
var nextState:NEXTSTATE = FREEPLAY;
|
||||
/**
|
||||
* The state to switch to after the stickers are done.
|
||||
* This is a FUNCTION so we can pass it directly to `FlxG.switchState()`,
|
||||
* and we can add constructor parameters in the caller.
|
||||
*/
|
||||
var targetState:StickerSubState->FlxState;
|
||||
|
||||
// what "folders" to potentially load from (as of writing only "keys" exist)
|
||||
var soundSelections:Array<String> = [];
|
||||
|
@ -38,10 +44,12 @@ class StickerSubState extends MusicBeatSubState
|
|||
var soundSelection:String = "";
|
||||
var sounds:Array<String> = [];
|
||||
|
||||
public function new(?oldStickers:Array<StickerSprite>, ?nextState:NEXTSTATE = FREEPLAY):Void
|
||||
public function new(?oldStickers:Array<StickerSprite>, ?targetState:StickerSubState->FlxState):Void
|
||||
{
|
||||
super();
|
||||
|
||||
this.targetState = (targetState == null) ? ((sticker) -> new MainMenuState()) : targetState;
|
||||
|
||||
// todo still
|
||||
// make sure that ONLY plays mp3/ogg files
|
||||
// if there's no mp3/ogg file, then it regenerates/reloads the random folder
|
||||
|
@ -84,10 +92,6 @@ class StickerSubState extends MusicBeatSubState
|
|||
|
||||
trace(sounds);
|
||||
|
||||
// trace(assetsInList);
|
||||
|
||||
this.nextState = nextState;
|
||||
|
||||
grpStickers = new FlxTypedGroup<StickerSprite>();
|
||||
add(grpStickers);
|
||||
|
||||
|
@ -112,11 +116,20 @@ class StickerSubState extends MusicBeatSubState
|
|||
{
|
||||
grpStickers.cameras = FlxG.cameras.list;
|
||||
|
||||
/*
|
||||
if (dipshit != null)
|
||||
{
|
||||
FlxG.removeChild(dipshit);
|
||||
dipshit = null;
|
||||
}
|
||||
*/
|
||||
|
||||
if (grpStickers.members == null || grpStickers.members.length == 0)
|
||||
{
|
||||
switchingState = false;
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
for (ind => sticker in grpStickers.members)
|
||||
{
|
||||
|
@ -228,9 +241,13 @@ class StickerSubState extends MusicBeatSubState
|
|||
if (ind == grpStickers.members.length - 1)
|
||||
{
|
||||
switchingState = true;
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
// 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();
|
||||
|
@ -239,22 +256,11 @@ class StickerSubState extends MusicBeatSubState
|
|||
var bitmap:Bitmap = new Bitmap(scrn);
|
||||
|
||||
dipshit.addChild(bitmap);
|
||||
FlxG.addChildBelowMouse(dipshit);
|
||||
// FlxG.addChildBelowMouse(dipshit);
|
||||
*/
|
||||
|
||||
switch (nextState)
|
||||
{
|
||||
case FREEPLAY:
|
||||
FlxG.switchState(new FreeplayState(this));
|
||||
case STORY:
|
||||
FlxG.switchState(new StoryMenuState(this));
|
||||
case MAIN_MENU:
|
||||
FlxG.switchState(new MainMenuState());
|
||||
default:
|
||||
FlxG.switchState(new MainMenuState());
|
||||
FlxG.switchState(() -> targetState(this));
|
||||
}
|
||||
}
|
||||
|
||||
// sticky.angle *= FlxG.random.float(0, 0.05);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -368,10 +374,3 @@ typedef StickerShit =
|
|||
stickers:Map<String, Array<String>>,
|
||||
stickerPacks:Map<String, Array<String>>
|
||||
}
|
||||
|
||||
enum abstract NEXTSTATE(String)
|
||||
{
|
||||
var MAIN_MENU = 'mainmenu';
|
||||
var FREEPLAY = 'freeplay';
|
||||
var STORY = 'story';
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class EvacuateDebugPlugin extends FlxBasic
|
|||
|
||||
if (FlxG.keys.justPressed.F4)
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
|
||||
FlxG.switchState(() -> new funkin.ui.mainmenu.MainMenuState());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -23,6 +23,24 @@ class ArrayTools
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the array with all `null` elements removed.
|
||||
* @param array The array to remove `null` elements from.
|
||||
* @return A copy of the array with all `null` elements removed.
|
||||
*/
|
||||
public static function nonNull<T>(array:Array<Null<T>>):Array<T>
|
||||
{
|
||||
var result:Array<T> = [];
|
||||
for (element in array)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
result.push(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first element of the array that satisfies the predicate, or null if none do.
|
||||
* @param input The array to search
|
||||
|
|
|
@ -18,9 +18,9 @@ class Int64Tools
|
|||
|
||||
public static function toFloat(i:Int64):Float
|
||||
{
|
||||
var f:Float = Int64.getLow(i);
|
||||
var f:Float = i.low;
|
||||
if (f < 0) f += MAX_32_PRECISION;
|
||||
return (Int64.getHigh(i) * MAX_32_PRECISION + f);
|
||||
return (i.high * MAX_32_PRECISION + f);
|
||||
}
|
||||
|
||||
public static function isToIntSafe(i:Int64):Bool
|
||||
|
|
Loading…
Reference in a new issue