mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-09-06 14:07:47 +00:00
Compare commits
10 commits
19c061490a
...
b30c4b982b
Author | SHA1 | Date | |
---|---|---|---|
|
b30c4b982b | ||
|
c2eff142bd | ||
|
2784fa18c0 | ||
|
ad07fddf89 | ||
|
4768eedd5b | ||
|
f1c3e99a11 | ||
|
eefe8927c4 | ||
|
3ff4f14510 | ||
|
955b0db542 | ||
|
d2df4f0832 |
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -53,8 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- You can import them in another script as usual.
|
||||
- Added support for renaming imported classes using the `as` keyword. (Thanks KoloInDaCrib!)
|
||||
- Fixed `try`/`catch` blocks not working properly. (Thanks NotHyper-474!)
|
||||
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()`). (Thanks KoloInDaCrib!)
|
||||
- Fixed Linux being case-sensitive with filenames. (Thanks mikolka9144!)
|
||||
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()). (Thanks KoloInDaCrib!)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -89,7 +88,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
* @Smokey555 made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
|
||||
* @CCobaltDev made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
|
||||
* @mikolka9144 made their first contribution in [polymod#212](https://github.com/larsiusprime/polymod/pull/212)
|
||||
|
||||
|
||||
|
||||
|
@ -707,7 +705,6 @@ This patch resolves a critical issue that could cause user's save data to become
|
|||
|
||||
## [0.5.0] - 2024-09-12
|
||||
The Playable Pico Update!
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new Character Select screen to switch between playable characters in Freeplay.
|
||||
|
@ -828,7 +825,7 @@ The Playable Pico Update!
|
|||
### Fixed
|
||||
|
||||
- Control binds in the controls menu no longer overlap their names.
|
||||
- Attempting to exit the gameover screen and retry the song at the same time no longer crashes the game. ([thanks DM-kun for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
|
||||
- Attempting to exit the gameover screen and retry the song at the same time no longer crashes the game. ([thanks DMMaster636 for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
|
||||
- Botplay mode now handles the player's animations properly during hold notes. ([thanks Hundrec!](https://github.com/FunkinCrew/Funkin/pull/2683))
|
||||
- Camera movement now pauses when the game is paused. ([thanks Matriculaso!](https://github.com/FunkinCrew/Funkin/pull/2684))
|
||||
- Pico's gameplay sprite no longer appears on the gameover screen when dying from an explosion in 2hot.
|
||||
|
@ -842,14 +839,13 @@ The Playable Pico Update!
|
|||
## New Contributors for 0.4.1
|
||||
|
||||
* @Hundrec made their first contribution in [#2661](https://github.com/FunkinCrew/Funkin/pull/2661)
|
||||
* @DM-kun made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
|
||||
* @DMMaster636 made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
|
||||
* @eltociear made their first contribution in [#2730](https://github.com/FunkinCrew/Funkin/pull/2730)
|
||||
|
||||
|
||||
|
||||
## [0.4.0] - 2024-06-06
|
||||
The Pit Stop 1 update!
|
||||
|
||||
### Added
|
||||
|
||||
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu!
|
||||
|
@ -1021,7 +1017,6 @@ The Pit Stop 1 update!
|
|||
|
||||
## [0.3.0] - 2024-04-30
|
||||
The Weekend 1 update!
|
||||
|
||||
### Added
|
||||
|
||||
- New Story Level: Weekend 1, starring Pico, Darnell, and Nene.
|
||||
|
|
17
README.md
17
README.md
|
@ -1,25 +1,14 @@
|
|||
<div align='center'><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Friday_Night_Funkin%27_logo.svg/2560px-Friday_Night_Funkin%27_logo.svg.png" width="800">
|
||||
# Friday Night Funkin'
|
||||
|
||||
<h2>Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for <a href="https://ldjam.com/events/ludum-dare/47">Ludum Dare 47.</a></h2>
|
||||
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
|
||||
|
||||
This game was made with love to Newgrounds and its community. Extra love to Tom Fulp.
|
||||
|
||||
</div>
|
||||
|
||||
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||
- [Download Android builds from Google Play!](https://play.google.com/store/apps/details?id=me.funkin.fnf)
|
||||
- [Download iOS builds from the App Store!](https://apps.apple.com/app/id6740428530)
|
||||
|
||||
<div align='center'>
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif" alt="Title Screen" width="350"/></td>
|
||||
<td><img src="https://fridaynightfunkin.wiki.gg/images/9/99/Menu.png" alt="Main Menu" width="350"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
# Getting Started
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
|
@ -63,4 +52,4 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
|
|||
## Special Thanks
|
||||
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
|
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 094ff109197e35d21342bdf2d51132be23948d3f
|
||||
Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 311f6e963f76c1d9cad5bfe8789315025286687e
|
||||
Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109
|
|
@ -1,64 +0,0 @@
|
|||
{
|
||||
"input": "./assets",
|
||||
"output": "./astc-textures/",
|
||||
"quality": "medium",
|
||||
"blocksize": "10x10",
|
||||
"colorprofile": "cl",
|
||||
"custom": [
|
||||
{
|
||||
"asset": "assets/week1/images/erect/server.png",
|
||||
"blocksize": "8x8"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week1/images/erect/crowd.png",
|
||||
"blocksize": "8x8"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week1/image/erect/bg.png",
|
||||
"blocksize": "8x8"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week1/image/erect/lights.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week2/images/erect/stairsDark.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week2/images/erect/stairsLight.png",
|
||||
"blocksize": "6x6"
|
||||
}
|
||||
],
|
||||
"excludes": [
|
||||
"assets/preload/images/cursor/",
|
||||
"assets/preload/images/freeplay/",
|
||||
"assets/preload/images/icons/",
|
||||
"assets/preload/images/fonts/",
|
||||
"assets/preload/images/titleEnter.png",
|
||||
"assets/preload/images/titleEnter_mobile.png",
|
||||
"assets/preload/images/soundtray/",
|
||||
"assets/preload/images/stageBuild/",
|
||||
"assets/preload/images/ui/popup/pixel/*",
|
||||
"assets/shared/images/characters/abotPixel/*",
|
||||
"assets/shared/images/characters/bfPixel.png",
|
||||
"assets/shared/images/characters/bfPixelsDEAD.png",
|
||||
"assets/shared/images/characters/gfPixel.png",
|
||||
"assets/shared/images/characters/nenePixel/*",
|
||||
"assets/shared/images/characters/picoPixel/*",
|
||||
"assets/shared/images/characters/senpai.png",
|
||||
"assets/shared/images/characters/spirit.png",
|
||||
"assets/shared/images/ui/chart-editor/",
|
||||
"assets/shared/images/ui/countdown/pixel/*",
|
||||
"assets/shared/images/resultScreen/*",
|
||||
"assets/shared/images/resultScreen/clearPercent/",
|
||||
"assets/shared/images/resultScreen/rankText/",
|
||||
"assets/week1/*",
|
||||
"assets/week1/images/erect/brightLightSmall.png",
|
||||
"assets/week1/images/erect/lightAbove.png",
|
||||
"assets/week1/images/erect/lightgreen.png",
|
||||
"assets/week1/images/erect/lightred.png",
|
||||
"assets/week1/images/erect/orangeLight.png",
|
||||
"assets/week6/"
|
||||
]
|
||||
}
|
|
@ -80,7 +80,7 @@
|
|||
{
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^?:_[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
|
||||
"format": "^[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
|
||||
"tokens": ["INLINE", "NOTINLINE"]
|
||||
},
|
||||
"type": "ConstantName"
|
||||
|
|
21
compression-excludes.txt
Normal file
21
compression-excludes.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
assets/preload/images/cursor/*
|
||||
assets/preload/images/freeplay/*
|
||||
assets/preload/images/icons/*
|
||||
assets/preload/images/titleEnter.png
|
||||
assets/preload/images/titleEnter_mobile.png
|
||||
assets/preload/images/soundtray/*
|
||||
assets/preload/images/stageBuild/*
|
||||
assets/preload/images/ui/popup/pixel/
|
||||
assets/shared/images/characters/abotPixel/
|
||||
assets/shared/images/characters/bfPixel.png
|
||||
assets/shared/images/characters/bfPixelsDEAD.png
|
||||
assets/shared/images/characters/gfPixel.png
|
||||
assets/shared/images/characters/nenePixel/
|
||||
assets/shared/images/characters/picoPixel/
|
||||
assets/shared/images/characters/senpai.png
|
||||
assets/shared/images/characters/spirit.png
|
||||
assets/shared/images/resultScreen/*
|
||||
assets/shared/images/ui/chart-editor/*
|
||||
assets/shared/images/ui/countdown/pixel/
|
||||
assets/week1/
|
||||
assets/week6/*
|
26
hmm.json
26
hmm.json
|
@ -11,41 +11,41 @@
|
|||
"name": "astc-compressor",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "9b0819150b67bf0d49145f250099b75d21be5839",
|
||||
"ref": "8c9f56927c523df7b849352c6951f04112fe15cc",
|
||||
"url": "https://github.com/KarimAkra/astc-compressor"
|
||||
},
|
||||
{
|
||||
"name": "extension-admob",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "02334589ff9603a5f483077a44395009644f6274",
|
||||
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
|
||||
"url": "https://github.com/FunkinCrew/extension-admob"
|
||||
},
|
||||
{
|
||||
"name": "extension-androidtools",
|
||||
"type": "haxelib",
|
||||
"version": "2.2.2"
|
||||
"version": "2.2.1"
|
||||
},
|
||||
{
|
||||
"name": "extension-haptics",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.4"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
{
|
||||
"name": "extension-iapcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.4"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
{
|
||||
"name": "extension-iarcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "ac2a34fe4b3400bc04218f46320a65af0f337fc0",
|
||||
"ref": "08fc955ca87f192a971719a675f1d3b21709725d",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
|
@ -59,7 +59,7 @@
|
|||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "49214278b9124823582cdcecd94f4a1de9a4b36b",
|
||||
"ref": "39c1572add28869c558b218fffed13df1b64f376",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
|
@ -104,14 +104,14 @@
|
|||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8c1d238b069ef97cec13f5121b29d2afe2b8985d",
|
||||
"ref": "d60bb2947fa609fdc875ccfae89666a6984eeaf2",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
|
||||
"ref": "4e24283a047f11bded6affabbc9ec405156e026e",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp"
|
||||
},
|
||||
{
|
||||
|
@ -170,7 +170,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e5f8c27124598505917a001588b560244731adfb",
|
||||
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
@ -219,14 +219,14 @@
|
|||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "db5dcf3ac4cb59197188a4442d75d4e76b350f40",
|
||||
"ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2bf2b992e06159510f595554e6b952e47922f128",
|
||||
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
|
||||
"url": "https://github.com/fponticelli/thx.core"
|
||||
},
|
||||
{
|
||||
|
|
230
project.hxp
230
project.hxp
|
@ -5,7 +5,6 @@ import hxp.*;
|
|||
import lime.tools.*;
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import haxe.ds.Map;
|
||||
|
||||
|
@ -59,11 +58,6 @@ class Project extends HXProject
|
|||
*/
|
||||
static final SOURCE_DIR:String = "source";
|
||||
|
||||
/**
|
||||
* The relative location of the templates folder.
|
||||
*/
|
||||
static final TEMPLATES_DIR:String = "templates";
|
||||
|
||||
/**
|
||||
* The fully qualified class path for the game's preloader.
|
||||
* Particularly important on HTML5 but we use it on all platforms.
|
||||
|
@ -109,11 +103,30 @@ class Project extends HXProject
|
|||
|
||||
static final ANDROID_EXTENSIONS:Array<String> = ["funkin.extensions.CallbackUtil"];
|
||||
|
||||
//
|
||||
// ASTC COMPRESSION
|
||||
//
|
||||
|
||||
/**
|
||||
* The quality of the compression process.
|
||||
* This doesn't exactly reduce the quality of the outputted texture..
|
||||
* But it changes how hard the CPU gotta work to maintin the quality and details of the image while compressing.
|
||||
* possible values: fastest, fast, medium, thorough, exhaustive.
|
||||
*/
|
||||
static var ASTC_QUALITY:String = "medium";
|
||||
|
||||
/**
|
||||
* The team ID to use for the iOS app. Configured in XCode.
|
||||
*/
|
||||
static var IOS_TEAM_ID:String = "Z7G7AVNGSH";
|
||||
|
||||
/**
|
||||
* The block size of the ASTC compressed textures.
|
||||
* Higher block size means lower quality and lighter file size for the generated compressed textures.
|
||||
* possible block sizes: 4x4, 4x6, 6x6, 8x8 ... 12x12.
|
||||
*/
|
||||
static var ASTC_BLOCKSIZE:String = "10x10";
|
||||
|
||||
/**
|
||||
* A list of asset file globs to exclude from ASTC compression when creating optimized mobile builds.
|
||||
*/
|
||||
|
@ -317,10 +330,10 @@ class Project extends HXProject
|
|||
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_LAG_ADJUSTMENT`
|
||||
* `-DFEATURE_INPUT_OFFSETS`
|
||||
* If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets.
|
||||
*/
|
||||
static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT";
|
||||
static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_LOG_TRACE`
|
||||
|
@ -485,10 +498,16 @@ class Project extends HXProject
|
|||
configureOutputDir();
|
||||
configurePolymod();
|
||||
configureHaxelibs();
|
||||
configureASTCTextures();
|
||||
configureAssets();
|
||||
configureIcons();
|
||||
|
||||
readASTCExclusion();
|
||||
|
||||
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
|
||||
{
|
||||
runASTCCompressor();
|
||||
}
|
||||
|
||||
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
|
||||
{
|
||||
configureAdMobKeys();
|
||||
|
@ -513,9 +532,10 @@ class Project extends HXProject
|
|||
function flair()
|
||||
{
|
||||
// TODO: Implement this.
|
||||
info("Friday Night Funkin' - " + VERSION);
|
||||
info("Friday Night Funkin'");
|
||||
info("Initializing build...");
|
||||
|
||||
info("Target Version: " + VERSION);
|
||||
info("Git Branch: " + getGitBranch());
|
||||
info("Git Commit: " + getGitCommit());
|
||||
info("Git Modified? " + getGitModified());
|
||||
|
@ -546,9 +566,6 @@ class Project extends HXProject
|
|||
// If for some reason we have multiple source directories, we can add more entries here.
|
||||
this.sources.push(SOURCE_DIR);
|
||||
|
||||
// Templates stuff
|
||||
this.templatePaths.push(TEMPLATES_DIR);
|
||||
|
||||
// Tell Lime to run some prebuild and postbuild scripts.
|
||||
this.preBuildCallbacks.push(buildHaxeCLICommand(PREBUILD_HX));
|
||||
this.postBuildCallbacks.push(buildHaxeCLICommand(POSTBUILD_HX));
|
||||
|
@ -812,7 +829,7 @@ class Project extends HXProject
|
|||
FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile()));
|
||||
|
||||
// Should be true except on web builds (some asset stuff breaks it)
|
||||
FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb());
|
||||
FEATURE_INPUT_OFFSETS.apply(this, !isWeb());
|
||||
|
||||
// Should be true except on web and mobile builds.
|
||||
// Screenshots doesn't work there, and mobile has its own screenshots anyway.
|
||||
|
@ -843,78 +860,6 @@ class Project extends HXProject
|
|||
// Since mobile is pretty memory safe (I'm looking at you Apple...) we need them BADLY to have the game run properly.
|
||||
// It's kept off for desktop due to the low ASTC support on desktop GPUs (which seem to be only among Intergrated Graphics).
|
||||
FEATURE_COMPRESSED_TEXTURES.apply(this, isMobile() && isRelease());
|
||||
|
||||
renderFlagsTable();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a cute little table of which feature flags are enabled and which are disabled
|
||||
*/
|
||||
function renderFlagsTable():Void
|
||||
{
|
||||
var enabledWidth:Int = 0;
|
||||
var disabledWidth:Int = 0;
|
||||
var enabledFlags:Array<String> = [];
|
||||
var disabledFlags:Array<String> = [];
|
||||
var unicodeCheck:String = "✓ ";
|
||||
var unicodeCross:String = "× ";
|
||||
|
||||
#if windows && !target.unicode
|
||||
// lime build tools uses neko (unless compiling with `-eval`), and neko on windows doesn't support UTF8/unicode characters
|
||||
// so we need to remove our cute emoji!
|
||||
unicodeCheck = "o ";
|
||||
unicodeCross = "x ";
|
||||
|
||||
#end
|
||||
|
||||
|
||||
for (flagStr in this.haxedefs.keys())
|
||||
{
|
||||
if (!flagStr.startsWith(FeatureFlag.INVERSE_PREFIX))
|
||||
{
|
||||
// we do flagStr.length + 3 to accomodate for adding the "✓ " at the beginning
|
||||
enabledWidth = Std.int(Math.max(flagStr.length + 3, enabledWidth));
|
||||
enabledFlags.push(unicodeCheck + flagStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
disabledWidth = Std.int(Math.max(flagStr.length, disabledWidth));
|
||||
disabledFlags.push(unicodeCross + flagStr.substring(FeatureFlag.INVERSE_PREFIX.length));
|
||||
}
|
||||
}
|
||||
|
||||
enabledFlags.sort(function(a, b) return a > b ? 1 : -1);
|
||||
disabledFlags.sort(function(a, b) return a > b ? 1 : -1);
|
||||
|
||||
|
||||
var horizontalDivider:String = "".rpad("-", enabledWidth + disabledWidth);
|
||||
|
||||
info(horizontalDivider);
|
||||
// Header
|
||||
info("FEATURE FLAGS");
|
||||
info('Enabled Flags $unicodeCheck'.rpad(" ", enabledWidth + 5) + 'Disabled Flags $unicodeCross');
|
||||
info(horizontalDivider);
|
||||
|
||||
|
||||
while (enabledFlags.length > 0 || disabledFlags.length > 0)
|
||||
{
|
||||
var outputLine:String = "";
|
||||
|
||||
if (enabledFlags.length > 0)
|
||||
outputLine += enabledFlags.shift();
|
||||
|
||||
outputLine = outputLine.rpad(" ", enabledWidth + 5);
|
||||
outputLine += "| ";
|
||||
|
||||
if (disabledFlags.length > 0)
|
||||
outputLine += disabledFlags.shift() ?? "";
|
||||
|
||||
info(outputLine);
|
||||
}
|
||||
|
||||
info(horizontalDivider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -945,7 +890,7 @@ class Project extends HXProject
|
|||
|
||||
if (FEATURE_LOG_TRACE.isDisabled(this))
|
||||
{
|
||||
setHaxedef("no-traces");
|
||||
addHaxeFlag("--no-traces");
|
||||
}
|
||||
|
||||
// Disable the built in pause screen when unfocusing the game.
|
||||
|
@ -1038,7 +983,7 @@ class Project extends HXProject
|
|||
|
||||
function configureAndroid()
|
||||
{
|
||||
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java']));
|
||||
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
|
||||
|
||||
if (isRelease())
|
||||
{
|
||||
|
@ -1306,7 +1251,27 @@ class Project extends HXProject
|
|||
if (isAndroid())
|
||||
{
|
||||
// Adaptive icons
|
||||
adaptiveIcon = new AdaptiveIcon('art/icons/android/', true);
|
||||
// TODO: Add Adapative Icons in Lime
|
||||
templatePaths.push("templates");
|
||||
|
||||
var androidTheme:String = "@style/LimeAppMainTheme";
|
||||
if (window.fullscreen != null && window.fullscreen)
|
||||
{
|
||||
androidTheme += "Fullscreen";
|
||||
}
|
||||
|
||||
final iconXmlChild:Dynamic = {
|
||||
"android:label": meta.title,
|
||||
"android:allowBackup": "true",
|
||||
"android:theme": androidTheme,
|
||||
"android:hardwareAccelerated": "true",
|
||||
"android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null,
|
||||
"android:largeHeap": "true",
|
||||
"android:icon": "@mipmap/ic_launcher",
|
||||
"android:roundIcon": "@mipmap/ic_launcher_round"
|
||||
};
|
||||
|
||||
config.set('android.application', iconXmlChild);
|
||||
}
|
||||
else if (isIOS())
|
||||
{
|
||||
|
@ -1338,24 +1303,7 @@ class Project extends HXProject
|
|||
addIcon("art/icons/icon64.png", 64);
|
||||
addIcon("art/icons/iconOG.png");
|
||||
|
||||
}
|
||||
|
||||
info('Done configuring icons.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the astc textures.
|
||||
*/
|
||||
function configureASTCTextures()
|
||||
{
|
||||
if (command != "display")
|
||||
{
|
||||
readASTCExclusion();
|
||||
|
||||
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
|
||||
{
|
||||
runASTCCompressor();
|
||||
}
|
||||
info('Done configuring icons.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1755,8 +1703,7 @@ class Project extends HXProject
|
|||
*/
|
||||
public function error(message:String):Void
|
||||
{
|
||||
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}'));
|
||||
Sys.exit(1);
|
||||
Log.error('${message}');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1766,7 +1713,7 @@ class Project extends HXProject
|
|||
{
|
||||
if (command != "display")
|
||||
{
|
||||
Sys.println('[INFO] ${message}');
|
||||
Log.info('[INFO] ${message}');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1793,67 +1740,46 @@ class Project extends HXProject
|
|||
var env = new Map<String, Dynamic>();
|
||||
for (line in envFile.split('\n'))
|
||||
{
|
||||
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
|
||||
if (line == "" || line.startsWith("#")) continue;
|
||||
|
||||
var index:Int = line.indexOf('=');
|
||||
var parts = line.split('=');
|
||||
if (parts.length != 2) continue;
|
||||
|
||||
if (index == -1) continue;
|
||||
|
||||
var field:String = line.substr(0, index);
|
||||
var value:String = line.substr(index + 1);
|
||||
|
||||
env.set(field, value);
|
||||
env.set(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
private function shouldExcludeEnvKey(key:String):Bool
|
||||
{
|
||||
final android:Bool = key.startsWith('ANDROID_');
|
||||
final ios:Bool = key.startsWith('IOS_');
|
||||
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
|
||||
final web:Bool = key.startsWith('WEB_');
|
||||
final desktop:Bool = key.startsWith('DESKTOP_');
|
||||
|
||||
if (isWeb() && (mobile || desktop)) return true;
|
||||
if (isDesktop() && (mobile || web)) return true;
|
||||
if (isAndroid() && (ios || web || desktop)) return true;
|
||||
if (isIOS() && (android || web || desktop)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function readASTCExclusion():Void
|
||||
{
|
||||
@:nullSafety(Off)
|
||||
astcExcludes = haxe.Json.parse(File.getContent('./astc-compression-data.json')).excludes;
|
||||
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');
|
||||
|
||||
for (i in 0...astcExcludes.length)
|
||||
astcExcludes[i] = astcExcludes[i].trim();
|
||||
}
|
||||
|
||||
public function isASTCExcluded(file:String):Bool
|
||||
public function isASTCExcluded(filePath:String):Bool
|
||||
{
|
||||
for (exclusion in astcExcludes)
|
||||
{
|
||||
if (exclusion.endsWith("/"))
|
||||
if (exclusion.endsWith("/*"))
|
||||
{
|
||||
var normalizedFilePath = Path.normalize(file);
|
||||
var normalizedExclusion = Path.normalize(exclusion);
|
||||
var normalizedFilePath = Path.normalize(filePath);
|
||||
var normalizedExclusion = Path.normalize(exclusion.substr(0, exclusion.length - 2));
|
||||
|
||||
if (normalizedFilePath.startsWith(normalizedExclusion)) return true;
|
||||
}
|
||||
else if (exclusion.endsWith("/*"))
|
||||
else if (exclusion.endsWith("/"))
|
||||
{
|
||||
var normalizedExclusion = Path.normalize(exclusion.substr(0, exclusion.length - 2));
|
||||
var fileDirectory = Path.directory(Path.normalize(file));
|
||||
var normalizedExclusion = Path.normalize(exclusion);
|
||||
var fileDirectory = Path.directory(Path.normalize(filePath));
|
||||
|
||||
if (fileDirectory == normalizedExclusion) return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file == exclusion) return true;
|
||||
if (filePath == exclusion) return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1864,8 +1790,12 @@ class Project extends HXProject
|
|||
{
|
||||
info('Compressing ASTC textures...');
|
||||
|
||||
var args:Array<String> = ['run', 'astc-compressor', 'compress-from-json'];
|
||||
args = args.concat(['-json', './astc-compression-data.json']);
|
||||
var args:Array<String> = ['run', 'astc-compressor'];
|
||||
args = args.concat(['-i', './assets']);
|
||||
args = args.concat(['-o', './astc-textures/assets/']);
|
||||
args = args.concat(['-blocksize', ASTC_BLOCKSIZE]);
|
||||
args = args.concat(['-quality', ASTC_QUALITY]);
|
||||
args = args.concat(['-excludes', './compression-excludes.txt']);
|
||||
|
||||
Sys.command('haxelib', args);
|
||||
|
||||
|
@ -1877,9 +1807,9 @@ class Project extends HXProject
|
|||
* An object representing a feature flag, which can be enabled or disabled.
|
||||
* Includes features such as automatic generation of compile defines and inversion.
|
||||
*/
|
||||
abstract FeatureFlag(String) from String to String
|
||||
abstract FeatureFlag(String)
|
||||
{
|
||||
public static final INVERSE_PREFIX:String = "NO_";
|
||||
static final INVERSE_PREFIX:String = "NO_";
|
||||
|
||||
public function new(input:String)
|
||||
{
|
||||
|
@ -1904,11 +1834,13 @@ abstract FeatureFlag(String) from String to String
|
|||
if (isEnabled(project))
|
||||
{
|
||||
// If this flag was already enabled, disable the inverse.
|
||||
project.info('Enabling feature flag ${this}');
|
||||
getInverse().disable(project, false);
|
||||
}
|
||||
else if (getInverse().isEnabled(project))
|
||||
{
|
||||
// If the inverse flag was already enabled, disable this flag.
|
||||
project.info('Disabling feature flag ${this}');
|
||||
disable(project, false);
|
||||
}
|
||||
else
|
||||
|
@ -1916,11 +1848,13 @@ abstract FeatureFlag(String) from String to String
|
|||
if (enableByDefault)
|
||||
{
|
||||
// Enable this flag if it was unset, and disable the inverse.
|
||||
project.info('Enabling feature flag ${this}');
|
||||
enable(project, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable this flag if it was unset, and enable the inverse.
|
||||
project.info('Disabling feature flag ${this}');
|
||||
disable(project, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class Postbuild
|
|||
|
||||
var buildTime:Float = roundToTwoDecimals(end - start);
|
||||
|
||||
Sys.println('[INFO] Build took: ${buildTime} seconds');
|
||||
trace('Build took: ${buildTime} seconds');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,28 @@ class Prebuild
|
|||
{
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||
|
||||
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||
|
||||
class NewgroundsCredentials
|
||||
{
|
||||
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||
}";
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
var start:Float = Sys.time();
|
||||
// Sys.println('[INFO] Performing pre-build tasks...');
|
||||
trace('[PREBUILD] Performing pre-build tasks...');
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
buildCredsFile();
|
||||
|
||||
var end:Float = Sys.time();
|
||||
var duration:Float = end - start;
|
||||
// Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
|
||||
trace('[PREBUILD] Finished pre-build tasks in $duration seconds.');
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
|
@ -29,4 +41,20 @@ class Prebuild
|
|||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
|
||||
static function buildCredsFile():Void
|
||||
{
|
||||
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||
{
|
||||
trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[PREBUILD] Creating NewgroundsCredentials.hx...');
|
||||
|
||||
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||
|
||||
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -417,7 +417,7 @@ class Conductor
|
|||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||
{
|
||||
this.songPosition = Math.min(this.combinedOffset, 0).clamp(songPos, currentLength);
|
||||
this.songPosition = FlxMath.bound(Math.min(this.combinedOffset, 0), songPos, currentLength);
|
||||
this.songPositionDelta += FlxG.elapsed * 1000 * FlxG.sound.music.pitch;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -117,7 +117,10 @@ class FunkinMemory
|
|||
*/
|
||||
public static function cacheTexture(key:String):Void
|
||||
{
|
||||
if (currentCachedTextures.exists(key)) return;
|
||||
if (currentCachedTextures.exists(key))
|
||||
{
|
||||
return; // Already cached.
|
||||
}
|
||||
|
||||
if (previousCachedTextures.exists(key))
|
||||
{
|
||||
|
@ -132,13 +135,13 @@ class FunkinMemory
|
|||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
return;
|
||||
}
|
||||
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
forceRender(graphic);
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,54 +150,26 @@ class FunkinMemory
|
|||
*/
|
||||
static function permanentCacheTexture(key:String):Void
|
||||
{
|
||||
if (permanentCachedTextures.exists(key)) return;
|
||||
if (permanentCachedTextures.exists(key))
|
||||
{
|
||||
return; // Already cached.
|
||||
}
|
||||
|
||||
var graphic:Null<FlxGraphic> = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
permanentCachedTextures.set(key, graphic);
|
||||
}
|
||||
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
permanentCachedTextures.set(key, graphic);
|
||||
forceRender(graphic);
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the GPU to load and upload a FlxGraphic.
|
||||
*/
|
||||
private static function forceRender(graphic:FlxGraphic):Void
|
||||
{
|
||||
if (graphic == null) return;
|
||||
|
||||
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
|
||||
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
|
||||
|
||||
// Draws sprite and actually caches it.
|
||||
var sprite = new flixel.FlxSprite();
|
||||
sprite.loadGraphic(graphic);
|
||||
sprite.draw(); // Draw sprite and load it into game's memory.
|
||||
sprite.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if graphic with given path cached in memory.
|
||||
*/
|
||||
public static inline function isGraphicCached(path:String):Bool
|
||||
return permanentCachedTextures.exists(path) || currentCachedTextures.exists(path) || previousCachedTextures.exists(path);
|
||||
|
||||
public static function getCachedGraphic(path:String):Null<FlxGraphic>
|
||||
{
|
||||
if (permanentCachedTextures.exists(path)) return permanentCachedTextures.get(path);
|
||||
if (currentCachedTextures.exists(path)) return currentCachedTextures.get(path);
|
||||
if (previousCachedTextures.exists(path)) return previousCachedTextures.get(path); // just in case
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the cache for purging unused textures.
|
||||
*/
|
||||
|
|
|
@ -82,8 +82,11 @@ class InitState extends FlxState
|
|||
// GAME SETUP
|
||||
//
|
||||
|
||||
// Setup window events (like callbacks for onWindowClose) and fullscreen keybind setup
|
||||
// Setup window events (like callbacks for onWindowClose)
|
||||
// and fullscreen keybind setup
|
||||
WindowUtil.initWindowEvents();
|
||||
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
|
||||
WindowUtil.disableCrashHandler();
|
||||
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
funkin.util.WindowUtil.initTracy();
|
||||
|
@ -111,7 +114,7 @@ class InitState extends FlxState
|
|||
|
||||
#if ios
|
||||
// Setup Audio session
|
||||
funkin.external.ios.AudioSession.initialize();
|
||||
funkin.mobile.external.ios.AudioSession.initialize();
|
||||
#end
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
|
@ -199,7 +202,7 @@ class InitState extends FlxState
|
|||
//
|
||||
#if android
|
||||
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
|
||||
funkin.external.android.CallbackUtil.init();
|
||||
funkin.mobile.external.android.CallbackUtil.init();
|
||||
#end
|
||||
|
||||
//
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.api.discord;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types.DiscordButton;
|
||||
|
@ -12,7 +11,7 @@ import sys.thread.Thread;
|
|||
@:nullSafety
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
@ -41,28 +40,12 @@ class DiscordClient
|
|||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
if (!hasValidCredentials())
|
||||
{
|
||||
FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
}
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `false` if the client ID is invalid.
|
||||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
|
||||
}
|
||||
|
||||
var daemon:Null<Thread> = null;
|
||||
|
||||
function createDaemon():Void
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.objects.SaveSlot;
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import funkin.save.Save;
|
||||
|
||||
@:nullSafety
|
||||
@:access(funkin.save.Save)
|
||||
class NGSaveSlot
|
||||
{
|
||||
public static var instance(get, never):NGSaveSlot;
|
||||
static var _instance:Null<NGSaveSlot> = null;
|
||||
|
||||
static function get_instance():NGSaveSlot
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
return loadInstance();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public static function loadInstance():NGSaveSlot
|
||||
{
|
||||
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
|
||||
if (_instance == null) _instance = loadedSave;
|
||||
|
||||
return loadedSave;
|
||||
}
|
||||
|
||||
static function loadSlot(slot:Int):NGSaveSlot
|
||||
{
|
||||
trace('[NEWGROUNDS] Getting save slot from ID $slot');
|
||||
|
||||
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
|
||||
|
||||
var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot);
|
||||
return saveSlotObj;
|
||||
}
|
||||
|
||||
public var ngSaveSlot:Null<SaveSlot> = null;
|
||||
|
||||
public function new(?ngSaveSlot:Null<SaveSlot>)
|
||||
{
|
||||
this.ngSaveSlot = ngSaveSlot;
|
||||
|
||||
#if FLX_DEBUG
|
||||
FlxG.console.registerClass(NGSaveSlot);
|
||||
FlxG.console.registerClass(Save);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves `data` to the newgrounds save slot.
|
||||
* @param data The raw save data.
|
||||
*/
|
||||
public function save(data:RawSaveData):Void
|
||||
{
|
||||
var encodedData:String = haxe.Serializer.run(data);
|
||||
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function load(?onComplete:Null<Dynamic->Void>, ?onError:Null<CallError->Void>):Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(value):
|
||||
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Save Slot Data:');
|
||||
trace(value);
|
||||
#end
|
||||
|
||||
if (onComplete != null && value != null)
|
||||
{
|
||||
var decodedData:Dynamic = haxe.Unserializer.run(value);
|
||||
onComplete(decodedData);
|
||||
}
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(RESPONSE({message: error, code: 500}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function clear():Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully cleared save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSlot():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
|
||||
|
||||
trace(' Is null? ${ngSaveSlot == null}');
|
||||
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
import funkin.save.Save;
|
||||
import funkin.api.newgrounds.Medals.Medal;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
@ -11,18 +10,13 @@ import io.newgrounds.NGLite.LoginOutcome;
|
|||
import io.newgrounds.NGLite.LoginFail;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
import io.newgrounds.objects.User;
|
||||
|
||||
@:nullSafety
|
||||
class NewgroundsClient
|
||||
{
|
||||
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
|
||||
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
|
||||
|
||||
public static var instance(get, never):NewgroundsClient;
|
||||
|
||||
static var _instance:Null<NewgroundsClient> = null;
|
||||
|
||||
static function get_instance():NewgroundsClient
|
||||
|
@ -35,15 +29,14 @@ class NewgroundsClient
|
|||
public var user(get, never):Null<User>;
|
||||
public var medals(get, never):Null<MedalList>;
|
||||
public var leaderboards(get, never):Null<ScoreBoardList>;
|
||||
public var saveSlots(get, never):Null<SaveSlotList>;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[NEWGROUNDS] Initializing client...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
trace('[NEWGROUNDS] App ID: ${APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
|
||||
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
|
||||
#end
|
||||
|
||||
if (!hasValidCredentials())
|
||||
|
@ -52,12 +45,9 @@ class NewgroundsClient
|
|||
return;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
|
||||
|
||||
NG.core.setupEncryption(ENCRYPTION_KEY);
|
||||
}
|
||||
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
|
||||
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
|
||||
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
public function init()
|
||||
|
@ -176,12 +166,12 @@ class NewgroundsClient
|
|||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(APP_ID == null
|
||||
|| APP_ID == ""
|
||||
|| (APP_ID != null && APP_ID.contains(" "))
|
||||
|| ENCRYPTION_KEY == null
|
||||
|| ENCRYPTION_KEY == ""
|
||||
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
|
||||
return !(NewgroundsCredentials.APP_ID == null
|
||||
|| NewgroundsCredentials.APP_ID == ""
|
||||
|| NewgroundsCredentials.APP_ID.contains(" ")
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
|
||||
}
|
||||
|
||||
function onLoginResolved(outcome:LoginOutcome):Void
|
||||
|
@ -246,8 +236,6 @@ class NewgroundsClient
|
|||
|
||||
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||
trace('[NEWGROUNDS] Submitting save slot request...');
|
||||
NG.core.saveSlots.loadList(onFetchedSaveSlots);
|
||||
}
|
||||
|
||||
function onLoginFailed(result:LoginFail):Void
|
||||
|
@ -313,13 +301,6 @@ class NewgroundsClient
|
|||
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||
}
|
||||
|
||||
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched save slots!');
|
||||
|
||||
NGSaveSlot.instance.checkSlot();
|
||||
}
|
||||
|
||||
function get_user():Null<User>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
|
@ -338,12 +319,6 @@ class NewgroundsClient
|
|||
return NG.core.scoreBoards;
|
||||
}
|
||||
|
||||
function get_saveSlots():Null<SaveSlotList>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.saveSlots;
|
||||
}
|
||||
|
||||
static function getSessionId():Null<String>
|
||||
{
|
||||
#if js
|
||||
|
|
|
@ -72,7 +72,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
override function set_volume(value:Float):Float
|
||||
{
|
||||
// Uncap the volume.
|
||||
_volume = value.clamp(0.0, MAX_VOLUME);
|
||||
_volume = FlxMath.bound(value, 0.0, MAX_VOLUME);
|
||||
updateTransform();
|
||||
return _volume;
|
||||
}
|
||||
|
@ -568,8 +568,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
if (_sound == null) return;
|
||||
|
||||
// Create a channel manually if the sound is considered important.
|
||||
var pan:Float = (SoundMixer.__soundTransform.pan + _transform.pan).clamp(-1, 1);
|
||||
var volume:Float = (SoundMixer.__soundTransform.volume * _transform.volume).clamp(0, MAX_VOLUME);
|
||||
var pan:Float = FlxMath.bound(SoundMixer.__soundTransform.pan + _transform.pan, -1, 1);
|
||||
var volume:Float = FlxMath.bound(SoundMixer.__soundTransform.volume * _transform.volume, 0, MAX_VOLUME);
|
||||
|
||||
var audioSource:AudioSource = new AudioSource(_sound.__buffer);
|
||||
audioSource.offset = Std.int(startTime);
|
||||
|
|
|
@ -165,7 +165,7 @@ typedef NoteStyleAssetData<T> =
|
|||
var assetPath:String;
|
||||
|
||||
/**
|
||||
* The scale to render the note at.
|
||||
* The scale to render the prop at.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:default(1.0)
|
||||
|
@ -181,7 +181,7 @@ typedef NoteStyleAssetData<T> =
|
|||
var offsets:Null<Array<Float>>;
|
||||
|
||||
/**
|
||||
* If true, the note is a pixel sprite, and will be rendered without anti-aliasing.
|
||||
* If true, the prop is a pixel sprite, and will be rendered without anti-aliasing.
|
||||
*/
|
||||
@:default(false)
|
||||
@:optional
|
||||
|
|
|
@ -1117,23 +1117,6 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
|
||||
public function buildTooltip():String
|
||||
{
|
||||
if ((this.kind?.length ?? 0) == 0) return "";
|
||||
|
||||
var result:String = 'Kind: ${this.kind}';
|
||||
if (this.params.length == 0) return result;
|
||||
|
||||
result += "\nParams:";
|
||||
|
||||
for (param in params)
|
||||
{
|
||||
result += '\n- ${param.name}: ${param.value}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
75
source/funkin/external/windows/WinAPI.hx
vendored
75
source/funkin/external/windows/WinAPI.hx
vendored
|
@ -1,75 +0,0 @@
|
|||
package funkin.external.windows;
|
||||
|
||||
#if (windows && cpp)
|
||||
/**
|
||||
* This class provides handling for Windows API-related functions.
|
||||
*/
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('winapi.hpp')
|
||||
extern class WinAPI
|
||||
{
|
||||
/**
|
||||
* Shows a message box with an error icon.
|
||||
*
|
||||
* @param message The message to display.
|
||||
* @param title The title of the message box.
|
||||
*/
|
||||
@:native('WINAPI_ShowError')
|
||||
static function showError(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
||||
|
||||
/**
|
||||
* Shows a message box with a warning icon.
|
||||
*
|
||||
* @param message The message to display.
|
||||
* @param title The title of the message box.
|
||||
*/
|
||||
@:native('WINAPI_ShowWarning')
|
||||
static function showWarning(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
||||
|
||||
/**
|
||||
* Shows a message box with an information icon.
|
||||
*
|
||||
* @param message The message to display.
|
||||
* @param title The title of the message box.
|
||||
*/
|
||||
@:native('WINAPI_ShowInformation')
|
||||
static function showInformation(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
||||
|
||||
/**
|
||||
* Shows a message box with a question icon.
|
||||
*
|
||||
* @param message The message to display.
|
||||
* @param title The title of the message box.
|
||||
*/
|
||||
@:native('WINAPI_ShowQuestion')
|
||||
static function showQuestion(message:cpp.ConstCharStar, title:cpp.ConstCharStar):Void;
|
||||
|
||||
/**
|
||||
* Disables the "Report to Microsoft" dialog that appears when the application crashes.
|
||||
*/
|
||||
@:native('WINAPI_DisableErrorReporting')
|
||||
static function disableErrorReporting():Void;
|
||||
|
||||
/**
|
||||
* Disables Windows ghosting, which prevents the system from marking unresponsive windows as "Not Responding."
|
||||
*/
|
||||
@:native('WINAPI_DisableWindowsGhosting')
|
||||
static function disableWindowsGhosting():Void;
|
||||
|
||||
/**
|
||||
* Sets the dark mode for the active window.
|
||||
*
|
||||
* @param enable A boolean value indicating whether to enable (true) or disable (false) dark mode.
|
||||
*/
|
||||
@:native('WINAPI_SetDarkMode')
|
||||
static function setDarkMode(enable:Bool):Void;
|
||||
|
||||
/**
|
||||
* Checks if the system is using dark mode.
|
||||
*
|
||||
* @return True if system is in dark mode, false otherwise.
|
||||
*/
|
||||
@:native('WINAPI_IsSystemDarkMode')
|
||||
static function isSystemDarkMode():Bool;
|
||||
}
|
||||
#end
|
36
source/funkin/external/windows/project/Build.xml
vendored
36
source/funkin/external/windows/project/Build.xml
vendored
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xml>
|
||||
<pragma once="true" />
|
||||
|
||||
<files id="haxe">
|
||||
<compilerflag value="-I${this_dir}/include" />
|
||||
</files>
|
||||
|
||||
<files id="__main__">
|
||||
<compilerflag value="-I${this_dir}/include" />
|
||||
</files>
|
||||
|
||||
<files id="winapi">
|
||||
<compilerflag value="-I${this_dir}/include" />
|
||||
|
||||
<file name="${this_dir}/src/winapi.cpp" />
|
||||
</files>
|
||||
|
||||
<target id="haxe">
|
||||
<section if="mingw">
|
||||
<lib name="-luser32" />
|
||||
<lib name="-lkernel32" />
|
||||
<lib name="-ldwmapi" />
|
||||
<lib name="-ladvapi32" />
|
||||
</section>
|
||||
|
||||
<section if="windows" unless="mingw">
|
||||
<lib name="user32.lib" />
|
||||
<lib name="kernel32.lib" />
|
||||
<lib name="dwmapi.lib" />
|
||||
<lib name="advapi32.lib" />
|
||||
</section>
|
||||
|
||||
<files id="winapi" />
|
||||
</target>
|
||||
</xml>
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* @brief Shows an error message box
|
||||
* @param message The error message to display
|
||||
* @param title The title of the message box
|
||||
*/
|
||||
void WINAPI_ShowError(const char *message, const char *title);
|
||||
|
||||
/**
|
||||
* @brief Shows a warning message box
|
||||
* @param message The warning message to display
|
||||
* @param title The title of the message box
|
||||
*/
|
||||
void WINAPI_ShowWarning(const char *message, const char *title);
|
||||
|
||||
/**
|
||||
* @brief Shows an information message box
|
||||
* @param message The information message to display
|
||||
* @param title The title of the message box
|
||||
*/
|
||||
void WINAPI_ShowInformation(const char *message, const char *title);
|
||||
|
||||
/**
|
||||
* @brief Shows a question message box with OK/Cancel buttons
|
||||
* @param message The question message to display
|
||||
* @param title The title of the message box
|
||||
*/
|
||||
void WINAPI_ShowQuestion(const char *message, const char *title);
|
||||
|
||||
/**
|
||||
* @brief Disables Windows error reporting dialogs
|
||||
*/
|
||||
void WINAPI_DisableErrorReporting();
|
||||
|
||||
/**
|
||||
* @brief Disables Windows ghosting for the current process
|
||||
*/
|
||||
void WINAPI_DisableWindowsGhosting();
|
||||
|
||||
/**
|
||||
* @brief Sets dark mode for the active window
|
||||
* @param enable True to enable dark mode, false to disable
|
||||
*/
|
||||
void WINAPI_SetDarkMode(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Checks if the system is using dark mode
|
||||
* @return True if system is in dark mode, false otherwise
|
||||
*/
|
||||
bool WINAPI_IsSystemDarkMode();
|
|
@ -1,75 +0,0 @@
|
|||
#define WIN32_LEAN_AND_MEAN // Excludes rarely-used APIs like cryptography, DDE, RPC, and shell functions, reducing compile time and binary size.
|
||||
#define NOMINMAX // Prevents Windows from defining min() and max() macros, which can conflict with standard C++ functions.
|
||||
#define NOCRYPT // Excludes Cryptographic APIs, such as Encrypt/Decrypt functions.
|
||||
#define NOCOMM // Excludes serial communication APIs, such as COM port handling.
|
||||
#define NOKANJI // Excludes Kanji character set support (not needed unless working with Japanese text processing).
|
||||
#define NOHELP // Excludes Windows Help APIs, removing functions related to WinHelp and other help systems.
|
||||
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <dwmapi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void WINAPI_ShowError(const char *message, const char *title)
|
||||
{
|
||||
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
void WINAPI_ShowWarning(const char *message, const char *title)
|
||||
{
|
||||
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONWARNING);
|
||||
}
|
||||
|
||||
void WINAPI_ShowInformation(const char *message, const char *title)
|
||||
{
|
||||
MessageBox(GetActiveWindow(), message, title, MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
void WINAPI_ShowQuestion(const char *message, const char *title)
|
||||
{
|
||||
MessageBox(GetActiveWindow(), message, title, MB_OKCANCEL | MB_ICONQUESTION);
|
||||
}
|
||||
|
||||
void WINAPI_DisableErrorReporting()
|
||||
{
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
}
|
||||
|
||||
void WINAPI_DisableWindowsGhosting()
|
||||
{
|
||||
DisableProcessWindowsGhosting();
|
||||
}
|
||||
|
||||
void WINAPI_SetDarkMode(bool enable)
|
||||
{
|
||||
HWND window = GetActiveWindow();
|
||||
|
||||
int darkMode = enable ? 1 : 0;
|
||||
|
||||
if (DwmSetWindowAttribute(window, 20, &darkMode, sizeof(darkMode)) != S_OK)
|
||||
DwmSetWindowAttribute(window, 19, &darkMode, sizeof(darkMode));
|
||||
|
||||
UpdateWindow(window);
|
||||
}
|
||||
|
||||
bool WINAPI_IsSystemDarkMode()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD dwValue = 0;
|
||||
DWORD dwSize = sizeof(DWORD);
|
||||
DWORD dwType = REG_DWORD;
|
||||
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
if (RegQueryValueEx(hKey, "AppsUseLightTheme", NULL, &dwType, (LPBYTE)&dwValue, &dwSize) == ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return dwValue == 0;
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -12,7 +12,6 @@ import flixel.math.FlxPoint;
|
|||
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
||||
import flixel.FlxCamera;
|
||||
import openfl.system.System;
|
||||
import funkin.FunkinMemory;
|
||||
|
||||
using StringTools;
|
||||
|
||||
|
@ -24,6 +23,20 @@ using StringTools;
|
|||
@:nullSafety
|
||||
class FunkinSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* An internal list of all the textures cached with `cacheTexture`.
|
||||
* This excludes any temporary textures like those from `FlxText` or `makeSolidColor`.
|
||||
*/
|
||||
static var currentCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
/**
|
||||
* An internal list of textures that were cached in the previous state.
|
||||
* We don't know whether we want to keep them cached or not.
|
||||
*/
|
||||
static var previousCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
static var permanentCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
/**
|
||||
* @param x Starting X position
|
||||
* @param y Starting Y position
|
||||
|
@ -203,40 +216,122 @@ class FunkinSprite extends FlxSprite
|
|||
return FlxG.bitmap.get(key) != null;
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.cacheTexture() instead")
|
||||
/**
|
||||
* Ensure the texture with the given key is cached.
|
||||
* @param key The key of the texture to cache.
|
||||
*/
|
||||
public static function cacheTexture(key:String):Void
|
||||
{
|
||||
FunkinMemory.cacheTexture(Paths.image(key));
|
||||
// We don't want to cache the same texture twice.
|
||||
if (currentCachedTextures.exists(key)) return;
|
||||
|
||||
if (previousCachedTextures.exists(key))
|
||||
{
|
||||
// Move the graphic from the previous cache to the current cache.
|
||||
var graphic = previousCachedTextures.get(key);
|
||||
previousCachedTextures.remove(key);
|
||||
if (graphic != null) currentCachedTextures.set(key, graphic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Else, texture is currently uncached.
|
||||
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
}
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.permanentCacheTexture() instead")
|
||||
public static function permanentCacheTexture(key:String):Void
|
||||
{
|
||||
@:privateAccess FunkinMemory.permanentCacheTexture(Paths.image(key));
|
||||
// We don't want to cache the same texture twice.
|
||||
if (permanentCachedTextures.exists(key)) return;
|
||||
|
||||
// Else, texture is currently uncached.
|
||||
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
permanentCachedTextures.set(key, graphic);
|
||||
}
|
||||
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.cacheTexture() instead")
|
||||
public static function cacheSparrow(key:String):Void
|
||||
{
|
||||
FunkinMemory.cacheTexture(Paths.image(key));
|
||||
cacheTexture(Paths.image(key));
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.cacheTexture() instead")
|
||||
public static function cachePacker(key:String):Void
|
||||
{
|
||||
FunkinMemory.cacheTexture(Paths.image(key));
|
||||
cacheTexture(Paths.image(key));
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.preparePurgeTextureCache() instead")
|
||||
/**
|
||||
* Call this, then `cacheTexture` to keep the textures we still need, then `purgeCache` to remove the textures that we won't be using anymore.
|
||||
*/
|
||||
public static function preparePurgeCache():Void
|
||||
{
|
||||
FunkinMemory.preparePurgeTextureCache();
|
||||
previousCachedTextures = currentCachedTextures;
|
||||
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
if (!permanentCachedTextures.exists(graphicKey)) continue;
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
}
|
||||
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
}
|
||||
|
||||
@:deprecated("Use FunkinMemory.purgeCache() instead")
|
||||
public static function purgeCache():Void
|
||||
{
|
||||
FunkinMemory.purgeCache();
|
||||
// Everything that is in previousCachedTextures but not in currentCachedTextures should be destroyed.
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
var graphic = previousCachedTextures.get(graphicKey);
|
||||
if (graphic == null) continue;
|
||||
FlxG.bitmap.remove(graphic);
|
||||
graphic.destroy();
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
}
|
||||
@:privateAccess
|
||||
if (FlxG.bitmap._cache == null)
|
||||
{
|
||||
@:privateAccess
|
||||
FlxG.bitmap._cache = new Map();
|
||||
System.gc();
|
||||
return;
|
||||
}
|
||||
|
||||
@:privateAccess
|
||||
for (key in FlxG.bitmap._cache.keys())
|
||||
{
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
if (obj == null) continue;
|
||||
if (obj.persist) continue;
|
||||
if (permanentCachedTextures.exists(key)) continue;
|
||||
if (!(obj.useCount <= 0 || key.contains("characters") || key.contains("charSelect") || key.contains("results"))) continue;
|
||||
|
||||
FlxG.bitmap.removeKey(key);
|
||||
obj.destroy();
|
||||
}
|
||||
openfl.Assets.cache.clear("songs");
|
||||
openfl.Assets.cache.clear("sounds");
|
||||
openfl.Assets.cache.clear("music");
|
||||
|
||||
System.gc();
|
||||
}
|
||||
|
||||
static function isGraphicCached(graphic:FlxGraphic):Bool
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.external.android;
|
||||
package funkin.mobile.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
||||
import flixel.util.FlxSignal;
|
||||
|
||||
|
@ -10,6 +9,7 @@ import flixel.util.FlxSignal;
|
|||
@:unreflective
|
||||
class CallbackUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* The result code for `DATA_FOLDER_CLOSED` activity.
|
||||
*/
|
||||
|
@ -42,6 +42,7 @@ class CallbackUtil
|
|||
initCallBackJNI(new CallbackHandler());
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,7 +50,8 @@ class CallbackUtil
|
|||
*/
|
||||
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
|
||||
{
|
||||
@:allow(funkin.external.android.CallbackUtil)
|
||||
#if android
|
||||
@:allow(funkin.mobile.external.android.CallbackUtil)
|
||||
function new():Void {}
|
||||
|
||||
/**
|
||||
|
@ -66,5 +68,5 @@ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
|
|||
{
|
||||
if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,12 +1,12 @@
|
|||
package funkin.external.android;
|
||||
package funkin.mobile.external.android;
|
||||
|
||||
#if android
|
||||
/**
|
||||
* A Utility class to manage the Application's Data folder on Android.
|
||||
*/
|
||||
@:unreflective
|
||||
class DataFolderUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* Opens the data folder on an Android device using JNI.
|
||||
*/
|
||||
|
@ -19,5 +19,5 @@ class DataFolderUtil
|
|||
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.external.android;
|
||||
package funkin.mobile.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.external.android;
|
||||
package funkin.mobile.external.android;
|
||||
|
||||
#if android
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.JNI;
|
||||
|
||||
|
@ -10,6 +9,7 @@ import lime.system.JNI;
|
|||
@:unreflective
|
||||
class ScreenUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* Retrieves the dimensions of display cutouts (such as notches) on Android devices.
|
||||
*
|
||||
|
@ -48,5 +48,5 @@ class ScreenUtil
|
|||
|
||||
return [];
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,10 +1,9 @@
|
|||
package funkin.external.ios;
|
||||
package funkin.mobile.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to manage iOS audio.
|
||||
*/
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('AudioSession.hpp')
|
||||
@:unreflective
|
||||
extern class AudioSession
|
||||
|
@ -14,4 +13,3 @@ extern class AudioSession
|
|||
@:native('setActive')
|
||||
static function setActive(active:Bool):Void;
|
||||
}
|
||||
#end
|
|
@ -1,10 +1,9 @@
|
|||
package funkin.external.ios;
|
||||
package funkin.mobile.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to get iOS screen related informations.
|
||||
*/
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('ScreenUtil.hpp')
|
||||
@:unreflective
|
||||
extern class ScreenUtil
|
||||
|
@ -15,4 +14,3 @@ extern class ScreenUtil
|
|||
@:native('getScreenSize')
|
||||
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
|
||||
}
|
||||
#end
|
|
@ -125,7 +125,7 @@ class ControlsHandler
|
|||
@:noCompletion
|
||||
private static function get_hasExternalInputDevice():Bool
|
||||
{
|
||||
return FlxG.gamepads.numActiveGamepads > 0 #if android || extension.androidtools.Tools.isChromebook() #end;
|
||||
return FlxG.gamepads.numActiveGamepads > 0;
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
|
|
37
source/funkin/mobile/macros/LinkerMacro.hx
Normal file
37
source/funkin/mobile/macros/LinkerMacro.hx
Normal file
|
@ -0,0 +1,37 @@
|
|||
package funkin.mobile.macros;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.xml.Printer;
|
||||
import sys.FileSystem;
|
||||
|
||||
using haxe.io.Path;
|
||||
|
||||
/**
|
||||
* This class provides a macro to include an XML build file in the metadata of a Haxe class.
|
||||
* The file must be located relative to the directory of the Haxe class that uses this macro.
|
||||
*/
|
||||
@:nullSafety
|
||||
class LinkerMacro
|
||||
{
|
||||
/**
|
||||
* Adds an XML `<include>` element to the class's metadata, pointing to a specified build file.
|
||||
* @param file_name The name of the XML file to include. Defaults to `Build.xml` if not provided.
|
||||
* @return An array of fields that are processed during the build.
|
||||
*/
|
||||
public static macro function xml(?file_name:String = 'Build.xml'):Array<Field>
|
||||
{
|
||||
final pos:Position = Context.currentPos();
|
||||
final sourcePath:String = FileSystem.absolutePath(Context.getPosInfos(pos).file.directory()).removeTrailingSlashes();
|
||||
final fileToInclude:String = Path.join([sourcePath, file_name?.length > 0 ? file_name : 'Build.xml']);
|
||||
|
||||
if (!FileSystem.exists(fileToInclude)) Context.error('The specified file "$fileToInclude" could not be found at "$sourcePath".', pos);
|
||||
|
||||
final includeElement:Xml = Xml.createElement('include');
|
||||
includeElement.set('name', fileToInclude);
|
||||
Context.getLocalClass().get().meta.add(':buildXml', [
|
||||
{expr: EConst(CString(Printer.print(includeElement, true))), pos: pos}], pos);
|
||||
|
||||
return Context.getBuildFields();
|
||||
}
|
||||
}
|
|
@ -15,14 +15,7 @@ class FunkinBackButton extends FunkinButton
|
|||
|
||||
public var enabled:Bool = true;
|
||||
|
||||
public var confirming(get, never):Bool;
|
||||
|
||||
function get_confirming():Bool
|
||||
{
|
||||
return _confirming;
|
||||
}
|
||||
|
||||
var _confirming:Bool = false;
|
||||
var confirming:Bool = false;
|
||||
|
||||
public var restingOpacity:Float;
|
||||
|
||||
|
@ -93,7 +86,7 @@ class FunkinBackButton extends FunkinButton
|
|||
return;
|
||||
}
|
||||
|
||||
_confirming = true;
|
||||
confirming = true;
|
||||
|
||||
FlxTween.cancelTweensOf(this);
|
||||
HapticUtil.vibrate(0, 0.05, 0.5);
|
||||
|
@ -105,7 +98,7 @@ class FunkinBackButton extends FunkinButton
|
|||
|
||||
animation.onFinish.addOnce(function(name:String) {
|
||||
if (name != 'confirm') return;
|
||||
_confirming = false;
|
||||
confirming = false;
|
||||
held = false;
|
||||
onConfirmEnd.dispatch();
|
||||
});
|
||||
|
@ -134,7 +127,7 @@ class FunkinBackButton extends FunkinButton
|
|||
onDown.removeAll();
|
||||
onOut.removeAll();
|
||||
|
||||
_confirming = false;
|
||||
confirming = false;
|
||||
held = false;
|
||||
|
||||
onUp.add(playConfirmAnim);
|
||||
|
|
|
@ -12,16 +12,7 @@ class FunkinOptionsButton extends FunkinButton
|
|||
public var onConfirmStart(default, null):FlxSignal = new FlxSignal();
|
||||
public var onConfirmEnd(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
public var enabled:Bool = true;
|
||||
|
||||
public var confirming(get, never):Bool;
|
||||
|
||||
function get_confirming():Bool
|
||||
{
|
||||
return _confirming;
|
||||
}
|
||||
|
||||
var _confirming:Bool = false;
|
||||
var confirming:Bool = false;
|
||||
var instant:Bool = false;
|
||||
var held:Bool = false;
|
||||
|
||||
|
@ -58,7 +49,7 @@ class FunkinOptionsButton extends FunkinButton
|
|||
|
||||
function playHoldAnim():Void
|
||||
{
|
||||
if (confirming || held || !enabled) return;
|
||||
if (confirming || held) return;
|
||||
|
||||
held = true;
|
||||
|
||||
|
@ -79,7 +70,7 @@ class FunkinOptionsButton extends FunkinButton
|
|||
return;
|
||||
}
|
||||
|
||||
_confirming = true;
|
||||
confirming = true;
|
||||
|
||||
FlxTween.cancelTweensOf(this);
|
||||
HapticUtil.vibrate(0, 0.05, 0.5);
|
||||
|
@ -95,20 +86,17 @@ class FunkinOptionsButton extends FunkinButton
|
|||
|
||||
animation.onFinish.addOnce(function(name:String) {
|
||||
if (name != 'confirm') return;
|
||||
_confirming = false;
|
||||
held = false;
|
||||
onConfirmEnd.dispatch();
|
||||
});
|
||||
}
|
||||
|
||||
function playOutAnim():Void
|
||||
{
|
||||
if (confirming || !enabled) return;
|
||||
if (confirming) return;
|
||||
|
||||
FlxTween.cancelTweensOf(this);
|
||||
HapticUtil.vibrate(0, 0.01, 0.2);
|
||||
animation.play('idle');
|
||||
held = false;
|
||||
}
|
||||
|
||||
public function resetCallbacks():Void
|
||||
|
@ -117,7 +105,7 @@ class FunkinOptionsButton extends FunkinButton
|
|||
onDown.removeAll();
|
||||
onOut.removeAll();
|
||||
|
||||
_confirming = false;
|
||||
confirming = false;
|
||||
held = false;
|
||||
|
||||
onUp.add(playConfirmAnim);
|
||||
|
|
|
@ -270,7 +270,7 @@ class ControlsSchemeMenu extends MusicBeatSubState
|
|||
*/
|
||||
function setSelection(index:Int):Void
|
||||
{
|
||||
final newIndex:Int = Math.floor(index.clamp(0, hitboxShowcases.length - 1));
|
||||
final newIndex:Int = Math.floor(FlxMath.bound(index, 0, hitboxShowcases.length - 1));
|
||||
|
||||
if (currentIndex != newIndex)
|
||||
{
|
||||
|
@ -348,7 +348,7 @@ class ControlsSchemeMenu extends MusicBeatSubState
|
|||
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, showcasesTargetX, elapsed, 0.5);
|
||||
|
||||
final minShowcasesX:Float = -1500 * availableSchemes.length;
|
||||
hitboxShowcases.x = hitboxShowcases.x.clamp(minShowcasesX, 400);
|
||||
hitboxShowcases.x = FlxMath.bound(hitboxShowcases.x, minShowcasesX, 400);
|
||||
|
||||
final targetIndex:Int = Math.round(hitboxShowcases.x / -1500);
|
||||
|
||||
|
@ -356,9 +356,8 @@ class ControlsSchemeMenu extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, (-1500 * currentIndex) + (-1500 / (availableSchemes.length + 1) * currentIndex),
|
||||
elapsed, 0.5);
|
||||
hitboxShowcases.x = MathUtil.smoothLerpPrecision(hitboxShowcases.x, (-1500 * currentIndex) + (-1500 / (availableSchemes.length + 1) * currentIndex), elapsed, 0.5);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class AdMobUtil
|
|||
/**
|
||||
* AdMob publisher ID used for the application.
|
||||
*/
|
||||
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER");
|
||||
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER");
|
||||
|
||||
/**
|
||||
* Test ad unit IDs for development and testing purposes.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package funkin.mobile.util;
|
||||
|
||||
#if ios
|
||||
import funkin.external.ios.ScreenUtil as NativeScreenUtil;
|
||||
import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil;
|
||||
#elseif android
|
||||
import funkin.external.android.ScreenUtil as NativeScreenUtil;
|
||||
import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil;
|
||||
#end
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.System;
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
package funkin.modding;
|
||||
|
||||
import haxe.ds.StringMap;
|
||||
|
||||
/**
|
||||
* Temporary persistent data storage for mods to use.
|
||||
*/
|
||||
@:nullSafety
|
||||
class ModStore
|
||||
{
|
||||
/**
|
||||
* All registered stores for this session.
|
||||
*/
|
||||
public static final stores:StringMap<Dynamic> = new StringMap<Dynamic>();
|
||||
|
||||
/**
|
||||
* Attempts to register a new store with the given ID and return it.
|
||||
* If a store with the same ID already exists, that store will be returned instead (discards `data`).
|
||||
*
|
||||
* @id The unique ID for this store.
|
||||
* @data Optional initial data, uses an empty object by default.
|
||||
* @return The store data at the given ID.
|
||||
*/
|
||||
public static function register(id:String, ?data:Dynamic):Dynamic
|
||||
{
|
||||
if (stores.exists(id)) return stores.get(id);
|
||||
stores.set(id, data ??= {});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a store by ID.
|
||||
*
|
||||
* @id The target ID of the store.
|
||||
* @return The store data, or `null` if the store did not exist.
|
||||
*/
|
||||
public static function get(id:String):Null<Dynamic>
|
||||
{
|
||||
return stores.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to remove a store by ID and return it.
|
||||
*
|
||||
* @id The target ID of the store.
|
||||
* @return The store data, or `null` if the store did not exist.
|
||||
*/
|
||||
public static function remove(id:String):Null<Dynamic>
|
||||
{
|
||||
var data:Null<Dynamic> = stores.get(id);
|
||||
stores.remove(id);
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,19 @@ import polymod.Polymod;
|
|||
@:nullSafety
|
||||
class PolymodErrorHandler
|
||||
{
|
||||
/**
|
||||
* Show a popup with the given text.
|
||||
* This displays a system popup, it WILL interrupt the game.
|
||||
* Make sure to only use this when it's important, like when there's a script error.
|
||||
*
|
||||
* @param name The name at the top of the popup.
|
||||
* @param desc The body text of the popup.
|
||||
*/
|
||||
public static function showAlert(name:String, desc:String):Void
|
||||
{
|
||||
lime.app.Application.current.window.alert(desc, name);
|
||||
}
|
||||
|
||||
public static function onPolymodError(error:PolymodError):Void
|
||||
{
|
||||
// Perform an action based on the error code.
|
||||
|
@ -24,12 +37,12 @@ class PolymodErrorHandler
|
|||
// A syntax error when parsing a script.
|
||||
logError(error.message);
|
||||
// Notify the user via popup.
|
||||
funkin.util.WindowUtil.showError('Polymod Script Parsing Error', error.message);
|
||||
showAlert('Polymod Script Parsing Error', error.message);
|
||||
case SCRIPT_RUNTIME_EXCEPTION:
|
||||
// A runtime error when running a script.
|
||||
logError(error.message);
|
||||
// Notify the user via popup.
|
||||
funkin.util.WindowUtil.showError('Polymod Script Exception', error.message);
|
||||
showAlert('Polymod Script Exception', error.message);
|
||||
case SCRIPT_CLASS_MODULE_NOT_FOUND:
|
||||
// A scripted class tried to reference an unknown class or module.
|
||||
logError(error.message);
|
||||
|
@ -41,12 +54,13 @@ class PolymodErrorHandler
|
|||
msg += '\nCheck to ensure the class exists and is spelled correctly.';
|
||||
|
||||
// Notify the user via popup.
|
||||
funkin.util.WindowUtil.showError('Polymod Script Import Error', msg);
|
||||
showAlert('Polymod Script Import Error', msg);
|
||||
case SCRIPT_CLASS_MODULE_BLACKLISTED:
|
||||
// A scripted class tried to reference a blacklisted class or module.
|
||||
logError(error.message);
|
||||
// Notify the user via popup.
|
||||
funkin.util.WindowUtil.showError('Polymod Script Blacklist Violation', error.message);
|
||||
showAlert('Polymod Script Blacklist Violation', error.message);
|
||||
|
||||
default:
|
||||
// Log the message based on its severity.
|
||||
switch (error.severity)
|
||||
|
|
|
@ -4,18 +4,6 @@ import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
|||
import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
||||
/**
|
||||
* Parameters used to initialize a module.
|
||||
*/
|
||||
typedef ModuleParams =
|
||||
{
|
||||
/**
|
||||
* The state this module is associated with.
|
||||
* If set, this module will only receive events when the game is in this state.
|
||||
*/
|
||||
?state:Class<Dynamic>
|
||||
}
|
||||
|
||||
/**
|
||||
* A module is a scripted class which receives all events without requiring a specific context.
|
||||
* You may have the module active at all times, or only when another script enables it.
|
||||
|
@ -51,27 +39,16 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The state this module is associated with.
|
||||
* If set, this module will only receive events when the game is in this state.
|
||||
*/
|
||||
public var state:Null<Class<Dynamic>> = null;
|
||||
|
||||
/**
|
||||
* Called when the module is initialized.
|
||||
* It may not be safe to reference other modules here since they may not be loaded yet.
|
||||
*
|
||||
* NOTE: To make the module start inactive, call `this.active = false` in the constructor.
|
||||
*/
|
||||
public function new(moduleId:String, priority:Int = 1000, ?params:ModuleParams):Void
|
||||
public function new(moduleId:String, priority:Int = 1000):Void
|
||||
{
|
||||
this.moduleId = moduleId;
|
||||
this.priority = priority;
|
||||
|
||||
if (params != null)
|
||||
{
|
||||
this.state = params.state ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
public function toString()
|
||||
|
|
|
@ -6,7 +6,6 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.Module;
|
||||
import funkin.modding.module.ScriptedModule;
|
||||
import flixel.FlxG;
|
||||
|
||||
/**
|
||||
* Utility functions for loading and manipulating active modules.
|
||||
|
@ -146,14 +145,6 @@ class ModuleHandler
|
|||
// The module needs to be active to receive events.
|
||||
if (module != null && module.active)
|
||||
{
|
||||
if (module.state != null)
|
||||
{
|
||||
// Only call the event if the current state is what the module's state is.
|
||||
if (!(Type.getClass(FlxG.state) == module.state) && !(Type.getClass(FlxG.state?.subState) == module.state))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ScriptEventDispatcher.callEvent(module, event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import funkin.util.MathUtil;
|
|||
import funkin.effects.RetroCameraFade;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.util.TouchUtil;
|
||||
import openfl.utils.Assets;
|
||||
#if FEATURE_MOBILE_ADVERTISEMENTS
|
||||
import funkin.mobile.util.AdMobUtil;
|
||||
#end
|
||||
|
@ -101,6 +102,18 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
var canInput:Bool = false;
|
||||
|
||||
var justDied:Bool = true;
|
||||
|
||||
var isSpecialAnimation:Bool = false;
|
||||
|
||||
var gameOverVibrationPreset:VibrationPreset =
|
||||
{
|
||||
period: 0,
|
||||
duration: Constants.DEFAULT_VIBRATION_DURATION,
|
||||
amplitude: Constants.MIN_VIBRATION_AMPLITUDE,
|
||||
sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS
|
||||
};
|
||||
|
||||
public function new(params:GameOverParams)
|
||||
{
|
||||
super();
|
||||
|
@ -162,7 +175,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
if ((parentPlayState?.isMinimalMode ?? true)) {}
|
||||
else
|
||||
{
|
||||
boyfriend = parentPlayState?.currentStage?.getBoyfriend(true);
|
||||
boyfriend = parentPlayState?.currentStage.getBoyfriend(true);
|
||||
if (boyfriend != null)
|
||||
{
|
||||
boyfriend.canPlayOtherAnims = true;
|
||||
|
@ -185,17 +198,16 @@ class GameOverSubState extends MusicBeatSubState
|
|||
addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack);
|
||||
#end
|
||||
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
|
||||
// Allow input a second later to prevent accidental gameover skips.
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
canInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
function setCameraTarget():Void
|
||||
{
|
||||
if (parentPlayState == null || parentPlayState.isMinimalMode || boyfriend == null) return;
|
||||
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
|
||||
|
||||
// Assign a camera follow point to the boyfriend's position.
|
||||
cameraFollowPoint = new FlxObject(parentPlayState.cameraFollowPoint.x, parentPlayState.cameraFollowPoint.y, 1, 1);
|
||||
|
@ -206,7 +218,6 @@ class GameOverSubState extends MusicBeatSubState
|
|||
cameraFollowPoint.y += offsets[1];
|
||||
add(cameraFollowPoint);
|
||||
|
||||
@:nullSafety(Off)
|
||||
FlxG.camera.target = null;
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2);
|
||||
targetCameraZoom = (parentPlayState?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
|
||||
|
@ -319,6 +330,9 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
// Handle vibrations on update.
|
||||
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
|
||||
|
||||
// Start death music before firstDeath gets replaced
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
@ -394,7 +408,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
// Readd Boyfriend to the stage.
|
||||
boyfriend.isDead = false;
|
||||
remove(boyfriend);
|
||||
parentPlayState?.currentStage?.addCharacter(boyfriend, BF);
|
||||
parentPlayState?.currentStage.addCharacter(boyfriend, BF);
|
||||
}
|
||||
|
||||
// Snap reset the camera which may have changed because of the player character data.
|
||||
|
@ -440,7 +454,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
#else
|
||||
resetPlaying();
|
||||
#end
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -564,11 +578,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
PlayStatePlaylist.reset();
|
||||
}
|
||||
|
||||
var stickerPackId:Null<String> = parentPlayState?.currentChart?.stickerPack;
|
||||
var stickerPackId:Null<String> = parentPlayState?.currentChart.stickerPack;
|
||||
|
||||
if (stickerPackId == null)
|
||||
{
|
||||
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart?.characters.player);
|
||||
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart.characters.player);
|
||||
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
|
||||
|
||||
if (playerCharacter != null)
|
||||
|
@ -601,6 +615,108 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
var hasPlayedDeathQuote:Bool = false;
|
||||
|
||||
/**
|
||||
* Used for death haptics.
|
||||
*/
|
||||
var startedTimerHaptics:Bool = false;
|
||||
|
||||
/**
|
||||
* Unique vibrations for each death animation.
|
||||
*/
|
||||
function handleAnimationVibrations():Void
|
||||
{
|
||||
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
|
||||
|
||||
if (justDied)
|
||||
{
|
||||
if (isSpecialAnimation)
|
||||
{
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5);
|
||||
trace("It's a special game over animation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
}
|
||||
justDied = false;
|
||||
}
|
||||
|
||||
if (boyfriend.animation == null) return;
|
||||
|
||||
final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1;
|
||||
if (boyfriend.characterId.startsWith("bf"))
|
||||
{
|
||||
// BF's mic drops.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27)
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
// BF's balls pulsating.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18))
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Pico dies because of Darnell beating him up.
|
||||
if (boyfriend.characterId == "pico-blazin")
|
||||
{
|
||||
if (!startedTimerHaptics)
|
||||
{
|
||||
startedTimerHaptics = true;
|
||||
|
||||
new FlxTimer().start(0.5, function(tmr:FlxTimer) {
|
||||
// Pico falls on his knees.
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
|
||||
new FlxTimer().start(0.6, function(tmr:FlxTimer) {
|
||||
// Pico falls "asleep". :)
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene")
|
||||
{
|
||||
if (isSpecialAnimation)
|
||||
{
|
||||
if (startedTimerHaptics) return;
|
||||
|
||||
startedTimerHaptics = true;
|
||||
|
||||
// Death by Darnell's can.
|
||||
new FlxTimer().start(1.85, function(tmr:FlxTimer) {
|
||||
// Pico falls on his knees.
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pico falls on his back.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20)
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
// Blood firework woohoo!!!!
|
||||
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0)
|
||||
{
|
||||
final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE);
|
||||
final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
|
||||
HapticUtil.vibrate(0, randomDuration, randomAmplitude);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
|
|
@ -18,9 +18,9 @@ class GitarooPause extends MusicBeatState
|
|||
|
||||
var replaySelect:Bool = false;
|
||||
|
||||
var previousParams:Null<PlayStateParams>;
|
||||
var previousParams:PlayStateParams;
|
||||
|
||||
public function new(?previousParams:PlayStateParams):Void
|
||||
public function new(previousParams:PlayStateParams):Void
|
||||
{
|
||||
super();
|
||||
|
||||
|
|
|
@ -213,11 +213,6 @@ class PauseSubState extends MusicBeatSubState
|
|||
*/
|
||||
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
|
||||
|
||||
/**
|
||||
* Callback that gets called once substate gets open.
|
||||
*/
|
||||
var onPause:Void->Void;
|
||||
|
||||
// ===============
|
||||
// Audio Variables
|
||||
// ===============
|
||||
|
@ -227,11 +222,10 @@ class PauseSubState extends MusicBeatSubState
|
|||
// Constructor
|
||||
// ===============
|
||||
|
||||
public function new(?params:PauseSubStateParams, ?onPause:Void->Void)
|
||||
public function new(?params:PauseSubStateParams)
|
||||
{
|
||||
super();
|
||||
this.currentMode = params?.mode ?? Standard;
|
||||
this.onPause = onPause;
|
||||
}
|
||||
|
||||
// ===============
|
||||
|
@ -250,8 +244,6 @@ class PauseSubState extends MusicBeatSubState
|
|||
AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT);
|
||||
#end
|
||||
|
||||
if (onPause != null) onPause();
|
||||
|
||||
super.create();
|
||||
|
||||
startPauseMusic();
|
||||
|
@ -294,7 +286,6 @@ class PauseSubState extends MusicBeatSubState
|
|||
hapticTimer.cancel();
|
||||
hapticTimer = null;
|
||||
pauseMusic.stop();
|
||||
onPause = null;
|
||||
}
|
||||
|
||||
// ===============
|
||||
|
@ -453,7 +444,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
offsetText.y = FlxG.height - (offsetText.height + offsetText.height + 40);
|
||||
offsetTextInfo.y = offsetText.y + offsetText.height + 4;
|
||||
|
||||
#if (!mobile && FEATURE_LAG_ADJUSTMENT)
|
||||
#if !mobile
|
||||
metadata.add(offsetText);
|
||||
metadata.add(offsetTextInfo);
|
||||
#end
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -527,7 +527,8 @@ class ResultState extends MusicBeatSubState
|
|||
bgFlash.visible = true;
|
||||
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
||||
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100;
|
||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
|
||||
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
|
||||
clearPercentTarget = Math.floor(clearPercentFloat);
|
||||
// Prevent off-by-one errors.
|
||||
|
||||
|
@ -743,7 +744,6 @@ class ResultState extends MusicBeatSubState
|
|||
super.draw();
|
||||
|
||||
songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
|
||||
clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height));
|
||||
|
||||
// PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
|
||||
|
||||
|
@ -751,6 +751,105 @@ class ResultState extends MusicBeatSubState
|
|||
// maskShaderSongName.frameUV = songName.frame.uv;
|
||||
}
|
||||
|
||||
private function handleAnimationVibrations()
|
||||
{
|
||||
for (atlas in characterAtlasAnimations)
|
||||
{
|
||||
if (atlas == null || atlas.sprite == null) continue;
|
||||
|
||||
switch (rank)
|
||||
{
|
||||
case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Feel the bed fun :freaky:
|
||||
case "bf":
|
||||
if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
// GF slams into the wall.
|
||||
if (atlas.sprite.anim.curFrame == 51)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pico drop-kicking Nene.
|
||||
case "pico":
|
||||
if (atlas.sprite.anim.curFrame == 52)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.GREAT | ScoringRank.EXCELLENT:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Pico explodes the targets with a rocket launcher.
|
||||
case "pico":
|
||||
// Pico shoots.
|
||||
if (atlas.sprite.anim.curFrame == 45)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
break;
|
||||
}
|
||||
|
||||
// The targets explode.
|
||||
if (atlas.sprite.anim.curFrame == 50)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.GOOD:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Pico shooting the targets.
|
||||
case "pico":
|
||||
if (atlas.sprite.anim.curFrame % 2 != 0) continue;
|
||||
|
||||
final frames:Array<Array<Int>> = [[40, 50], [80, 90], [140, 157]];
|
||||
for (i in 0...frames.length)
|
||||
{
|
||||
if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue;
|
||||
|
||||
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.SHIT:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// BF falling and GF slams on BF with her ass.
|
||||
case "bf":
|
||||
if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||
|
@ -936,6 +1035,8 @@ class ResultState extends MusicBeatSubState
|
|||
#end
|
||||
}
|
||||
|
||||
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
|
|
|
@ -580,7 +580,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
|
||||
override function set_alpha(value:Float):Float
|
||||
{
|
||||
value = value.clamp(0, 1);
|
||||
value = FlxMath.bound(value, 0, 1);
|
||||
|
||||
if (exists && alpha != value)
|
||||
{
|
||||
|
|
|
@ -288,22 +288,33 @@ class CharacterDataParser
|
|||
{
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
final charIDParts:Array<String> = char.split("-");
|
||||
var iconName:String = "";
|
||||
for (i in 0...charIDParts.length)
|
||||
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
|
||||
switch (char)
|
||||
{
|
||||
iconName += charIDParts[i];
|
||||
|
||||
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
|
||||
{
|
||||
charPath += '${iconName}pixel';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < charIDParts.length - 1) iconName += '-';
|
||||
continue;
|
||||
}
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas" | "tankman-bloody":
|
||||
charPath += "tankmanpixel";
|
||||
case "pico-christmas" | "pico-dark":
|
||||
charPath += "picopixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
|
|
|
@ -94,7 +94,6 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
{
|
||||
trace('Concatenating multi-sparrow atlas: ${asset}');
|
||||
subTexture.parent.destroyOnNoUse = false;
|
||||
FunkinMemory.cacheTexture(Paths.image(asset));
|
||||
}
|
||||
|
||||
texture.addAtlas(subTexture);
|
||||
|
|
|
@ -151,12 +151,10 @@ class HealthIcon extends FunkinSprite
|
|||
*/
|
||||
public function toggleOldIcon():Void
|
||||
{
|
||||
final playState:Null<PlayState> = PlayState.instance;
|
||||
if (playState == null || playState.currentStage == null) return;
|
||||
if (characterId == 'bf-old')
|
||||
{
|
||||
isPixel = playState.currentStage.getBoyfriend()?.isPixel ?? false;
|
||||
playState.currentStage.getBoyfriend()?.initHealthIcon(false);
|
||||
isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel;
|
||||
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -66,7 +66,7 @@ class VideoCutscene
|
|||
if (!openfl.Assets.exists(filePath))
|
||||
{
|
||||
// Display a popup.
|
||||
// funkin.util.WindowUtil.showError('Error playing video', 'Video file does not exist: ${filePath}');
|
||||
// lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
|
||||
// return;
|
||||
|
||||
// TODO: After moving videos to their own library,
|
||||
|
|
|
@ -10,9 +10,6 @@ class NoteSprite extends FunkinSprite
|
|||
{
|
||||
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
|
||||
|
||||
/**
|
||||
* The hold note sprite for this note.
|
||||
*/
|
||||
public var holdNoteSprite:SustainTrail;
|
||||
|
||||
var hsvShader:HSVShader;
|
||||
|
@ -98,23 +95,8 @@ class NoteSprite extends FunkinSprite
|
|||
return this.direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The note data associated with this note sprite.
|
||||
* This is used to store the strum time, length, and other properties.
|
||||
*/
|
||||
public var noteData:SongNoteData;
|
||||
|
||||
/**
|
||||
* If this note kind is scoreable (i.e., counted towards score and accuracy)
|
||||
* Only accessible in scripts
|
||||
* Defaults to true
|
||||
*/
|
||||
public var scoreable:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether this note is a hold note.
|
||||
* This is true if the length is greater than 0.
|
||||
*/
|
||||
public var isHoldNote(get, never):Bool;
|
||||
|
||||
function get_isHoldNote():Bool
|
||||
|
@ -256,8 +238,6 @@ class NoteSprite extends FunkinSprite
|
|||
this.hasBeenHit = false;
|
||||
this.mayHit = false;
|
||||
this.hasMissed = false;
|
||||
this.handledMiss = false;
|
||||
this.holdNoteSprite = null;
|
||||
|
||||
this.hsvShader.hue = 1.0;
|
||||
this.hsvShader.saturation = 1.0;
|
||||
|
|
|
@ -17,7 +17,6 @@ import funkin.play.notes.NoteVibrationsHandler;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.GRhythmUtil;
|
||||
import funkin.play.notes.notekind.NoteKind;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import flixel.math.FlxPoint;
|
||||
#if mobile
|
||||
|
@ -97,9 +96,9 @@ class Strumline extends FlxSpriteGroup
|
|||
/**
|
||||
* Reset the scroll speed to the current chart's scroll speed.
|
||||
*/
|
||||
public function resetScrollSpeed(?newScrollSpeed:Float):Void
|
||||
public function resetScrollSpeed():Void
|
||||
{
|
||||
scrollSpeed = newScrollSpeed ?? PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||
}
|
||||
|
||||
var _conductorInUse:Null<Conductor>;
|
||||
|
@ -184,7 +183,7 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
static final BACKGROUND_PAD:Int = 16;
|
||||
|
||||
public function new(noteStyle:NoteStyle, isPlayer:Bool, ?scrollSpeed:Float)
|
||||
public function new(noteStyle:NoteStyle, isPlayer:Bool)
|
||||
{
|
||||
super();
|
||||
|
||||
|
@ -236,13 +235,14 @@ class Strumline extends FlxSpriteGroup
|
|||
if (inArrowContorlSchemeMode && isPlayer) this.background.x -= 100;
|
||||
#end
|
||||
this.add(this.background);
|
||||
strumlineScale = new FlxCallbackPoint(strumlineScaleCallback);
|
||||
|
||||
strumlineScale = new FlxCallbackPoint(strumlineScaleCallback);
|
||||
|
||||
this.refresh();
|
||||
|
||||
this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
|
||||
resetScrollSpeed(scrollSpeed);
|
||||
resetScrollSpeed();
|
||||
|
||||
for (i in 0...KEY_COUNT)
|
||||
{
|
||||
|
@ -643,6 +643,8 @@ class Strumline extends FlxSpriteGroup
|
|||
if (holdNote.cover != null && isPlayer)
|
||||
{
|
||||
holdNote.cover.playEnd();
|
||||
|
||||
trace("Sustain Note Splash Vibration");
|
||||
}
|
||||
else if (holdNote.cover != null)
|
||||
{
|
||||
|
@ -946,6 +948,7 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
if (note == null) return;
|
||||
note.visible = false;
|
||||
notes.remove(note, false);
|
||||
note.kill();
|
||||
|
||||
if (note.holdNoteSprite != null)
|
||||
|
@ -1100,7 +1103,6 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
if (noteSprite != null)
|
||||
{
|
||||
var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind);
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||
noteSprite.setupNoteGraphic(noteKindStyle);
|
||||
|
||||
|
@ -1125,7 +1127,6 @@ class Strumline extends FlxSpriteGroup
|
|||
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
||||
noteSprite.x -= NUDGE;
|
||||
noteSprite.y = -9999;
|
||||
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
|
||||
}
|
||||
|
||||
return noteSprite;
|
||||
|
|
|
@ -283,7 +283,7 @@ class SustainTrail extends FlxSprite
|
|||
return;
|
||||
}
|
||||
|
||||
var clipHeight:Float = sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0).clamp(0, graphicHeight);
|
||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
|
||||
if (clipHeight <= 0.1)
|
||||
{
|
||||
visible = false;
|
||||
|
|
|
@ -28,13 +28,6 @@ class NoteKind implements INoteScriptedClass
|
|||
*/
|
||||
public var params:Array<NoteKindParam>;
|
||||
|
||||
/**
|
||||
* If this note kind is scoreable (ie, counted towards score and accuracy)
|
||||
* Only accessible in scripts
|
||||
* Defaults to true
|
||||
*/
|
||||
public var scoreable:Bool = true;
|
||||
|
||||
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
|
||||
{
|
||||
this.noteKind = noteKind;
|
||||
|
|
|
@ -11,21 +11,7 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam;
|
|||
|
||||
class NoteKindManager
|
||||
{
|
||||
/**
|
||||
* A map of all note kinds, keyed by their name.
|
||||
* This is used to retrieve note kinds by their name.
|
||||
*/
|
||||
public static var noteKinds:Map<String, NoteKind> = [];
|
||||
|
||||
/**
|
||||
* Retrieve a note kind by its name.
|
||||
* @param noteKind The name of the note kind.
|
||||
* @return The note kind, or null if it doesn't exist.
|
||||
*/
|
||||
public static function getNoteKind(noteKind:String):Null<NoteKind>
|
||||
{
|
||||
return noteKinds.get(noteKind);
|
||||
}
|
||||
static var noteKinds:Map<String, NoteKind> = [];
|
||||
|
||||
public static function loadScripts():Void
|
||||
{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.play.scoring;
|
||||
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.save.Save.SaveScoreTallyData;
|
||||
|
||||
/**
|
||||
* Which system to use when scoring and judging notes.
|
||||
|
@ -375,7 +374,8 @@ class Scoring
|
|||
if (scoreData.tallies.totalNotes == 0) return null;
|
||||
|
||||
// Perfect (Platinum) is a Sick Full Clear
|
||||
if (scoreData.tallies.sick == scoreData.tallies.totalNotes)
|
||||
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
|
||||
if (isPerfectGold)
|
||||
{
|
||||
return ScoringRank.PERFECT_GOLD;
|
||||
}
|
||||
|
@ -384,21 +384,21 @@ class Scoring
|
|||
|
||||
// Final Grade = (Sick + Good - Miss) / (Total Notes)
|
||||
|
||||
var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies);
|
||||
var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes;
|
||||
|
||||
if (completionAmount == Constants.RANK_PERFECT_THRESHOLD)
|
||||
if (grade == Constants.RANK_PERFECT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.PERFECT;
|
||||
}
|
||||
else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD)
|
||||
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.EXCELLENT;
|
||||
}
|
||||
else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD)
|
||||
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.GREAT;
|
||||
}
|
||||
else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD)
|
||||
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.GOOD;
|
||||
}
|
||||
|
@ -407,21 +407,6 @@ class Scoring
|
|||
return ScoringRank.SHIT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the "completion" of a song, based on how many GOOD and SICK notes were hit, minus how many were missed
|
||||
* Top secret funkin crew patented algorithm
|
||||
* TODO: Could possibly move more of the "tallying" related handling here.
|
||||
* In FreeplayState we make sure it's clamped between 0 and 1, and we probably always want to assume that?
|
||||
*
|
||||
* @param tallies
|
||||
* @return Float Completion, as a float value between 0 and 1. If `tallies` is `null`, we return 0;
|
||||
*/
|
||||
public static function tallyCompletion(?tallies:SaveScoreTallyData):Float
|
||||
{
|
||||
if (tallies == null) return 0.0;
|
||||
return (tallies.sick + tallies.good - tallies.missed) / tallies.totalNotes;
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract ScoringRank(String)
|
||||
|
|
|
@ -187,7 +187,6 @@ class Save
|
|||
theme: ChartEditorTheme.Light,
|
||||
playtestStartTime: false,
|
||||
downscroll: false,
|
||||
showNoteKinds: true,
|
||||
metronomeVolume: 1.0,
|
||||
hitsoundVolumePlayer: 1.0,
|
||||
hitsoundVolumeOpponent: 1.0,
|
||||
|
@ -359,23 +358,6 @@ class Save
|
|||
return data.optionsChartEditor.downscroll;
|
||||
}
|
||||
|
||||
public var chartEditorShowNoteKinds(get, set):Bool;
|
||||
|
||||
function get_chartEditorShowNoteKinds():Bool
|
||||
{
|
||||
if (data.optionsChartEditor.showNoteKinds == null) data.optionsChartEditor.showNoteKinds = true;
|
||||
|
||||
return data.optionsChartEditor.showNoteKinds;
|
||||
}
|
||||
|
||||
function set_chartEditorShowNoteKinds(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsChartEditor.showNoteKinds = value;
|
||||
flush();
|
||||
return data.optionsChartEditor.showNoteKinds;
|
||||
}
|
||||
|
||||
public var chartEditorPlaytestStartTime(get, set):Bool;
|
||||
|
||||
function get_chartEditorPlaytestStartTime():Bool
|
||||
|
@ -900,12 +882,14 @@ class Save
|
|||
return;
|
||||
}
|
||||
|
||||
var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes;
|
||||
var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes;
|
||||
|
||||
// Set the high score and the high rank separately.
|
||||
var newScore:SaveScoreData =
|
||||
{
|
||||
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
|
||||
tallies: (previousRank > newRank
|
||||
|| Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies
|
||||
tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
|
||||
};
|
||||
|
||||
song.set(difficultyId, newScore);
|
||||
|
@ -1220,7 +1204,7 @@ class Save
|
|||
{
|
||||
var msg = 'There was an error loading your save data in slot ${slot}.';
|
||||
msg += '\nPlease report this issue to the developers.';
|
||||
funkin.util.WindowUtil.showError("Save Data Failure", msg);
|
||||
lime.app.Application.current.window.alert(msg, "Save Data Failure");
|
||||
|
||||
// Don't touch that slot anymore.
|
||||
// Instead, load the next available slot.
|
||||
|
@ -1372,45 +1356,6 @@ class Save
|
|||
{
|
||||
FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...');
|
||||
}
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
public static function saveToNewgrounds():Void
|
||||
{
|
||||
if (_instance == null) return;
|
||||
trace('[SAVE] Saving Save Data to Newgrounds...');
|
||||
funkin.api.newgrounds.NGSaveSlot.instance.save(_instance.data);
|
||||
}
|
||||
|
||||
public static function loadFromNewgrounds(onFinish:Void->Void):Void
|
||||
{
|
||||
trace('[SAVE] Loading Save Data from Newgrounds...');
|
||||
funkin.api.newgrounds.NGSaveSlot.instance.load(function(data:Dynamic) {
|
||||
FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH);
|
||||
|
||||
if (FlxG.save.status != EMPTY)
|
||||
{
|
||||
// best i can do in case the NG file is corrupted or something along those lines
|
||||
var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data);
|
||||
trace('[SAVE] Backed up current save data in case of emergency to $backupSlot!');
|
||||
}
|
||||
|
||||
FlxG.save.erase();
|
||||
FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); // forces regeneration of the file as erase deletes it
|
||||
|
||||
var gameSave = SaveDataMigrator.migrate(data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
_instance = gameSave;
|
||||
onFinish();
|
||||
}, function(error:io.newgrounds.Call.CallError) {
|
||||
var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error);
|
||||
|
||||
var msg = 'There was an error loading your save data from Newgrounds.';
|
||||
msg += '\n${errorMsg}';
|
||||
msg += '\nAre you sure you are connected to the internet?';
|
||||
funkin.util.WindowUtil.showError("Newgrounds Save Slot Failure", msg);
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1860,12 +1805,6 @@ typedef SaveDataChartEditorOptions =
|
|||
*/
|
||||
var ?downscroll:Bool;
|
||||
|
||||
/**
|
||||
* Show Note Kind Indicator in the Chart Editor.
|
||||
* @default `true`
|
||||
*/
|
||||
var ?showNoteKinds:Bool;
|
||||
|
||||
/**
|
||||
* Metronome volume in the Chart Editor.
|
||||
* @default `1.0`
|
||||
|
|
|
@ -41,7 +41,7 @@ class SaveDataMigrator
|
|||
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
|
||||
var slot:Int = Save.archiveBadSaveData(inputData);
|
||||
var fullMessage:String = 'An error occurred migrating your save data.\n${message}\nInvalid data has been moved to save slot ${slot}.';
|
||||
funkin.util.WindowUtil.showError("Save Data Failure", fullMessage);
|
||||
lime.app.Application.current.window.alert(fullMessage, "Save Data Failure");
|
||||
return new Save(Save.getDefault());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,25 +277,25 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
|
||||
public function accept():Void
|
||||
{
|
||||
var menuItem:T = members[selectedIndex];
|
||||
var selected = members[selectedIndex];
|
||||
|
||||
if (!menuItem.available) return;
|
||||
if (!selected.available) return;
|
||||
|
||||
onAcceptPress.dispatch(menuItem);
|
||||
onAcceptPress.dispatch(selected);
|
||||
|
||||
if (menuItem.fireInstantly) menuItem.callback();
|
||||
if (selected.fireInstantly) selected.callback();
|
||||
else
|
||||
{
|
||||
busy = true;
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
FlxFlicker.flicker(menuItem, 1, 0.06, true, false, function(_) {
|
||||
FlxFlicker.flicker(selected, 1, 0.06, true, false, function(_) {
|
||||
busy = false;
|
||||
menuItem.callback();
|
||||
selected.callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function cancelAccept():Void
|
||||
public function cancelAccept()
|
||||
{
|
||||
FlxFlicker.stopFlickering(members[selectedIndex]);
|
||||
busy = false;
|
||||
|
@ -327,22 +327,22 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
|
||||
selectedIndex = index;
|
||||
|
||||
var selectedMenuItem:T = members[selectedIndex];
|
||||
selectedMenuItem.select();
|
||||
onChange.dispatch(selectedMenuItem);
|
||||
var selected = members[selectedIndex];
|
||||
selected.select();
|
||||
onChange.dispatch(selected);
|
||||
}
|
||||
|
||||
public function has(name:String):Bool
|
||||
public function has(name:String)
|
||||
{
|
||||
return byName.exists(name);
|
||||
}
|
||||
|
||||
public function getItem(name:String):Null<T>
|
||||
public function getItem(name:String)
|
||||
{
|
||||
return byName[name];
|
||||
}
|
||||
|
||||
override function destroy():Void
|
||||
override function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
byName.clear();
|
||||
|
@ -366,7 +366,7 @@ class MenuListItem extends FlxSprite
|
|||
/**
|
||||
* Set to true for things like opening URLs otherwise, it may it get blocked.
|
||||
*/
|
||||
public var fireInstantly:Bool = false;
|
||||
public var fireInstantly = false;
|
||||
|
||||
public var selected(get, never):Bool;
|
||||
|
||||
|
@ -386,7 +386,7 @@ class MenuListItem extends FlxSprite
|
|||
idle();
|
||||
}
|
||||
|
||||
function setData(name:String, ?callback:Void->Void, available:Bool):Void
|
||||
function setData(name:String, ?callback:Void->Void, available:Bool)
|
||||
{
|
||||
this.name = name;
|
||||
|
||||
|
@ -400,7 +400,7 @@ class MenuListItem extends FlxSprite
|
|||
* @param name the label.
|
||||
* @param callback Unchanged if null.
|
||||
*/
|
||||
public function setItem(name:String, ?callback:Void->Void):Void
|
||||
public function setItem(name:String, ?callback:Void->Void)
|
||||
{
|
||||
setData(name, callback, available);
|
||||
|
||||
|
@ -409,12 +409,12 @@ class MenuListItem extends FlxSprite
|
|||
idle();
|
||||
}
|
||||
|
||||
public function idle():Void
|
||||
public function idle()
|
||||
{
|
||||
alpha = 0.6;
|
||||
}
|
||||
|
||||
public function select():Void
|
||||
public function select()
|
||||
{
|
||||
alpha = 1.0;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import funkin.modding.PolymodHandler;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.graphics.FunkinCamera;
|
||||
|
@ -157,11 +156,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
function handleFunctionControls():Void
|
||||
{
|
||||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4)
|
||||
{
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
WindowUtil.setWindowTitle('Friday Night Funkin\'');
|
||||
}
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
|
|
|
@ -10,7 +10,6 @@ import funkin.modding.IScriptedClass.IEventHandler;
|
|||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.PolymodHandler;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
|
@ -141,11 +140,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
super.update(elapsed);
|
||||
|
||||
// Emergency exit button.
|
||||
if (FlxG.keys.justPressed.F4)
|
||||
{
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
WindowUtil.setWindowTitle('Friday Night Funkin\'');
|
||||
}
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
// Display Conductor info in the watch window.
|
||||
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||
|
|
|
@ -21,25 +21,33 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||
{
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
final charIDParts:Array<String> = char.split("-");
|
||||
var iconName:String = "";
|
||||
for (i in 0...charIDParts.length)
|
||||
switch (char)
|
||||
{
|
||||
iconName += charIDParts[i];
|
||||
|
||||
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
|
||||
{
|
||||
charPath += '${iconName}pixel';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < charIDParts.length - 1) iconName += '-';
|
||||
continue;
|
||||
}
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas" | "tankman-bloody":
|
||||
charPath += "tankmanpixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
this.visible = false;
|
||||
|
@ -50,7 +58,7 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||
this.visible = true;
|
||||
}
|
||||
|
||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
package funkin.ui;
|
||||
|
||||
/**
|
||||
* Simple state machine for UI components
|
||||
* Replaces scattered boolean flags with clean state management
|
||||
*/
|
||||
enum UIState
|
||||
{
|
||||
Idle;
|
||||
Interacting;
|
||||
Entering;
|
||||
Exiting;
|
||||
Disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Not to be confust with FlxState or FlxSubState!
|
||||
* State as in the design pattern!
|
||||
* https://refactoring.guru/design-patterns/state
|
||||
*/
|
||||
@:nullSafety
|
||||
class UIStateMachine
|
||||
{
|
||||
public var currentState(default, null):UIState = Idle;
|
||||
public var previousState(default, null):UIState = Idle;
|
||||
|
||||
var validTransitions:Map<UIState, Array<UIState>>;
|
||||
var onStateChange:Array<(UIState, UIState) -> Void> = [];
|
||||
|
||||
public function new(?transitions:Map<UIState, Array<UIState>>)
|
||||
{
|
||||
// Default valid transitions if none provided
|
||||
validTransitions = transitions != null ? transitions : [
|
||||
Idle => [Interacting, Entering, Exiting, Disabled],
|
||||
Interacting => [Idle, Entering, Exiting, Disabled],
|
||||
Entering => [Idle, Exiting, Disabled],
|
||||
Exiting => [Idle],
|
||||
Disabled => [Idle]
|
||||
];
|
||||
}
|
||||
|
||||
public function canTransition(from:UIState, to:UIState):Bool
|
||||
{
|
||||
if (from != currentState) return false;
|
||||
|
||||
var allowedStates = validTransitions.get(from);
|
||||
return allowedStates != null && allowedStates.contains(to);
|
||||
}
|
||||
|
||||
public function transition(newState:UIState):Bool
|
||||
{
|
||||
// Allow same-state transitions (idempotent)
|
||||
if (currentState == newState)
|
||||
{
|
||||
trace('State transition: ${currentState} -> ${newState} (no change)');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!canTransition(currentState, newState))
|
||||
{
|
||||
trace('Invalid state transition: ${currentState} -> ${newState}');
|
||||
return false;
|
||||
}
|
||||
|
||||
previousState = currentState;
|
||||
currentState = newState;
|
||||
|
||||
trace('State transition: ${previousState} -> ${currentState}');
|
||||
|
||||
// Notify listeners
|
||||
for (callback in onStateChange)
|
||||
{
|
||||
callback(previousState, currentState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onStateChanged(callback:(UIState, UIState) -> Void):Void
|
||||
{
|
||||
onStateChange.push(callback);
|
||||
}
|
||||
|
||||
public function reset():Void
|
||||
{
|
||||
previousState = currentState;
|
||||
currentState = Idle;
|
||||
}
|
||||
|
||||
public function is(state:UIState):Bool
|
||||
{
|
||||
return currentState == state;
|
||||
}
|
||||
|
||||
public function canInteract():Bool
|
||||
{
|
||||
// Entering is an enabled state since we want to be able to interact even during the screen fade wipe thing
|
||||
return currentState == Idle || currentState == Entering;
|
||||
}
|
||||
}
|
|
@ -140,7 +140,7 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
|||
alphaDiff /= 100; // flash exports alpha as a whole number
|
||||
|
||||
alpha += alphaDiff;
|
||||
alpha = alpha.clamp(0, 1);
|
||||
alpha = FlxMath.bound(alpha, 0, 1);
|
||||
x += xDiff;
|
||||
y += yDiff;
|
||||
|
||||
|
|
|
@ -271,12 +271,6 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
nametag.midpointX += cutoutSize;
|
||||
add(nametag);
|
||||
|
||||
@:privateAccess
|
||||
{
|
||||
nametag.midpointY += 200;
|
||||
FlxTween.tween(nametag, {midpointY: nametag.midpointY - 200}, 1, {ease: FlxEase.expoOut});
|
||||
}
|
||||
|
||||
nametag.scrollFactor.set();
|
||||
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"]));
|
||||
|
@ -745,7 +739,6 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
FlxTween.tween(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
|
||||
|
||||
FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(nametag, {y: nametag.y + 80}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn});
|
||||
|
|
|
@ -15,18 +15,17 @@ class CreditsDataHandler
|
|||
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||
#end
|
||||
|
||||
#if macro
|
||||
public static function debugPrint(data:Null<CreditsData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Sys.println('[INFO] CreditsData(NULL)');
|
||||
trace('CreditsData(NULL)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.entries == null || data.entries.length == 0)
|
||||
{
|
||||
Sys.println('[INFO] CreditsData(EMPTY)');
|
||||
trace('CreditsData(EMPTY)');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,9 +36,8 @@ class CreditsDataHandler
|
|||
lineCount += entry?.body?.length ?? 0;
|
||||
}
|
||||
|
||||
Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)');
|
||||
trace('CreditsData($entryCount entries containing $lineCount lines)');
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* If for some reason the full credits won't load,
|
||||
|
|
|
@ -10,7 +10,7 @@ class CreditsDataMacro
|
|||
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||
{
|
||||
#if !display
|
||||
Sys.println('[INFO] Hardcoding credits data...');
|
||||
trace('Hardcoding credits data...');
|
||||
var json = CreditsDataMacro.fetchJSON();
|
||||
|
||||
if (json == null)
|
||||
|
|
|
@ -293,7 +293,6 @@ class CreditsState extends MusicBeatState
|
|||
|
||||
function exit():Void
|
||||
{
|
||||
FlxG.keys.enabled = false;
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import flixel.math.FlxPoint;
|
|||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.ui.TextMenuList;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
|
@ -38,7 +37,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
// Create the green background.
|
||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||
menuBG.color = 0xFF4CAF50;
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x));
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
menuBG.scrollFactor.set(0, 0);
|
||||
|
|
|
@ -100,7 +100,6 @@ class DebugBoundingState extends FlxState
|
|||
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
|
||||
|
||||
offsetEditorDialog.cameras = [hudCam];
|
||||
offsetEditorDialog.closable = false;
|
||||
|
||||
add(offsetEditorDialog);
|
||||
offsetEditorDialog.showDialog(false);
|
||||
|
|
|
@ -94,9 +94,6 @@ import haxe.ui.components.Button;
|
|||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
|
@ -634,11 +631,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return isViewDownscroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show an indicator if a note is of a non-default kind.
|
||||
*/
|
||||
var showNoteKindIndicators:Bool = false;
|
||||
|
||||
/**
|
||||
* The current theme used by the editor.
|
||||
* Dictates the appearance of many UI elements.
|
||||
|
@ -1863,11 +1855,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var menubarItemDownscroll:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `View -> Note Kind Indicator` menu item.
|
||||
*/
|
||||
var menubarItemViewIndicators:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `View -> Increase Difficulty` menu item.
|
||||
*/
|
||||
|
@ -2371,7 +2358,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
noteSnapQuantIndex = save.chartEditorNoteQuant;
|
||||
currentLiveInputStyle = save.chartEditorLiveInputStyle;
|
||||
isViewDownscroll = save.chartEditorDownscroll;
|
||||
showNoteKindIndicators = save.chartEditorShowNoteKinds;
|
||||
playtestStartTime = save.chartEditorPlaytestStartTime;
|
||||
currentTheme = save.chartEditorTheme;
|
||||
metronomeVolume = save.chartEditorMetronomeVolume;
|
||||
|
@ -2382,7 +2368,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
menubarItemVolumeInstrumental.value = Std.int(save.chartEditorInstVolume * 100);
|
||||
menubarItemVolumeVocalsPlayer.value = Std.int(save.chartEditorPlayerVoiceVolume * 100);
|
||||
menubarItemVolumeVocalsOpponent.value = Std.int(save.chartEditorOpponentVoiceVolume * 100);
|
||||
menubarItemPlaybackSpeed.value = Std.int(save.chartEditorPlaybackSpeed * 100.0);
|
||||
menubarItemPlaybackSpeed.value = Std.int(save.chartEditorPlaybackSpeed * 100);
|
||||
}
|
||||
|
||||
public function writePreferences(hasBackup:Bool):Void
|
||||
|
@ -2401,7 +2387,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
save.chartEditorNoteQuant = noteSnapQuantIndex;
|
||||
save.chartEditorLiveInputStyle = currentLiveInputStyle;
|
||||
save.chartEditorDownscroll = isViewDownscroll;
|
||||
save.chartEditorShowNoteKinds = showNoteKindIndicators;
|
||||
save.chartEditorPlaytestStartTime = playtestStartTime;
|
||||
save.chartEditorTheme = currentTheme;
|
||||
save.chartEditorMetronomeVolume = metronomeVolume;
|
||||
|
@ -2534,7 +2519,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
add(gridTiledSprite);
|
||||
gridTiledSprite.zIndex = 10;
|
||||
|
||||
gridGhostNote = new ChartEditorNoteSprite(this, true);
|
||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||
gridGhostNote.alpha = 0.6;
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
|
||||
gridGhostNote.visible = false;
|
||||
|
@ -3104,9 +3089,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
|
||||
menubarItemDownscroll.selected = isViewDownscroll;
|
||||
|
||||
menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected;
|
||||
menubarItemViewIndicators.selected = showNoteKindIndicators;
|
||||
|
||||
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
|
||||
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
|
||||
|
||||
|
@ -3189,8 +3171,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
menubarItemPlaybackSpeed.onChange = event -> {
|
||||
var pitch:Float = (event.value.toFloat() * 2.0) / 100.0;
|
||||
pitch = Math.round(pitch / 0.05) * 0.05; // Round to nearest 5%
|
||||
pitch = pitch.clamp(0.05, 2.0); // Clamp to 5% to 200%
|
||||
pitch = Math.floor(pitch / 0.05) * 0.05; // Round to nearest 5%
|
||||
pitch = Math.max(0.05, Math.min(2.0, pitch)); // Clamp to 5% to 200%
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
audioVocalTrackGroup.pitch = pitch;
|
||||
|
@ -3939,9 +3921,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
selectionSquare.width = selectionSquare.height = GRID_SIZE;
|
||||
selectionSquare.color = FlxColor.RED;
|
||||
}
|
||||
|
||||
// Additional cleanup on notes.
|
||||
if (noteTooltipsDirty) noteSprite.updateTooltipText();
|
||||
}
|
||||
|
||||
for (eventSprite in renderedEvents.members)
|
||||
|
@ -4747,15 +4726,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
dragLengthCurrent = dragLengthSteps;
|
||||
}
|
||||
|
||||
var sameHold:Bool = (gridGhostHoldNote.noteData == currentPlaceNoteData);
|
||||
|
||||
gridGhostHoldNote.visible = true;
|
||||
gridGhostHoldNote.noteData = currentPlaceNoteData;
|
||||
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, sameHold);
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
|
||||
gridGhostHoldNote.noteStyle = NoteKindManager.getNoteStyleId(currentPlaceNoteData.kind, currentSongNoteStyle) ?? currentSongNoteStyle;
|
||||
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
||||
gridGhostHoldNote.updateHoldNoteGraphic();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -5187,7 +5163,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
var variationMetadata:Null<SongMetadata> = songMetadata.get(selectedVariation);
|
||||
if (variationMetadata != null)
|
||||
variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
var difficultyToolbox:ChartEditorDifficultyToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
if (difficultyToolbox == null) return;
|
||||
|
@ -5623,20 +5599,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
@:nullSafety(Off)
|
||||
function quitChartEditor():Void
|
||||
{
|
||||
if (saveDataDirty)
|
||||
{
|
||||
Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true,
|
||||
function(button:DialogButton) {
|
||||
if (button == DialogButton.YES)
|
||||
{
|
||||
autoSave();
|
||||
quitChartEditor();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
autoSave();
|
||||
stopWelcomeMusic();
|
||||
// TODO: PR Flixel to make onComplete nullable.
|
||||
if (audioInstTrack != null) audioInstTrack.onComplete = null;
|
||||
|
@ -5896,9 +5859,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
|
||||
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = playbackRate.clamp(0.05, 2.0); // Clamp to 5% to 200%
|
||||
var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0;
|
||||
playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200%
|
||||
|
||||
var targetSong:Song;
|
||||
try
|
||||
|
@ -6333,8 +6296,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
currentScrollEase = Math.max(0, targetScrollPosition);
|
||||
currentScrollEase = Math.min(currentScrollEase, songLengthInPixels);
|
||||
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION,
|
||||
1 / 1000), currentScrollEase, 1 / 1000);
|
||||
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000);
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
|
@ -6369,30 +6331,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION);
|
||||
|
||||
// Reapply the volume and playback rate.
|
||||
var instTargetVolume:Float = (menubarItemVolumeInstrumental.value / 100.0) ?? 1.0;
|
||||
var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0;
|
||||
var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0;
|
||||
|
||||
var playbackRate = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
|
||||
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = playbackRate.clamp(0.05, 2.0); // Clamp to 5% to 200%
|
||||
// Reapply the volume.
|
||||
var instTargetVolume:Float = menubarItemVolumeInstrumental.value / 100.0 ?? 1.0;
|
||||
var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value / 100.0 ?? 1.0;
|
||||
var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value / 100.0 ?? 1.0;
|
||||
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
audioInstTrack.volume = instTargetVolume;
|
||||
#if FLX_PITCH
|
||||
audioInstTrack.pitch = playbackRate;
|
||||
#end
|
||||
audioInstTrack.onComplete = null;
|
||||
}
|
||||
if (audioVocalTrackGroup != null)
|
||||
{
|
||||
audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume;
|
||||
audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume;
|
||||
#if FLX_PITCH
|
||||
audioVocalTrackGroup.pitch = playbackRate;
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6547,11 +6499,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
public function postLoadInstrumental():Void
|
||||
{
|
||||
// Reapply the volume and playback rate.
|
||||
var instTargetVolume:Float = ((menubarItemVolumeInstrumental.value / 100) ?? 1.0);
|
||||
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
|
||||
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = playbackRate.clamp(0.05, 2.0); // Clamp to 5% to 200%
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
// Prevent the time from skipping back to 0 when the song ends.
|
||||
|
@ -6564,10 +6511,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
audioVocalTrackGroup.pause();
|
||||
};
|
||||
audioInstTrack.volume = instTargetVolume;
|
||||
#if FLX_PITCH
|
||||
audioInstTrack.pitch = playbackRate;
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -6584,25 +6527,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
healthIconsDirty = true;
|
||||
}
|
||||
|
||||
public function postLoadVocals():Void
|
||||
{
|
||||
// Reapply the volume and playback rate.
|
||||
var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0;
|
||||
var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0;
|
||||
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
|
||||
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = playbackRate.clamp(0.05, 2.0); // Clamp to 5% to 200%
|
||||
|
||||
if (audioVocalTrackGroup != null)
|
||||
{
|
||||
audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume;
|
||||
audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume;
|
||||
#if FLX_PITCH
|
||||
audioVocalTrackGroup.pitch = playbackRate;
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
function hardRefreshOffsetsToolbox():Void
|
||||
{
|
||||
var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
|
@ -12,9 +10,6 @@ import funkin.data.song.SongData.SongNoteData;
|
|||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
import funkin.util.HaxeUIUtil;
|
||||
import haxe.ui.tooltips.ToolTipManager;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a note in a chart.
|
||||
|
@ -68,21 +63,11 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
return overrideData;
|
||||
}
|
||||
|
||||
public var isGhost:Bool = false;
|
||||
public var tooltip:ToolTipRegionOptions;
|
||||
|
||||
/**
|
||||
* An indicator if the note is a note kind different than Default ("").
|
||||
*/
|
||||
public var kindIndicator:FlxText = new FlxText(5, 5, 100, '*', 16);
|
||||
|
||||
public function new(parent:ChartEditorState, isGhost:Bool = false)
|
||||
public function new(parent:ChartEditorState)
|
||||
{
|
||||
super();
|
||||
|
||||
this.parentState = parent;
|
||||
this.isGhost = isGhost;
|
||||
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
|
||||
|
||||
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
|
||||
|
||||
|
@ -104,8 +89,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
{
|
||||
addNoteStyleAnimations(fetchNoteStyle(entry));
|
||||
}
|
||||
|
||||
kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
}
|
||||
|
||||
static var noteFrameCollection:Null<FlxFramesCollection> = null;
|
||||
|
@ -173,7 +156,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
if (this.noteData == null)
|
||||
{
|
||||
this.kill();
|
||||
updateTooltipPosition();
|
||||
return this.noteData;
|
||||
}
|
||||
|
||||
|
@ -185,7 +167,7 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
|
||||
// Update the position to match the note data.
|
||||
updateNotePosition();
|
||||
updateTooltipText();
|
||||
|
||||
return this.noteData;
|
||||
}
|
||||
|
||||
|
@ -212,50 +194,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
this.updateTooltipPosition();
|
||||
}
|
||||
|
||||
public function updateTooltipText():Void
|
||||
{
|
||||
if (this.noteData == null) return;
|
||||
if (this.isGhost) return;
|
||||
this.tooltip.tipData = {text: this.noteData.buildTooltip()};
|
||||
}
|
||||
|
||||
public function updateTooltipPosition():Void
|
||||
{
|
||||
// No tooltip for ghost sprites.
|
||||
if (this.isGhost) return;
|
||||
|
||||
if (this.noteData == null || (this.tooltip.tipData?.text ?? "").length == 0)
|
||||
{
|
||||
// Disable the tooltip.
|
||||
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the position.
|
||||
this.tooltip.left = this.x;
|
||||
this.tooltip.top = this.y;
|
||||
this.tooltip.width = this.width;
|
||||
this.tooltip.height = this.height;
|
||||
|
||||
// Enable the tooltip.
|
||||
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
override public function draw()
|
||||
{
|
||||
super.draw();
|
||||
|
||||
if (!parentState.showNoteKindIndicators) return;
|
||||
if ((this.noteData?.kind ?? "").length == 0) return; // Do not render the note kind indicator if the note kind is default.
|
||||
|
||||
kindIndicator.x = this.x;
|
||||
kindIndicator.y = this.y;
|
||||
kindIndicator.draw();
|
||||
}
|
||||
|
||||
function get_noteStyle():Null<String>
|
||||
|
|
|
@ -137,8 +137,6 @@ class ChartEditorAudioHandler
|
|||
result = playVocals(state, DAD, opponentId, instId);
|
||||
// if (!result) return false;
|
||||
|
||||
state.postLoadVocals();
|
||||
|
||||
state.hardRefreshOffsetsToolbox();
|
||||
|
||||
state.hardRefreshFreeplayToolbox();
|
||||
|
|
|
@ -207,7 +207,7 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
|
|||
{
|
||||
// Move the playhead if it would go out of view.
|
||||
var prevPlayheadRelativePos = playheadRelativePos;
|
||||
playheadRelativePos = playheadRelativePos.clamp(0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
playheadRelativePos = FlxMath.bound(playheadRelativePos, 0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
trace('newPos: ${playheadRelativePos}');
|
||||
var diff = playheadRelativePos - prevPlayheadRelativePos;
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
|||
{
|
||||
// Move the playhead if it would go out of view.
|
||||
var prevPlayheadRelativePos = playheadRelativePos;
|
||||
playheadRelativePos = playheadRelativePos.clamp(0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
playheadRelativePos = FlxMath.bound(playheadRelativePos, 0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
var diff = playheadRelativePos - prevPlayheadRelativePos;
|
||||
|
||||
if (diff != 0)
|
||||
|
|
|
@ -11,6 +11,8 @@ import funkin.data.stage.StageData;
|
|||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorDialogType;
|
||||
|
||||
using funkin.util.tools.FloatTools;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/welcome.xml"))
|
||||
class WelcomeDialog extends Dialog
|
||||
{
|
||||
|
|
|
@ -192,7 +192,7 @@ class AssetDataHandler
|
|||
|
||||
for (daFrame in obj.frames.frames)
|
||||
{
|
||||
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}" rotated="${daFrame.angle == -90}"/>\n';
|
||||
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}"/>\n';
|
||||
}
|
||||
|
||||
xml += "</TextureAtlas>";
|
||||
|
|
|
@ -170,8 +170,6 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
*/
|
||||
public function playIntro():Void
|
||||
{
|
||||
this.visible = true;
|
||||
|
||||
if (albumTitle != null) albumTitle.visible = false;
|
||||
newAlbumArt.visible = true;
|
||||
newAlbumArt.playAnimation('intro', true);
|
||||
|
@ -188,7 +186,6 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
|
||||
public function skipIntro():Void
|
||||
{
|
||||
this.visible = true;
|
||||
// Weird workaround
|
||||
newAlbumArt.playAnimation('switch', true);
|
||||
if (albumTitle != null) albumTitle.animation.play('switch');
|
||||
|
@ -213,7 +210,7 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
}
|
||||
|
||||
albumTitle = FunkinSprite.createSparrow((FlxG.width - 355) - FullScreenScaleMode.gameNotchSize.x, 500, assetKey);
|
||||
albumTitle.visible = this.visible && (albumTitle.frames != null && newAlbumArt.visible) && difficultyStars.visible;
|
||||
albumTitle.visible = albumTitle.frames != null && newAlbumArt.visible;
|
||||
albumTitle.animation.addByPrefix('idle', 'idle0', 24, true);
|
||||
albumTitle.animation.addByPrefix('switch', 'switch0', 24, false);
|
||||
add(albumTitle);
|
||||
|
|
|
@ -4,80 +4,70 @@ import flixel.FlxObject;
|
|||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxDestroyUtil;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
// its kinda like marqeee html lol!
|
||||
@:nullSafety
|
||||
class BGScrollingText extends FlxSpriteGroup
|
||||
{
|
||||
var grpTexts:FlxTypedSpriteGroup<FlxSprite>;
|
||||
var sourceText:FlxText;
|
||||
var grpTexts:FlxTypedSpriteGroup<FlxText>;
|
||||
|
||||
public var widthShit:Float = FlxG.width;
|
||||
public var placementOffset:Float = 20;
|
||||
public var speed:Float = 1;
|
||||
public var size(default, set):Int = 48;
|
||||
|
||||
public var funnyColor(default, set):FlxColor = 0xFFFFFFFF;
|
||||
public var funnyColor(default, set):Int = 0xFFFFFFFF;
|
||||
|
||||
public function new(x:Float, y:Float, text:String, widthShit:Float = 100, ?bold:Bool = false, ?size:Int = 48)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
grpTexts = new FlxTypedSpriteGroup<FlxSprite>();
|
||||
grpTexts = new FlxTypedSpriteGroup<FlxText>();
|
||||
add(grpTexts);
|
||||
|
||||
this.widthShit = widthShit;
|
||||
if (size != null) this.size = size;
|
||||
|
||||
// Only keep one FlxText graphic at a time for batching
|
||||
sourceText = new FlxText(0, 0, 0, text, size ?? this.size);
|
||||
sourceText.font = "5by7";
|
||||
sourceText.bold = bold ?? false;
|
||||
var testText:FlxText = new FlxText(0, 0, 0, text, this.size);
|
||||
testText.font = "5by7";
|
||||
testText.bold = bold ?? false;
|
||||
testText.updateHitbox();
|
||||
grpTexts.add(testText);
|
||||
|
||||
@:privateAccess
|
||||
sourceText.regenGraphic();
|
||||
|
||||
var needed:Int = Math.ceil(widthShit / sourceText.frameWidth) + 1;
|
||||
var needed:Int = Math.ceil(widthShit / testText.frameWidth) + 1;
|
||||
|
||||
for (i in 0...needed)
|
||||
{
|
||||
var coolText = new FlxSprite((i * sourceText.frameWidth) + (i * 20), 0);
|
||||
var lmfao:Int = i + 1;
|
||||
|
||||
var coolText:FlxText = new FlxText((lmfao * testText.frameWidth) + (lmfao * 20), 0, 0, text, this.size);
|
||||
|
||||
coolText.font = "5by7";
|
||||
coolText.bold = bold ?? false;
|
||||
coolText.updateHitbox();
|
||||
grpTexts.add(coolText);
|
||||
}
|
||||
|
||||
if (size != null) this.size = size;
|
||||
|
||||
add(grpTexts);
|
||||
}
|
||||
|
||||
function reloadGraphics()
|
||||
{
|
||||
if (grpTexts != null)
|
||||
{
|
||||
@:privateAccess
|
||||
sourceText.regenGraphic();
|
||||
grpTexts.forEach(function(txt:FlxSprite) {
|
||||
txt.loadGraphic(sourceText.graphic);
|
||||
txt.updateHitbox();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function set_size(value:Int):Int
|
||||
{
|
||||
sourceText.size = value;
|
||||
reloadGraphics();
|
||||
if (grpTexts != null)
|
||||
{
|
||||
grpTexts.forEach(function(txt:FlxText) {
|
||||
txt.size = value;
|
||||
});
|
||||
}
|
||||
this.size = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
function set_funnyColor(value:FlxColor):FlxColor
|
||||
function set_funnyColor(col:Int):Int
|
||||
{
|
||||
sourceText.color = value;
|
||||
reloadGraphics();
|
||||
this.funnyColor = value;
|
||||
return value;
|
||||
grpTexts.forEach(function(txt) {
|
||||
txt.color = col;
|
||||
});
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
|
@ -116,10 +106,4 @@ class BGScrollingText extends FlxSpriteGroup
|
|||
return FlxSort.byValues(Order, Obj1.x, Obj2.x);
|
||||
});
|
||||
}
|
||||
|
||||
override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
sourceText = FlxDestroyUtil.destroy(sourceText);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,8 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case Intro:
|
||||
|
@ -183,10 +185,6 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
default:
|
||||
// I shit myself.
|
||||
}
|
||||
|
||||
// Call the superclass function AFTER updating the current state and playing the next animation.
|
||||
// This ensures that FlxAnimate starts rendering the new animation immediately.
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
function onFinishAnim(name:String):Void
|
||||
|
|
|
@ -31,7 +31,6 @@ import funkin.input.Controls;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.scoring.Scoring.ScoringRank;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.save.Save;
|
||||
|
@ -120,11 +119,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
*/
|
||||
public static final SONGS_POS_MULTI:Float = 0.75;
|
||||
|
||||
/**
|
||||
* For positioning the difficulty dots.
|
||||
*/
|
||||
public static final DEFAULT_DOTS_GROUP_POS:Array<Int> = [260, 170];
|
||||
|
||||
var songs:Array<Null<FreeplaySongData>> = [];
|
||||
|
||||
var curSelected:Int = 0;
|
||||
|
@ -175,6 +169,17 @@ class FreeplayState extends MusicBeatSubState
|
|||
return grpCapsules.members[curSelected];
|
||||
}
|
||||
|
||||
var coolColors:Array<Int> = [
|
||||
0xFF9271FD,
|
||||
0xFF9271FD,
|
||||
0xFF223344,
|
||||
0xFF941653,
|
||||
0xFFFC96D7,
|
||||
0xFFA0D1FF,
|
||||
0xFFFF78BF,
|
||||
0xFFF6B604
|
||||
];
|
||||
|
||||
var grpCapsules:FlxTypedGroup<SongMenuItem>;
|
||||
|
||||
var dj:Null<FreeplayDJ> = null;
|
||||
|
@ -310,7 +315,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
grpCapsules = new FlxTypedGroup<SongMenuItem>();
|
||||
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
|
||||
|
||||
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(DEFAULT_DOTS_GROUP_POS[0], DEFAULT_DOTS_GROUP_POS[1]);
|
||||
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(203, 170);
|
||||
letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75);
|
||||
rankBg = new FunkinSprite(0, 0);
|
||||
rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette'));
|
||||
|
@ -490,7 +495,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
albumRoll.albumId = null;
|
||||
albumRoll.visible = false;
|
||||
albumRoll.applyExitMovers(exitMovers, exitMoversCharSel);
|
||||
add(albumRoll);
|
||||
|
||||
|
@ -499,7 +503,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (fromCharSelect)
|
||||
{
|
||||
blackOverlayBullshitLOLXD.visible = false;
|
||||
blackOverlayBullshitLOLXD.x = backingImage.x;
|
||||
overhangStuff.y = -100;
|
||||
backingCard.skipIntroTween();
|
||||
}
|
||||
|
@ -699,9 +703,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
ease: FlxEase.expoOut,
|
||||
onUpdate: function(_) {
|
||||
angleMaskShader.extraColor = backingImage.color;
|
||||
},
|
||||
onComplete: function(_) {
|
||||
blackOverlayBullshitLOLXD.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -763,11 +764,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
albumRoll.skipIntro();
|
||||
albumRoll.showStars();
|
||||
}
|
||||
else if (fromCharSelect)
|
||||
{
|
||||
albumRoll.skipIntro();
|
||||
albumRoll.showStars();
|
||||
}
|
||||
|
||||
refreshDots(5, Constants.DEFAULT_DIFFICULTY_LIST_FULL.indexOf(currentDifficulty), Constants.DEFAULT_DIFFICULTY_LIST_FULL.indexOf(currentDifficulty));
|
||||
fadeDots(true);
|
||||
|
@ -1274,6 +1270,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
|
||||
new FlxTimer().start(2, _ -> {
|
||||
// dj.fistPump();
|
||||
prepForNewRank = false;
|
||||
});
|
||||
}
|
||||
|
@ -1298,22 +1295,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
function refreshDots(amount:Int, index:Int, prevIndex:Int):Void
|
||||
{
|
||||
var distance:Int = 30;
|
||||
var groupOffset:Float = 14.7;
|
||||
var shiftAmt:Float = (distance * amount) / 2;
|
||||
var daSong:Null<FreeplaySongData> = currentCapsule.freeplayData;
|
||||
final maxDotsPerRow:Int = 8;
|
||||
|
||||
if (difficultyDots.group.members.length > maxDotsPerRow)
|
||||
{
|
||||
difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (maxDotsPerRow - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (difficultyDots.group.members.length - 1);
|
||||
}
|
||||
|
||||
var curRow:Int = 0;
|
||||
var curDot:Int = 0;
|
||||
for (i in 0...difficultyDots.group.members.length)
|
||||
{
|
||||
// if (difficultyDots.group.members[i] == null) continue;
|
||||
|
@ -1345,16 +1329,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
difficultyDots.group.members[i].visible = true;
|
||||
difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * curDot)) - shiftAmt);
|
||||
difficultyDots.group.members[i].y = DEFAULT_DOTS_GROUP_POS[1] + distance * curRow;
|
||||
|
||||
curDot++;
|
||||
|
||||
if (curDot >= maxDotsPerRow)
|
||||
{
|
||||
curDot = 0;
|
||||
curRow++;
|
||||
}
|
||||
difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - shiftAmt);
|
||||
|
||||
if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false)
|
||||
{
|
||||
|
@ -1607,10 +1582,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong();
|
||||
|
||||
if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected);
|
||||
|
||||
if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1);
|
||||
|
||||
lerpScoreDisplays();
|
||||
calculateCompletion();
|
||||
|
||||
handleInputs(elapsed);
|
||||
|
||||
|
@ -1620,7 +1597,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
}
|
||||
|
||||
function lerpScoreDisplays():Void
|
||||
function calculateCompletion():Void
|
||||
{
|
||||
lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, FlxG.elapsed, 0.2), intendedScore, 1);
|
||||
lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100);
|
||||
|
@ -1640,7 +1617,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
// sets the text of the completion percentage. Perhaps eventually we may want to generalize this,
|
||||
// but for now we can just clamp the values between 0 and 100.
|
||||
// Fixes issue where it rounds to negative integer overflow on Windows? Which occurs when switching to an unranked song?
|
||||
txtCompletion.text = '${Math.floor(lerpCompletion * 100).clamp(0, 100)}';
|
||||
txtCompletion.text = '${FlxMath.clamp(Math.floor(lerpCompletion * 100), 0, 100)}';
|
||||
|
||||
// Right align the completion percentage
|
||||
switch (txtCompletion.text.length)
|
||||
|
@ -1885,7 +1862,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
var dpiScale = FlxG.stage.window.display.dpi / 160;
|
||||
|
||||
dpiScale = dpiScale.clamp(0.5, #if android 1 #else 2 #end);
|
||||
dpiScale = FlxMath.clamp(dpiScale, 0.5, #if android 1 #else 2 #end);
|
||||
|
||||
var moveLength = delta / FlxG.updateFramerate / dpiScale;
|
||||
_moveLength += Math.abs(moveLength);
|
||||
|
@ -1912,7 +1889,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
_flickEnded = false;
|
||||
var dpiScale = FlxG.stage.window.display.dpi / 160;
|
||||
|
||||
dpiScale = dpiScale.clamp(0.5, #if android 1 #else 2 #end);
|
||||
dpiScale = FlxMath.clamp(dpiScale, 0.5, #if android 1 #else 2 #end);
|
||||
var velocityMove = flickVelocity * elapsed / dpiScale;
|
||||
_moveLength += Math.abs(velocityMove);
|
||||
curSelectedFloat -= velocityMove;
|
||||
|
@ -1929,7 +1906,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
curSelectedFloat = curSelectedFloat.clamp(0, grpCapsules.countLiving() - 1);
|
||||
curSelectedFloat = FlxMath.clamp(curSelectedFloat, 0, grpCapsules.countLiving() - 1);
|
||||
curSelected = Math.round(curSelectedFloat);
|
||||
|
||||
for (i in 0...grpCapsules.members.length)
|
||||
|
@ -2202,6 +2179,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
*/
|
||||
function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void
|
||||
{
|
||||
if (!controls.active) return;
|
||||
|
||||
if (capsuleAnim)
|
||||
{
|
||||
if (currentCapsule != null)
|
||||
|
@ -2296,10 +2275,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies));
|
||||
intendedCompletion = songScore == null ? 0.0 : Math.max(0,
|
||||
((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes));
|
||||
rememberedDifficulty = currentDifficulty;
|
||||
if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
|
||||
currentCapsule.refreshDisplay(!prepForNewRank);
|
||||
currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2371,7 +2351,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
// Set the album graphic and play the animation if relevant.
|
||||
var newAlbumId:Null<String> = daSong?.data.getAlbumId(currentDifficulty, currentVariation);
|
||||
if (albumRoll.albumId != newAlbumId && (currentVariation != previousVariation || controls.active) && !fromCharSelect)
|
||||
if (albumRoll.albumId != newAlbumId)
|
||||
{
|
||||
albumRoll.albumId = newAlbumId;
|
||||
albumRoll.skipIntro();
|
||||
|
@ -2410,6 +2390,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
trace('RANDOM SELECTED');
|
||||
|
||||
controls.active = false;
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
letterSort.inputEnabled = false;
|
||||
#end
|
||||
|
||||
var availableSongCapsules:Array<SongMenuItem> = grpCapsules.members.filter(function(cap:SongMenuItem) {
|
||||
// Dead capsules are ones which were removed from the list when changing filters.
|
||||
return cap.alive && cap.freeplayData != null;
|
||||
|
@ -2435,10 +2420,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
// Seeing if I can do an animation...
|
||||
curSelected = grpCapsules.members.indexOf(targetSong);
|
||||
changeSelection(0); // Trigger an update.
|
||||
controls.active = false;
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
letterSort.inputEnabled = false;
|
||||
#end
|
||||
|
||||
// Act like we hit Confirm on that song.
|
||||
capsuleOnConfirmDefault(targetSong);
|
||||
|
@ -2606,28 +2587,26 @@ class FreeplayState extends MusicBeatSubState
|
|||
new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) {
|
||||
FunkinSound.emptyPartialQueue();
|
||||
|
||||
funnyCam.fade(FlxColor.BLACK, 0.2, false, function() {
|
||||
Paths.setCurrentLevel(cap?.freeplayData?.levelId);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetVariation: currentVariation,
|
||||
targetInstrumental: targetInstId,
|
||||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
Paths.setCurrentLevel(cap?.freeplayData?.levelId);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetVariation: currentVariation,
|
||||
targetInstrumental: targetInstId,
|
||||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||
#else
|
||||
botPlayMode: false,
|
||||
#end
|
||||
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||
// startTimestamp: 0.0,
|
||||
// playbackRate: 0.5,
|
||||
// botPlayMode: true,
|
||||
}, true);
|
||||
});
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||
#else
|
||||
botPlayMode: false,
|
||||
#end
|
||||
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||
// startTimestamp: 0.0,
|
||||
// playbackRate: 0.5,
|
||||
// botPlayMode: true,
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2675,7 +2654,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat);
|
||||
capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI);
|
||||
if (index + 0.5 < curSelectedFloat) capsule.targetPos.y -= 100;
|
||||
}
|
||||
|
||||
if (curSelected != prevSelected)
|
||||
|
@ -2715,18 +2693,26 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(currentCapsule.freeplayData?.data.id ?? "", currentDifficulty, currentVariation);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
|
||||
intendedCompletion = Scoring.tallyCompletion(songScore?.tallies);
|
||||
rememberedSongId = currentCapsule.freeplayData?.data.id;
|
||||
|
||||
if (currentCapsule.freeplayData == null) albumRoll.albumId = null;
|
||||
|
||||
changeDiff();
|
||||
if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay();
|
||||
var daSongCapsule:SongMenuItem = currentCapsule;
|
||||
if (daSongCapsule.freeplayData != null)
|
||||
{
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick +
|
||||
songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes);
|
||||
rememberedSongId = daSongCapsule.freeplayData.data.id;
|
||||
changeDiff();
|
||||
daSongCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
|
||||
}
|
||||
else
|
||||
currentCapsule.refreshDisplay(!prepForNewRank);
|
||||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
rememberedSongId = null;
|
||||
albumRoll.albumId = null;
|
||||
changeDiff();
|
||||
daSongCapsule.refreshDisplay();
|
||||
}
|
||||
|
||||
for (index => capsule in grpCapsules.members)
|
||||
{
|
||||
|
@ -2739,15 +2725,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
capsule.targetPos.y = capsule.intendedY(index - curSelected);
|
||||
capsule.targetPos.x = capsule.intendedX(index - curSelected) + (CUTOUT_WIDTH * SONGS_POS_MULTI);
|
||||
if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure
|
||||
if (index < curSelected #if FEATURE_TOUCH_CONTROLS
|
||||
&& ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure
|
||||
}
|
||||
|
||||
if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active)
|
||||
{
|
||||
playCurSongPreview(currentCapsule);
|
||||
playCurSongPreview(daSongCapsule);
|
||||
currentCapsule.selected = true;
|
||||
|
||||
// switchBackingImage(currentCapsule.freeplayData);
|
||||
// switchBackingImage(daSongCapsule.freeplayData);
|
||||
}
|
||||
|
||||
// Small vibrations every selection change.
|
||||
|
@ -2765,7 +2752,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
overrideExisting: true,
|
||||
restartTrack: false
|
||||
});
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.7);
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2802,7 +2789,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
end: 0.2
|
||||
},
|
||||
onLoad: function() {
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.7);
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.4);
|
||||
}
|
||||
});
|
||||
if (songDifficulty != null)
|
||||
|
@ -3045,7 +3032,7 @@ class FreeplaySongData
|
|||
/**
|
||||
* Whether or not the song has been favorited.
|
||||
*/
|
||||
public var isFav(get, never):Bool;
|
||||
public var isFav:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether the player has seen/played this song before within freeplay
|
||||
|
@ -3063,11 +3050,6 @@ class FreeplaySongData
|
|||
*/
|
||||
public var fullSongName(get, never):String;
|
||||
|
||||
/**
|
||||
* The song's id and variation, combined with a colon. Dynamically generated depending on your current (or rather, rememberd) variation and difficulty.
|
||||
*/
|
||||
public var idAndVariation(get, never):String;
|
||||
|
||||
/**
|
||||
* The starting BPM of the song, dynamically generated depending on your current (or rather, rememberd) variation and difficulty.
|
||||
*/
|
||||
|
@ -3081,6 +3063,7 @@ class FreeplaySongData
|
|||
{
|
||||
this.data = data;
|
||||
_levelId = levelData.id;
|
||||
this.isFav = Save.instance.isSongFavorited(data.songName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3089,13 +3072,14 @@ class FreeplaySongData
|
|||
*/
|
||||
public function toggleFavorite():Bool
|
||||
{
|
||||
isFav = !isFav;
|
||||
if (isFav)
|
||||
{
|
||||
Save.instance.unfavoriteSong(idAndVariation);
|
||||
Save.instance.favoriteSong(data.songName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Save.instance.favoriteSong(idAndVariation);
|
||||
Save.instance.unfavoriteSong(data.songName);
|
||||
}
|
||||
return isFav;
|
||||
}
|
||||
|
@ -3105,19 +3089,6 @@ class FreeplaySongData
|
|||
// this.isNew = song.isSongNew(suffixedDifficulty);
|
||||
}
|
||||
|
||||
function get_idAndVariation()
|
||||
{
|
||||
var variations:Array<String> = data.getVariationsByCharacterId(FreeplayState.rememberedCharacterId);
|
||||
var variation:Null<String> = data.getFirstValidVariation(FreeplayState.rememberedDifficulty, null, variations);
|
||||
if (variation == null) variation = Constants.DEFAULT_VARIATION;
|
||||
return '${data.id}:${variation}';
|
||||
}
|
||||
|
||||
function get_isFav():Bool
|
||||
{
|
||||
return Save.instance.isSongFavorited(idAndVariation);
|
||||
}
|
||||
|
||||
public function isDifficultyNew(difficulty:String):Bool
|
||||
{
|
||||
// grabs a specific difficulty's new status. used for the difficulty dots.
|
||||
|
|
|
@ -148,22 +148,18 @@ class LetterSort extends FlxSpriteGroup
|
|||
|
||||
public function changeSelection(diff:Int = 0, playSound:Bool = true):Void
|
||||
{
|
||||
@:privateAccess
|
||||
if (instance.controls.active)
|
||||
{
|
||||
doLetterChangeAnims(diff);
|
||||
doLetterChangeAnims(diff);
|
||||
|
||||
var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
|
||||
var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
|
||||
|
||||
// if we're moving left (diff < 0), we want control of the right arrow, and vice versa
|
||||
var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
|
||||
arrowToMove.offset.x = 3 * multiPosOrNeg;
|
||||
// if we're moving left (diff < 0), we want control of the right arrow, and vice versa
|
||||
var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
|
||||
arrowToMove.offset.x = 3 * multiPosOrNeg;
|
||||
|
||||
new FlxTimer().start(2 / 24, function(_) {
|
||||
arrowToMove.offset.x = 0;
|
||||
});
|
||||
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
}
|
||||
new FlxTimer().start(2 / 24, function(_) {
|
||||
arrowToMove.offset.x = 0;
|
||||
});
|
||||
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,6 +49,8 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
public var fakeRanking:FreeplayRank;
|
||||
|
||||
var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect", "perfectsick"];
|
||||
|
||||
public var targetPos:FlxPoint = new FlxPoint();
|
||||
public var doLerp:Bool = false;
|
||||
public var doJumpIn:Bool = false;
|
||||
|
@ -67,9 +69,12 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
public var newText:FlxSprite;
|
||||
|
||||
var difficultyNumbers:Array<CapsuleNumber> = []; // referred to as "bignumbers" in the .fla file!
|
||||
var bpmNumbers:Array<CapsuleNumber> = []; // referred to as "smallnumbers" in the .fla file!
|
||||
var weekNumbers:Array<CapsuleNumber> = [];
|
||||
// public var weekType:FlxSprite;
|
||||
public var bigNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
public var smallNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
public var weekNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
var impactThing:FunkinSprite;
|
||||
|
||||
|
@ -126,18 +131,18 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
for (i in 0...2)
|
||||
{
|
||||
var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
|
||||
add(num);
|
||||
var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
|
||||
add(bigNumber);
|
||||
|
||||
difficultyNumbers.push(num);
|
||||
bigNumbers.push(bigNumber);
|
||||
}
|
||||
|
||||
for (i in 0...3)
|
||||
{
|
||||
var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
|
||||
add(num);
|
||||
var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
|
||||
add(smallNumber);
|
||||
|
||||
bpmNumbers.push(num);
|
||||
smallNumbers.push(smallNumber);
|
||||
}
|
||||
|
||||
// doesn't get added, simply is here to help with visibility of things for the pop in!
|
||||
|
@ -329,38 +334,38 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
shiftX = 186;
|
||||
}
|
||||
|
||||
for (i in 0...bpmNumbers.length)
|
||||
for (i in 0...smallNumbers.length)
|
||||
{
|
||||
bpmNumbers[i].x = this.x + (shiftX + (i * 11));
|
||||
smallNumbers[i].x = this.x + (shiftX + (i * 11));
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (newBPM < 100)
|
||||
{
|
||||
bpmNumbers[i].digit = 0;
|
||||
smallNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10;
|
||||
smallNumbers[i].digit = Math.floor(newBPM / 100) % 10;
|
||||
}
|
||||
|
||||
case 1:
|
||||
if (newBPM < 10)
|
||||
{
|
||||
bpmNumbers[i].digit = 0;
|
||||
smallNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bpmNumbers[i].digit = Math.floor(newBPM / 10) % 10;
|
||||
smallNumbers[i].digit = Math.floor(newBPM / 10) % 10;
|
||||
|
||||
if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4;
|
||||
}
|
||||
case 2:
|
||||
bpmNumbers[i].digit = newBPM % 10;
|
||||
smallNumbers[i].digit = newBPM % 10;
|
||||
default:
|
||||
trace('why the fuck is this being called');
|
||||
}
|
||||
bpmNumbers[i].x += tempShift;
|
||||
smallNumbers[i].x += tempShift;
|
||||
}
|
||||
// diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
|
||||
// diffRatingSprite.visible = false;
|
||||
|
@ -434,21 +439,21 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
{
|
||||
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
|
||||
|
||||
for (i in 0...difficultyNumbers.length)
|
||||
for (i in 0...bigNumbers.length)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (newRating < 10)
|
||||
{
|
||||
difficultyNumbers[i].digit = 0;
|
||||
bigNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
difficultyNumbers[i].digit = Math.floor(newRating / 10);
|
||||
bigNumbers[i].digit = Math.floor(newRating / 10);
|
||||
}
|
||||
case 1:
|
||||
difficultyNumbers[i].digit = newRating % 10;
|
||||
bigNumbers[i].digit = newRating % 10;
|
||||
default:
|
||||
trace('why the fuck is this being called');
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.util.BitmapUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* A class for the backing cards so they dont have to be part of freeplayState......
|
||||
|
|
|
@ -10,6 +10,7 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import openfl.display.BlendMode;
|
||||
import funkin.util.BitmapUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class NewCharacterCard extends BackingCard
|
||||
{
|
||||
|
|
|
@ -15,11 +15,8 @@ import flixel.tweens.FlxEase;
|
|||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.util.SwipeUtil;
|
||||
import funkin.util.InputUtil;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.ui.UIStateMachine;
|
||||
import funkin.ui.UIStateMachine.UIState;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.ui.AtlasMenuList.AtlasMenuItem;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
|
@ -29,7 +26,6 @@ import funkin.ui.title.TitleState;
|
|||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.mobile.ui.FunkinButton;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.util.TouchUtil;
|
||||
import funkin.api.newgrounds.Referral;
|
||||
|
@ -60,12 +56,21 @@ class MainMenuState extends MusicBeatState
|
|||
#end
|
||||
|
||||
var overrideMusic:Bool = false;
|
||||
var uiStateMachine:UIStateMachine = new UIStateMachine();
|
||||
var canInteract(get, never):Bool;
|
||||
var goingToOptions:Bool = false;
|
||||
var goingBack:Bool = false;
|
||||
var canInteract(get, set):Bool;
|
||||
var _canInteract:Bool = false;
|
||||
|
||||
function get_canInteract():Bool
|
||||
{
|
||||
return uiStateMachine.canInteract();
|
||||
return _canInteract;
|
||||
}
|
||||
|
||||
function set_canInteract(value:Bool):Bool
|
||||
{
|
||||
_canInteract = value;
|
||||
trace('canInteract set to: ' + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static var rememberedSelectedIndex:Int = 0;
|
||||
|
@ -79,16 +84,9 @@ class MainMenuState extends MusicBeatState
|
|||
super();
|
||||
overrideMusic = _overrideMusic;
|
||||
|
||||
// Start in Entering state during screen fade in
|
||||
uiStateMachine.transition(Entering);
|
||||
|
||||
upgradeSparkles = new FlxTypedSpriteGroup<UpgradeSparkle>();
|
||||
magenta = new FlxSprite(Paths.image('menuBGMagenta'));
|
||||
camFollow = new FlxObject(0, 0, 1, 1);
|
||||
|
||||
// TODO: enabling and disabling keys is a lil quirky,
|
||||
// we should move towards unifying the UI and it's inputs into this UIStateMachine managed system
|
||||
FlxG.keys.enabled = true;
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
|
@ -128,38 +126,38 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
add(camFollow);
|
||||
|
||||
magenta.scrollFactor.copyFrom(bg.scrollFactor);
|
||||
magenta.scrollFactor.x = bg.scrollFactor.x;
|
||||
magenta.scrollFactor.y = bg.scrollFactor.y;
|
||||
magenta.setGraphicSize(Std.int(bg.width));
|
||||
magenta.updateHitbox();
|
||||
magenta.x = bg.x;
|
||||
magenta.y = bg.y;
|
||||
magenta.visible = false;
|
||||
|
||||
// TODO: Why doesn't this line compile I'm going fucking feral
|
||||
|
||||
if (Preferences.flashingLights) add(magenta);
|
||||
|
||||
menuItems = new MenuTypedList<AtlasMenuItem>();
|
||||
add(menuItems);
|
||||
|
||||
menuItems.onChange.add(onMenuItemChange);
|
||||
menuItems.onAcceptPress.add(_ -> {
|
||||
menuItems.onAcceptPress.add(function(_) {
|
||||
// canInteract = false;
|
||||
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
|
||||
uiStateMachine.transition(Interacting);
|
||||
});
|
||||
|
||||
menuItems.enabled = true;
|
||||
|
||||
createMenuItem('storymode', 'mainmenu/storymode', () -> {
|
||||
FlxG.signals.preStateSwitch.addOnce(() -> {
|
||||
menuItems.enabled = true; // can move on intro
|
||||
createMenuItem('storymode', 'mainmenu/storymode', function() {
|
||||
FlxG.signals.preStateSwitch.addOnce(function() {
|
||||
funkin.FunkinMemory.clearFreeplay();
|
||||
funkin.FunkinMemory.purgeCache();
|
||||
});
|
||||
startExitState(() -> new StoryMenuState());
|
||||
});
|
||||
|
||||
createMenuItem('freeplay', 'mainmenu/freeplay', function() {
|
||||
persistentDraw = true;
|
||||
persistentUpdate = false;
|
||||
rememberedSelectedIndex = menuItems?.selectedIndex ?? 0;
|
||||
if (menuItems != null) rememberedSelectedIndex = menuItems.selectedIndex;
|
||||
// Freeplay has its own custom transition
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
@ -167,11 +165,12 @@ class MainMenuState extends MusicBeatState
|
|||
// Since CUTOUT_WIDTH is static it might retain some old inccrect values so we update it before loading freeplay
|
||||
FreeplayState.CUTOUT_WIDTH = funkin.ui.FullScreenScaleMode.gameCutoutSize.x / 1.5;
|
||||
|
||||
var rememberedFreeplayCharacter = FreeplayState.rememberedCharacterId;
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Debug function: Hold SHIFT when selecting Freeplay to swap character without the char select menu
|
||||
var targetCharacter:Null<String> = FlxG.keys.pressed.SHIFT ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : FreeplayState.rememberedCharacterId;
|
||||
var targetCharacter:Null<String> = (FlxG.keys.pressed.SHIFT) ? (FreeplayState.rememberedCharacterId == "pico" ? "bf" : "pico") : rememberedFreeplayCharacter;
|
||||
#else
|
||||
var targetCharacter:Null<String> = FreeplayState.rememberedCharacterId;
|
||||
var targetCharacter:Null<String> = rememberedFreeplayCharacter;
|
||||
#end
|
||||
|
||||
if (!hasUpgraded)
|
||||
|
@ -186,6 +185,7 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
character: targetCharacter
|
||||
}));
|
||||
canInteract = true;
|
||||
});
|
||||
|
||||
if (hasUpgraded)
|
||||
|
@ -194,7 +194,7 @@ class MainMenuState extends MusicBeatState
|
|||
// In order to prevent popup blockers from triggering,
|
||||
// we need to open the link as an immediate result of a keypress event,
|
||||
// so we can't wait for the flicker animation to complete.
|
||||
var hasPopupBlocker:Bool = #if web true #else false #end;
|
||||
var hasPopupBlocker = #if web true #else false #end;
|
||||
createMenuItem('merch', 'mainmenu/merch', selectMerch, hasPopupBlocker);
|
||||
#end
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ class MainMenuState extends MusicBeatState
|
|||
createMenuItem('upgrade', 'mainmenu/upgrade', function() {
|
||||
#if FEATURE_MOBILE_IAP
|
||||
InAppPurchasesUtil.purchase(InAppPurchasesUtil.UPGRADE_PRODUCT_ID, FlxG.resetState);
|
||||
uiStateMachine.transition(Idle);
|
||||
canInteract = true;
|
||||
#end
|
||||
});
|
||||
}
|
||||
|
@ -222,18 +222,21 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
final spacing:Float = 160;
|
||||
final top:Float = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
|
||||
for (index => menuItem in menuItems)
|
||||
var spacing = 160;
|
||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
for (i in 0...menuItems.length)
|
||||
{
|
||||
var menuItem = menuItems.members[i];
|
||||
menuItem.x = FlxG.width / 2;
|
||||
menuItem.y = top + spacing * index;
|
||||
menuItem.y = top + spacing * i;
|
||||
menuItem.scrollFactor.x = #if !mobile 0.0 #else 0.4 #end; // we want a lil scroll on mobile, for the cute gyro effect
|
||||
// This one affects how much the menu items move when you scroll between them.
|
||||
menuItem.scrollFactor.y = 0.4;
|
||||
|
||||
if (index == 1) camFollow.setPosition(menuItem.getGraphicMidpoint().x, menuItem.getGraphicMidpoint().y);
|
||||
if (i == 1)
|
||||
{
|
||||
camFollow.setPosition(menuItem.getGraphicMidpoint().x, menuItem.getGraphicMidpoint().y);
|
||||
}
|
||||
}
|
||||
|
||||
menuItems.selectItem(rememberedSelectedIndex);
|
||||
|
@ -242,7 +245,7 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
// the upgrade item
|
||||
var targetItem = menuItems.members[2];
|
||||
for (_ in 0...8)
|
||||
for (i in 0...8)
|
||||
{
|
||||
var sparkle:UpgradeSparkle = new UpgradeSparkle(targetItem.x - (targetItem.width / 2), targetItem.y - (targetItem.height / 2), targetItem.width,
|
||||
targetItem.height, FlxG.random.bool(80));
|
||||
|
@ -265,10 +268,12 @@ class MainMenuState extends MusicBeatState
|
|||
// reset camera when debug menu is closed
|
||||
subStateClosed.add(_ -> resetCamStuff(false));
|
||||
|
||||
// TODO: Why does this specific function break with null safety?
|
||||
@:nullSafety(Off)
|
||||
subStateOpened.add((sub:FlxSubState) -> {
|
||||
if (Std.isOfType(sub, FreeplayState))
|
||||
if (Type.getClass(sub) == FreeplayState)
|
||||
{
|
||||
FlxTimer.wait(0.5, () -> {
|
||||
new FlxTimer().start(0.5, _ -> {
|
||||
magenta.visible = false;
|
||||
});
|
||||
}
|
||||
|
@ -286,38 +291,49 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (!ControlsHandler.usingExternalInputDevice)
|
||||
{
|
||||
addOptionsButton(35, FlxG.height - 210, goOptions);
|
||||
addOptionsButton(35, FlxG.height - 210, function() {
|
||||
if (!canInteract || menuItems != null && menuItems.busy) return;
|
||||
|
||||
trace("OPTIONS: Interact complete.");
|
||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
});
|
||||
}
|
||||
|
||||
backButton?.onConfirmStart.add(() -> {
|
||||
uiStateMachine.transition(Interacting);
|
||||
trace('BACK: Interact Start');
|
||||
});
|
||||
if (backButton != null)
|
||||
{
|
||||
backButton.onConfirmStart.add(function():Void {
|
||||
if (backButton == null) return;
|
||||
goingBack = true;
|
||||
if (menuItems != null) menuItems.enabled = false;
|
||||
trace('BACK: Interact Start');
|
||||
});
|
||||
}
|
||||
|
||||
optionsButton?.onConfirmStart.add(() -> {
|
||||
uiStateMachine.transition(Interacting);
|
||||
trace('OPTIONS: Interact Start');
|
||||
});
|
||||
if (optionsButton != null)
|
||||
{
|
||||
optionsButton.onConfirmStart.add(function():Void {
|
||||
if (optionsButton == null) return;
|
||||
goingToOptions = true;
|
||||
if (menuItems != null) menuItems.enabled = false;
|
||||
trace('OPTIONS: Interact Start');
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
super.create();
|
||||
|
||||
// This has to come AFTER!
|
||||
initLeftWatermarkText();
|
||||
}
|
||||
|
||||
function initLeftWatermarkText():Void
|
||||
{
|
||||
if (leftWatermarkText == null) return;
|
||||
|
||||
leftWatermarkText.text = Constants.VERSION;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
if (this.leftWatermarkText != null)
|
||||
{
|
||||
leftWatermarkText.text += ' | Newgrounds: Logged in as ${NewgroundsClient.instance.user?.name}';
|
||||
this.leftWatermarkText.text = Constants.VERSION;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
this.leftWatermarkText.text += ' | Newgrounds: Logged in as ${NewgroundsClient.instance.user?.name}';
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function playMenuMusic():Void
|
||||
|
@ -340,24 +356,27 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void
|
||||
{
|
||||
if (menuItems == null) return;
|
||||
if (menuItems != null)
|
||||
{
|
||||
var item = new AtlasMenuItem(name, Paths.getSparrowAtlas(atlas), callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.ID = menuItems.length;
|
||||
|
||||
var item:AtlasMenuItem = new AtlasMenuItem(name, Paths.getSparrowAtlas(atlas), callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.ID = menuItems.length;
|
||||
item.scrollFactor.set();
|
||||
item.scrollFactor.set();
|
||||
|
||||
// Set the offset of the item so the sprite is centered on the origin.
|
||||
item.centered = true;
|
||||
item.changeAnim('idle');
|
||||
menuItems.addItem(name, item);
|
||||
// Set the offset of the item so the sprite is centered on the origin.
|
||||
item.centered = true;
|
||||
item.changeAnim('idle');
|
||||
|
||||
menuItems.addItem(name, item);
|
||||
}
|
||||
}
|
||||
|
||||
var buttonGrp:Array<FlxSprite> = [];
|
||||
|
||||
function createMenuButtion(name:String, atlas:String, callback:Void->Void):Void
|
||||
{
|
||||
var item:FunkinButton = new FunkinButton(Math.round(FlxG.width * 0.8), Math.round(FlxG.height * 0.7));
|
||||
var item = new funkin.mobile.ui.FunkinButton(Math.round(FlxG.width * 0.8), Math.round(FlxG.height * 0.7));
|
||||
item.makeGraphic(250, 250, FlxColor.BLUE);
|
||||
item.onDown.add(callback);
|
||||
buttonGrp.push(item);
|
||||
|
@ -367,19 +386,30 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
magenta.visible = false;
|
||||
#if FEATURE_TOUCH_CONTROLS
|
||||
// we want to reset our backButton + optionsButton if we are returning to the main menu from a substate like freeplay
|
||||
// however, we dont want to trigger these resets if we are entering the state
|
||||
if (!uiStateMachine.is(Entering))
|
||||
if (backButton != null)
|
||||
{
|
||||
backButton?.animation.play('idle');
|
||||
backButton?.resetCallbacks();
|
||||
|
||||
optionsButton?.animation.play('idle');
|
||||
optionsButton?.resetCallbacks();
|
||||
backButton.animation.play('idle');
|
||||
backButton.resetCallbacks();
|
||||
}
|
||||
if (optionsButton != null)
|
||||
{
|
||||
optionsButton.animation.play('idle');
|
||||
optionsButton.resetCallbacks();
|
||||
}
|
||||
#end
|
||||
super.closeSubState();
|
||||
uiStateMachine.transition(Idle);
|
||||
canInteract = true;
|
||||
}
|
||||
|
||||
override function finishTransIn():Void
|
||||
{
|
||||
super.finishTransIn();
|
||||
canInteract = true;
|
||||
if (menuItems != null)
|
||||
{
|
||||
menuItems.busy = false;
|
||||
menuItems.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onMenuItemChange(selected:MenuListItem)
|
||||
|
@ -397,17 +427,17 @@ class MainMenuState extends MusicBeatState
|
|||
function selectMerch()
|
||||
{
|
||||
Referral.doMerchReferral();
|
||||
uiStateMachine.transition(Idle);
|
||||
canInteract = true;
|
||||
}
|
||||
#end
|
||||
|
||||
public function openPrompt(prompt:Prompt, onClose:Void->Void):Void
|
||||
{
|
||||
uiStateMachine.transition(Interacting);
|
||||
if (menuItems != null) menuItems.enabled = false;
|
||||
persistentUpdate = false;
|
||||
|
||||
prompt.closeCallback = function() {
|
||||
// in our closeSubstate override, we set the uiStateMachine, so no need to set here
|
||||
if (menuItems != null) menuItems.enabled = true;
|
||||
if (onClose != null) onClose();
|
||||
}
|
||||
|
||||
|
@ -416,28 +446,40 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
function startExitState(state:NextState):Void
|
||||
{
|
||||
if (menuItems == null) return;
|
||||
|
||||
uiStateMachine.transition(Exiting); // Start fade out
|
||||
rememberedSelectedIndex = menuItems.selectedIndex;
|
||||
|
||||
// the fadeout duration for the initial alpha tweens, not the screen wipe fadeout!
|
||||
var fadeOutDuration:Float = 0.4;
|
||||
menuItems.forEach(item -> {
|
||||
if (rememberedSelectedIndex != item.ID) FlxTween.tween(item, {alpha: 0}, fadeOutDuration, {ease: FlxEase.quadOut});
|
||||
else
|
||||
item.visible = false;
|
||||
});
|
||||
|
||||
#if mobile
|
||||
if (optionsButton != null) FlxTween.tween(optionsButton, {alpha: 0}, fadeOutDuration, {ease: FlxEase.quadOut});
|
||||
if (backButton != null) FlxTween.tween(backButton, {alpha: 0}, fadeOutDuration, {ease: FlxEase.quadOut});
|
||||
// This just softlocks the menu items and prevents any further interaction.. needs testing with keyboard.
|
||||
if (!canInteract && !ControlsHandler.usingExternalInputDevice) return;
|
||||
#end
|
||||
|
||||
FlxTimer.wait(fadeOutDuration, () -> {
|
||||
trace('Exiting MainMenuState...');
|
||||
FlxG.switchState(state);
|
||||
});
|
||||
if (menuItems != null)
|
||||
{
|
||||
menuItems.enabled = false; // disable for exit
|
||||
canInteract = false;
|
||||
rememberedSelectedIndex = menuItems.selectedIndex;
|
||||
|
||||
var duration = 0.4;
|
||||
menuItems.forEach(function(item) {
|
||||
if (menuItems != null && menuItems.selectedIndex != item.ID)
|
||||
{
|
||||
FlxTween.tween(item, {alpha: 0}, duration, {ease: FlxEase.quadOut});
|
||||
}
|
||||
else
|
||||
{
|
||||
item.visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
#if mobile
|
||||
if (optionsButton != null) FlxTween.tween(optionsButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
|
||||
if (backButton != null) FlxTween.tween(backButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
|
||||
#end
|
||||
|
||||
new FlxTimer().start(duration, function(_) {
|
||||
trace('Exiting MainMenuState...');
|
||||
FlxG.switchState(state);
|
||||
canInteract = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
|
@ -461,26 +503,19 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
#end
|
||||
|
||||
if ((FlxG.sound.music?.volume ?? 1.0) < 0.8)
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.8)
|
||||
{
|
||||
FlxG.sound.music.volume += 0.5 * elapsed;
|
||||
}
|
||||
handleInputs();
|
||||
|
||||
if (menuItems != null) menuItems.busy = !canInteract;
|
||||
|
||||
#if mobile
|
||||
if (optionsButton != null)
|
||||
if (menuItems != null)
|
||||
{
|
||||
optionsButton.active = canInteract || optionsButton.confirming;
|
||||
optionsButton.enabled = optionsButton.active;
|
||||
#if mobile
|
||||
// if (optionsButton != null) optionsButton.active = canInteract && (!menuItems.busy && !goingBack);
|
||||
// if (backButton != null) backButton.active = canInteract && (!menuItems.busy && !goingToOptions);
|
||||
#end
|
||||
if (_exiting) menuItems.enabled = false;
|
||||
}
|
||||
if (backButton != null)
|
||||
{
|
||||
backButton.active = canInteract || backButton.confirming;
|
||||
backButton.enabled = backButton.active;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function handleInputs():Void
|
||||
|
@ -493,7 +528,6 @@ class MainMenuState extends MusicBeatState
|
|||
if (controls.DEBUG_MENU)
|
||||
{
|
||||
persistentUpdate = false;
|
||||
uiStateMachine.transition(Interacting);
|
||||
|
||||
// Cancel the currently flickering menu item because it's about to call a state switch
|
||||
if (menuItems != null && menuItems.busy) menuItems.cancelAccept();
|
||||
|
@ -511,12 +545,12 @@ class MainMenuState extends MusicBeatState
|
|||
// Ctrl+Alt+Shift+E = Dump save data
|
||||
// Ctrl+Alt+Shift+L = Force crash and create a log dump
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, P]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
|
||||
{
|
||||
FlxG.switchState(() -> new funkin.ui.charSelect.CharacterUnlockState('pico'));
|
||||
}
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, W]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 1 point on Weekend 1 story mode (Easy difficulty).
|
||||
|
@ -539,7 +573,7 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
}
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, M]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.M)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 0 points on Weekend 1 story mode (all difficulties).
|
||||
|
@ -565,7 +599,7 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, R]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
|
||||
{
|
||||
// Give the user a hypothetical overridden score,
|
||||
// and see if we can maintain that golden P rank.
|
||||
|
@ -587,7 +621,7 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
}
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, N]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.N)
|
||||
{
|
||||
@:privateAccess
|
||||
{
|
||||
|
@ -596,7 +630,7 @@ class MainMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
if (InputUtil.allPressedWithDebounce([CONTROL, ALT, SHIFT, E]))
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E)
|
||||
{
|
||||
funkin.save.Save.instance.debug_dumpSave();
|
||||
}
|
||||
|
@ -605,19 +639,17 @@ class MainMenuState extends MusicBeatState
|
|||
if (controls.BACK) goBack();
|
||||
}
|
||||
|
||||
function goOptions():Void
|
||||
public function goBack():Void
|
||||
{
|
||||
trace("OPTIONS: Interact complete.");
|
||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
}
|
||||
|
||||
function goBack():Void
|
||||
{
|
||||
trace("BACK: Interact complete.");
|
||||
uiStateMachine.transition(Exiting);
|
||||
rememberedSelectedIndex = menuItems?.selectedIndex ?? 0;
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
if (menuItems == null) return;
|
||||
if (canInteract && !menuItems.busy)
|
||||
{
|
||||
trace("BACK: Interact complete.");
|
||||
canInteract = false;
|
||||
menuItems.busy = true;
|
||||
rememberedSelectedIndex = menuItems.selectedIndex;
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -545,7 +545,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
if (FlxG.sound.music.time < _lastTime)
|
||||
{
|
||||
localConductor.update(FlxG.sound.music.time, !calibrating);
|
||||
b = localConductor.currentBeatTime;
|
||||
|
||||
// Update arrows to be the correct distance away from the receptor.
|
||||
var lastArrowBeat:Float = 0;
|
||||
|
@ -559,7 +558,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
}
|
||||
if (calibrating)
|
||||
{
|
||||
arrowBeat = lastArrowBeat;
|
||||
arrowBeat = lastArrowBeat + 2;
|
||||
}
|
||||
else
|
||||
arrowBeat = 4;
|
||||
|
@ -567,10 +566,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
testStrumline.clean();
|
||||
testStrumline.noteData = [];
|
||||
testStrumline.nextNoteIndex = 0;
|
||||
trace('Restarting conductor');
|
||||
|
||||
_lastTime = FlxG.sound.music.time;
|
||||
return;
|
||||
}
|
||||
|
||||
_lastBeat = b;
|
||||
|
@ -613,7 +608,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms';
|
||||
|
||||
var toRemove:Array<ArrowData> = [];
|
||||
var _lastArrowBeat:Float = 0;
|
||||
|
||||
// Update arrows
|
||||
for (i in 0...arrows.length)
|
||||
{
|
||||
|
@ -634,13 +629,12 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
arrow.sprite.alpha -= elapsed * 5;
|
||||
}
|
||||
|
||||
if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0)
|
||||
if (arrow.sprite.alpha <= 0)
|
||||
{
|
||||
toRemove.push(arrow);
|
||||
arrow.sprite.kill();
|
||||
continue;
|
||||
// arrow.debugText.kill();
|
||||
}
|
||||
_lastArrowBeat = arrow.beat;
|
||||
}
|
||||
|
||||
// Remove arrows that are marked for removal.
|
||||
|
@ -736,7 +730,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
var data:SongNoteData = new SongNoteData(arrowBeat * msPerBeat, _lastDirection, 0, null, null);
|
||||
testStrumline.addNoteData(data, false);
|
||||
|
||||
// Create a jump (double note) every 8 beats to visually indicate first beat - requested by Hundrec
|
||||
if (Math.floor(arrowBeat % 8) == 0)
|
||||
{
|
||||
var data:SongNoteData = new SongNoteData(arrowBeat * msPerBeat, 2, 0, null, null);
|
||||
|
@ -976,11 +969,4 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
preferenceItems.add(item.lefthandText);
|
||||
return item;
|
||||
}
|
||||
|
||||
override public function destroy()
|
||||
{
|
||||
MenuTypedList.pauseInput = false;
|
||||
exitCalibration(true);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,11 +69,10 @@ class OptionsState extends MusicBeatState
|
|||
optionsCodex = new Codex<OptionsMenuPageName>(Options);
|
||||
add(optionsCodex);
|
||||
|
||||
var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu());
|
||||
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData));
|
||||
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu());
|
||||
var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu());
|
||||
var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu());
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu());
|
||||
#end
|
||||
|
||||
|
@ -82,10 +81,9 @@ class OptionsState extends MusicBeatState
|
|||
options.onExit.add(exitToMainMenu);
|
||||
controls.onExit.add(exitControls);
|
||||
preferences.onExit.add(optionsCodex.switchPage.bind(Options));
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
offsets.onExit.add(exitOffsets);
|
||||
#end
|
||||
saveData.onExit.add(optionsCodex.switchPage.bind(Options));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -138,7 +136,6 @@ class OptionsState extends MusicBeatState
|
|||
{
|
||||
optionsCodex.currentPage.enabled = false;
|
||||
// TODO: Animate this transition?
|
||||
FlxG.keys.enabled = false;
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +159,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
|
||||
final CAMERA_MARGIN:Int = 150;
|
||||
|
||||
public function new(saveDataMenu:SaveDataMenu)
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
add(items = new TextMenuList());
|
||||
|
@ -175,8 +172,8 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
// createItem("CONTROL SCHEMES", function() {
|
||||
// FlxG.state.openSubState(new ControlsSchemeMenu());
|
||||
// });
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
createItem("LAG ADJUSTMENT", function() {
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
createItem("INPUT OFFSETS", function() {
|
||||
FlxG.sound.music.fadeOut(0.5, 0, function(tw) {
|
||||
FunkinSound.playMusic('offsetsLoop',
|
||||
{
|
||||
|
@ -199,7 +196,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
#end
|
||||
#if android
|
||||
createItem("OPEN DATA FOLDER", function() {
|
||||
funkin.external.android.DataFolderUtil.openDataFolder();
|
||||
funkin.mobile.external.android.DataFolderUtil.openDataFolder();
|
||||
});
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
@ -230,19 +227,9 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
});
|
||||
}
|
||||
#end
|
||||
|
||||
// no need to show an entire new menu for just one option
|
||||
if (saveDataMenu.hasMultipleOptions())
|
||||
{
|
||||
createItem("SAVE DATA OPTIONS", function() {
|
||||
codex.switchPage(SaveData);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt);
|
||||
}
|
||||
|
||||
createItem("CLEAR SAVE DATA", function() {
|
||||
promptClearSaveData();
|
||||
});
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
createItem("EXIT", exit);
|
||||
#else
|
||||
|
@ -290,6 +277,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
enabled = (prompt == null);
|
||||
#if FEATURE_TOUCH_CONTROLS
|
||||
backButton.active = (!goingBack) ? !items.busy : true;
|
||||
#end
|
||||
|
@ -310,6 +298,31 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
{
|
||||
return items.length > 2;
|
||||
}
|
||||
|
||||
var prompt:Prompt;
|
||||
|
||||
function promptClearSaveData():Void
|
||||
{
|
||||
if (prompt != null) return;
|
||||
prompt = new Prompt("This will delete
|
||||
\nALL your save data.
|
||||
\nAre you sure?
|
||||
", Custom("Delete", "Cancel"));
|
||||
prompt.create();
|
||||
prompt.createBgFromMargin(100, 0xFFFAFD6D);
|
||||
prompt.back.scrollFactor.set(0, 0);
|
||||
add(prompt);
|
||||
prompt.onYes = function() {
|
||||
// Clear the save data.
|
||||
funkin.save.Save.clearData();
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
};
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract OptionsMenuPageName(String) to PageName
|
||||
|
@ -320,5 +333,4 @@ enum abstract OptionsMenuPageName(String) to PageName
|
|||
var Mods = "mods";
|
||||
var Preferences = "preferences";
|
||||
var Offsets = "offsets";
|
||||
var SaveData = "saveData";
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
createPrefDescription();
|
||||
|
||||
camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70);
|
||||
if (items != null) camFollow.y = items.selectedItem.y;
|
||||
|
||||
menuCamera.follow(camFollow, null, 0.085);
|
||||
var margin = 160;
|
||||
|
@ -72,6 +73,7 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
menuCamera.minScrollY = 0;
|
||||
|
||||
items.onChange.add(function(selected) {
|
||||
camFollow.y = selected.y;
|
||||
itemDesc.text = preferenceDesc[items.selectedIndex];
|
||||
});
|
||||
|
||||
|
@ -202,9 +204,6 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// Positions the camera to the selected item.
|
||||
if (items != null) camFollow.y = items.selectedItem.y;
|
||||
|
||||
// Indent the selected item.
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
var thyOffset:Int = 0;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue