1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-08-30 10:25:00 +00:00

Compare commits

...

77 commits

Author SHA1 Message Date
JVN-Pixels b8e788a896
Merge branch 'develop' into JVNPixels-null-safety 2025-08-02 10:53:05 -07:00
JVN-Pixels 637ddb56f3
Merge branch 'FunkinCrew:main' into JVNPixels-null-safety 2025-08-02 10:33:56 -07:00
Abnormal c228d8008f buble 2 2025-08-02 12:07:49 -05:00
Abnormal 499581f793 buble 2025-08-02 12:02:59 -05:00
Abnormal 5b1bcdebde update hmm refs 2025-08-02 10:39:07 -05:00
Abnormal 44cdbb08c4 asset changes wooooooooooooooo 2025-08-02 17:18:18 +03:00
Kolo d6fe54ee79 gyafyhaswguhyshbughsd 2025-08-02 17:18:18 +03:00
Kolo 32c775ef3f tooltips....BUT FOR NOTES! 2025-08-02 17:18:18 +03:00
PurSnake 1605514424 Seriously? 2025-08-02 17:02:31 +03:00
Hyper_ 743e8b13c2 me...................................................................................... 2025-08-02 16:22:18 +03:00
Eric cf89d672e7
Merge pull request #5392 from SantiagoCalebe/main
Just a lil' update of the readme
2025-08-02 08:55:57 -04:00
Kolo 661ddb15f7 me......................................................................... 2025-08-02 07:16:05 -05:00
Hundrec 72fa82716d A little more README polish 2025-08-02 18:54:18 +08:00
Santiago a069ca2dc6
I think i have a problem 2025-08-02 07:46:57 -03:00
Santiago 90a2d7a1ad
??? 2025-08-02 07:41:13 -03:00
cyn0x8 3170346b83 Create ModStore.hx 2025-08-02 11:32:44 +03:00
anysad e6d737125e feature: are you sure? 2025-08-02 11:28:17 +03:00
AwesomezPro99 3d88fbb11f Update PreferencesMenu.hx 2025-08-02 11:10:03 +03:00
JVN-Pixels 0d6bc2bedd Fix NOR in Freeplay after spamming the letter keybinds 2025-08-02 11:07:04 +03:00
anysad da5f691865 fix: no more animation editor crash 2025-08-02 11:03:23 +03:00
Hyper_ f63cbf073a Restore a lost change from #4787 2025-08-02 10:55:58 +03:00
Kolo 30721c3a30 tfw changeDiff wasn't even being called 2025-08-02 10:49:15 +03:00
JackXson-Real 2b1f346097 Add enter/exit animation for charSelect nametag part 2 2025-08-02 10:38:32 +03:00
Hundrec 6b0e607af0 Update another contributor's name 2025-08-02 04:37:19 +08:00
Hundrec 2263b36cd7 Add a new contributor to Changelog and adjust spacing 2025-08-02 04:37:19 +08:00
Abnormal 2dcc528847 bump polymod to optimize some shiiiiiiiiiiiiiiiit 2025-08-02 02:19:14 +08:00
ExcodeFNF 5cb33e895c Freeplay CoolColors Array Removal 2025-08-02 02:01:36 +08:00
AwesomezPro99 1b91f3b57c Fixed DJ Flicker After New Rank 2025-08-02 01:28:14 +08:00
Eric e5e3270fef Update hmm.json to optimize FlxAnimate 2025-08-02 00:43:57 +08:00
EliteMasterEric e003a38cab Exclude the debug print function when we aren't in the build step 2025-08-01 18:32:37 +03:00
Karim Akra e6fd5d9b12
Make the Android back button close the game when in TitleState
Co-authored-by: Hyper_ <40342021+NotHyper-474@users.noreply.github.com>
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-08-01 13:59:02 +07:00
Cameron Taylor 967397311b remove tiny uncommented code, and simplify an unneeded variable initialization 2025-08-01 13:15:56 +07:00
Cameron Taylor 542f59ef72 tallyCompletion() function for cleanup + constency 2025-08-01 13:15:56 +07:00
Kade 065ee74aea
Scoreable notekinds
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-08-01 13:05:11 +07:00
Mihai Alexandru 312b5c8812
Flair adjustments to the build process
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-08-01 12:05:21 +07:00
Mihai Alexandru e9aa6872a3
Bump libs refs
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-08-01 12:03:58 +07:00
Karim Akra 4f9b63f22c
Configure the android adaptive icons through lime & moved the icons stuff to art submodule
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-08-01 01:17:29 +07:00
JVNpixels 0650e55319 Null safety for LevelTitle.hx 2025-07-30 21:23:43 -07:00
MAJigsaw77 962519e7f0 Fix android compile. 2025-07-28 10:41:31 +03:00
MAJigsaw77 34bae4a431 Fix compile once again. 2025-07-28 10:41:31 +03:00
Karim Akra 79478080f6 oops 2025-07-28 10:41:31 +03:00
Karim Akra 65a3ccc8fe add the same changes to the .env parser on project.hxp !! 2025-07-28 10:41:31 +03:00
Karim Akra 4ac851c387 add support for target specific environment values
and renamed `GLOBAL_ADMOB_PUBLISHER` to `MOBILE_GLOBAL_ADMOB_PUBLISHER` !!
2025-07-28 10:41:31 +03:00
MAJigsaw77 70d5730b70 Fix compile. 2025-07-27 00:56:31 -05:00
Abnormal c527fed70c conflict stuffs lol 2025-07-27 00:56:31 -05:00
TechnikTil 054a5cd00b fix fullscreen bug and show clear save data if logged out of ng 2025-07-27 00:56:31 -05:00
TechnikTil 19c5de8fe2 HOPEFULLY FIX EVERYTHING WRONG (this time it was tested between two computers) 2025-07-27 00:56:31 -05:00
TechnikTil dbc26fd621 hopefully fix everything with load from ng 2025-07-27 00:56:31 -05:00
TechnikTil eb1986b246 move clear save data to the top, add clear ng save data 2025-07-27 00:56:31 -05:00
TechnikTil d661d93184 rewrite NGSaveSlot.load to be async, dont precache assets, error handling 2025-07-27 00:56:31 -05:00
TechnikTil 4a07d0a347 make newgrounds functions in Save static and only available if newgrounds feature flag is enabled, make sure base save slot is loaded and is not empty, and recover save data in case newgrounds doesnt wanna play nice 2025-07-27 00:56:31 -05:00
TechnikTil 832f01345f Add Save Data Options, also allow user to load and save with Newgrounds Save Slots. 2025-07-27 00:56:31 -05:00
MAJigsaw77 e8d41c938c Use no-traces define instead of the flag. 2025-07-26 13:46:48 -05:00
MAJigsaw77 a94bd7f36c Fix compiling. 2025-07-26 13:43:19 -05:00
EliteMasterEric c185f10e65 Move Discord client ID to .env 2025-07-26 13:43:19 -05:00
Cameron Taylor 52f3c63071 mobile fix where the capsules don't properly shift away from the letter sorting crap 2025-07-26 12:31:38 +03:00
Cameron Taylor e71062b914 some lil variable renaming in SongMenuItem.hx 2025-07-26 12:31:38 +03:00
Cameron Taylor 3aa6a36b5e small function renaming 2025-07-26 12:31:38 +03:00
MAJigsaw77 a89fa9c121 Acually fix it (oops). 2025-07-26 11:55:36 +03:00
MAJigsaw77 cb2015f51a Fix a workflow compile error. 2025-07-26 11:55:36 +03:00
MAJigsaw77 139ce3847c Fix for env vars that contain = as one of thier characters. 2025-07-26 11:55:36 +03:00
EliteMasterEric 87b40c1203 Obsolete NewgroundsCredentials.hx for storing secret keys in favor of .env 2025-07-26 11:55:36 +03:00
Kade b9852c316b
offset menu changes
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-26 10:36:29 +03:00
Cameron Taylor 444697620c
Use round instead of floor to fix rounding errors in Preferences.
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-26 10:04:00 +03:00
sector-a 6abeefb15d
Set cliprect for clear percent.
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-25 17:43:34 +03:00
sector-a 530bd1f11d
Add onPause callback to pause substate.
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-25 17:36:34 +03:00
sector-a 76c0dea92b
Move results and death haptics to scripts. 2025-07-25 17:23:04 +03:00
Karim Akra 2d7a2202f5
Fix song restarting bug (for good??).
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-25 16:45:55 +03:00
sector-a 1f18a3d1f4
Fix diff dots alignment.
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-25 16:37:39 +03:00
sector-a 0d57f61ffa
Scale up the bg in debug menu on widescreen
Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-25 16:19:46 +03:00
MAJigsaw77 9fa70775b9 Use funkin.Assets instead of openfl.utils.Assets. 2025-07-25 16:15:51 +03:00
sector-a 463afb43c0 assets submod 2025-07-25 16:15:51 +03:00
sector-a 2833827fbf Change the way game searches for pixel icons 2025-07-25 16:15:51 +03:00
Santiago 01cd72e805
Merge branch 'main' into main 2025-07-22 14:13:22 -03:00
Eric 72e0ff6c9b
Merge pull request #5487 from Lasercar/bunker-infiltrated
Bump to 0.7.4
2025-07-22 11:59:45 -04:00
Lasercar 82367dffc2 Bunker infiltrated 2025-07-23 00:30:15 +10:00
Santiago 96a1796905
readme update 2025-07-17 17:39:53 -03:00
73 changed files with 659 additions and 771 deletions

View file

@ -53,7 +53,8 @@ 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 null-safe field access not working properly for functions (ex. `class?.someFunction()`). (Thanks KoloInDaCrib!)
- Fixed Linux being case-sensitive with filenames. (Thanks mikolka9144!)
### Fixed
@ -88,6 +89,7 @@ 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)
@ -705,6 +707,7 @@ 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.
@ -825,7 +828,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 DMMaster636 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 DM-kun 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.
@ -839,13 +842,14 @@ The Playable Pico Update!
## New Contributors for 0.4.1
* @Hundrec made their first contribution in [#2661](https://github.com/FunkinCrew/Funkin/pull/2661)
* @DMMaster636 made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
* @DM-kun 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!
@ -1017,6 +1021,7 @@ 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.

View file

@ -1,14 +1,25 @@
# Friday Night Funkin'
<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' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
<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>
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**

2
art

@ -1 +1 @@
Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723
Subproject commit 490e97f4c6e673a52ee4f9af98325b1aa2d0c3fe

2
assets

@ -1 +1 @@
Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109
Subproject commit 14808c4de5be43b6bc1e60d0285eba628ea6539d

View file

@ -18,28 +18,28 @@
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
"ref": "02334589ff9603a5f483077a44395009644f6274",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
"name": "extension-androidtools",
"type": "haxelib",
"version": "2.2.1"
"version": "2.2.2"
},
{
"name": "extension-haptics",
"type": "haxelib",
"version": "1.0.3"
"version": "1.0.4"
},
{
"name": "extension-iapcore",
"type": "haxelib",
"version": "1.0.3"
"version": "1.0.4"
},
{
"name": "extension-iarcore",
"type": "haxelib",
"version": "1.0.2"
"version": "1.0.3"
},
{
"name": "flixel",
@ -59,7 +59,7 @@
"name": "flxanimate",
"type": "git",
"dir": null,
"ref": "39c1572add28869c558b218fffed13df1b64f376",
"ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c",
"url": "https://github.com/FunkinCrew/flxanimate"
},
{
@ -111,7 +111,7 @@
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "4e24283a047f11bded6affabbc9ec405156e026e",
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
@ -170,7 +170,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
"ref": "0f1193c37761f1a80a888fb84e752490b5d0b656",
"url": "https://github.com/FunkinCrew/lime"
},
{
@ -219,7 +219,7 @@
"name": "polymod",
"type": "git",
"dir": null,
"ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1",
"ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b",
"url": "https://github.com/larsiusprime/polymod"
},
{

View file

@ -5,6 +5,7 @@ import hxp.*;
import lime.tools.*;
import sys.FileSystem;
import sys.io.File;
import haxe.io.Bytes;
import haxe.io.Path;
import haxe.ds.Map;
@ -330,10 +331,10 @@ class Project extends HXProject
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS";
/**
* `-DFEATURE_INPUT_OFFSETS`
* `-DFEATURE_LAG_ADJUSTMENT`
* If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets.
*/
static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS";
static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT";
/**
* `-DFEATURE_LOG_TRACE`
@ -500,13 +501,7 @@ class Project extends HXProject
configureHaxelibs();
configureAssets();
configureIcons();
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
configureASTCTextures();
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
{
@ -829,7 +824,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_INPUT_OFFSETS.apply(this, !isWeb());
FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb());
// Should be true except on web and mobile builds.
// Screenshots doesn't work there, and mobile has its own screenshots anyway.
@ -890,7 +885,7 @@ class Project extends HXProject
if (FEATURE_LOG_TRACE.isDisabled(this))
{
addHaxeFlag("--no-traces");
setHaxedef("no-traces");
}
// Disable the built in pause screen when unfocusing the game.
@ -1251,27 +1246,7 @@ class Project extends HXProject
if (isAndroid())
{
// Adaptive icons
// 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);
adaptiveIcon = new AdaptiveIcon('art/icons/android/', true);
}
else if (isIOS())
{
@ -1303,8 +1278,25 @@ 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();
}
}
}
/**
@ -1703,7 +1695,8 @@ class Project extends HXProject
*/
public function error(message:String):Void
{
Log.error('${message}');
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}'));
Sys.exit(1);
}
/**
@ -1713,7 +1706,7 @@ class Project extends HXProject
{
if (command != "display")
{
Log.info('[INFO] ${message}');
Sys.println('[INFO] ${message}');
}
}
@ -1740,17 +1733,37 @@ class Project extends HXProject
var env = new Map<String, Dynamic>();
for (line in envFile.split('\n'))
{
if (line == "" || line.startsWith("#")) continue;
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
var parts = line.split('=');
if (parts.length != 2) continue;
var index:Int = line.indexOf('=');
env.set(parts[0], parts[1]);
if (index == -1) continue;
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
env.set(field, value);
}
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
{
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');

View file

@ -29,7 +29,7 @@ class Postbuild
var buildTime:Float = roundToTwoDecimals(end - start);
trace('Build took: ${buildTime} seconds');
Sys.println('[INFO] Build took: ${buildTime} seconds');
}
}

View file

@ -9,28 +9,16 @@ 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();
trace('[PREBUILD] Performing pre-build tasks...');
Sys.println('[INFO] Performing pre-build tasks...');
saveBuildTime();
buildCredsFile();
var end:Float = Sys.time();
var duration:Float = end - start;
trace('[PREBUILD] Finished pre-build tasks in $duration seconds.');
Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
}
static function saveBuildTime():Void
@ -41,20 +29,4 @@ class NewgroundsCredentials
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);
}
}
}

View file

@ -1,5 +1,6 @@
package funkin.api.discord;
import funkin.util.macro.EnvironmentConfigMacro;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types.DiscordButton;
@ -11,7 +12,7 @@ import sys.thread.Thread;
@:nullSafety
class DiscordClient
{
static final CLIENT_ID:String = "816168432860790794";
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
@ -40,12 +41,28 @@ class DiscordClient
{
trace('[DISCORD] Initializing connection...');
// Discord.initialize(CLIENT_ID, handlers, true, null);
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, "");
}
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

View file

@ -1,5 +1,6 @@
package funkin.api.newgrounds;
import funkin.util.macro.EnvironmentConfigMacro;
import funkin.save.Save;
import funkin.api.newgrounds.Medals.Medal;
#if FEATURE_NEWGROUNDS
@ -17,7 +18,11 @@ 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
@ -37,8 +42,8 @@ class NewgroundsClient
trace('[NEWGROUNDS] Initializing client...');
#if FEATURE_NEWGROUNDS_DEBUG
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
trace('[NEWGROUNDS] App ID: ${APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
#end
if (!hasValidCredentials())
@ -47,9 +52,12 @@ class NewgroundsClient
return;
}
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
@:nullSafety(Off)
{
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.core.setupEncryption(ENCRYPTION_KEY);
}
}
public function init()
@ -168,12 +176,12 @@ class NewgroundsClient
*/
static function hasValidCredentials():Bool
{
return !(NewgroundsCredentials.APP_ID == null
|| NewgroundsCredentials.APP_ID == ""
|| NewgroundsCredentials.APP_ID.contains(" ")
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
return !(APP_ID == null
|| APP_ID == ""
|| (APP_ID != null && APP_ID.contains(" "))
|| ENCRYPTION_KEY == null
|| ENCRYPTION_KEY == ""
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
}
function onLoginResolved(outcome:LoginOutcome):Void

View file

@ -1117,6 +1117,23 @@ 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;
}
}
/**

View file

@ -29,7 +29,7 @@ class AdMobUtil
/**
* AdMob publisher ID used for the application.
*/
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER");
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER");
/**
* Test ad unit IDs for development and testing purposes.

View file

@ -0,0 +1,54 @@
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;
}
}

View file

@ -21,7 +21,6 @@ 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
@ -102,18 +101,6 @@ 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();
@ -198,6 +185,8 @@ 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;
@ -330,9 +319,6 @@ class GameOverSubState extends MusicBeatSubState
}
}
// Handle vibrations on update.
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
// Start death music before firstDeath gets replaced
super.update(elapsed);
}
@ -615,108 +601,6 @@ 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();

View file

@ -213,6 +213,11 @@ class PauseSubState extends MusicBeatSubState
*/
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
/**
* Callback that gets called once substate gets open.
*/
var onPause:Void->Void;
// ===============
// Audio Variables
// ===============
@ -222,10 +227,11 @@ class PauseSubState extends MusicBeatSubState
// Constructor
// ===============
public function new(?params:PauseSubStateParams)
public function new(?params:PauseSubStateParams, ?onPause:Void->Void)
{
super();
this.currentMode = params?.mode ?? Standard;
this.onPause = onPause;
}
// ===============
@ -244,6 +250,8 @@ class PauseSubState extends MusicBeatSubState
AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT);
#end
if (onPause != null) onPause();
super.create();
startPauseMusic();
@ -286,6 +294,7 @@ class PauseSubState extends MusicBeatSubState
hapticTimer.cancel();
hapticTimer = null;
pauseMusic.stop();
onPause = null;
}
// ===============
@ -444,7 +453,7 @@ class PauseSubState extends MusicBeatSubState
offsetText.y = FlxG.height - (offsetText.height + offsetText.height + 40);
offsetTextInfo.y = offsetText.y + offsetText.height + 4;
#if !mobile
#if (!mobile && FEATURE_LAG_ADJUSTMENT)
metadata.add(offsetText);
metadata.add(offsetTextInfo);
#end

View file

@ -43,6 +43,7 @@ import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
import funkin.play.notes.NoteDirection;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.notes.notekind.NoteKind;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.Strumline;
@ -1063,10 +1064,10 @@ class PlayState extends MusicBeatSubState
// If, after updating the conductor, the instrumental has finished, end the song immediately.
// This helps prevent a major bug where the level suddenly loops back to the start or middle.
if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
{
if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition);
}
// if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
// {
// if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition);
// }
}
var pauseButtonCheck:Bool = false;
@ -1234,14 +1235,12 @@ class PlayState extends MusicBeatSubState
switch (mode)
{
case Conversation:
currentConversation.pauseMusic();
preparePauseUI();
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation.pauseMusic());
case Cutscene:
VideoCutscene.pauseVideo();
preparePauseUI();
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> VideoCutscene.pauseVideo());
default: // also known as standard
if (!isInCountdown || isInCutscene) return;
@ -1299,9 +1298,9 @@ class PlayState extends MusicBeatSubState
#end
}
function openPauseSubState(mode:PauseMode, cam:FlxCamera):Void
function openPauseSubState(mode:PauseMode, cam:FlxCamera, ?onPause:Void->Void):Void
{
final pauseSubState = new PauseSubState({mode: mode});
final pauseSubState = new PauseSubState({mode: mode}, onPause);
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
pauseSubState.camera = cam;
@ -1502,7 +1501,9 @@ class PlayState extends MusicBeatSubState
musicPausedBySubState = false;
}
forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume());
// The logic here is that if this sound doesn't auto-destroy
// then it's gonna be reused somewhere, so we just stop it instead.
forEachPausedSound(s -> needsReset ? (s.autoDestroy ? s.destroy() : s.stop()) : s.resume());
// Resume camera tweens if we paused any.
for (camTween in cameraTweensPausedBySubState)
@ -2242,6 +2243,13 @@ class PlayState extends MusicBeatSubState
var strumTime:Float = songNote.time;
if (strumTime < startTime) continue; // Skip notes that are before the start time.
var scoreable = true;
if (songNote.kind != null)
{
var noteKind:NoteKind = NoteKindManager.getNoteKind(songNote.kind);
if (noteKind != null) scoreable = noteKind.scoreable;
}
var noteData:Int = songNote.getDirection();
var playerNote:Bool = true;
@ -2252,7 +2260,7 @@ class PlayState extends MusicBeatSubState
case 0:
playerNoteData.push(songNote);
// increment totalNotes for total possible notes able to be hit by the player
Highscore.tallies.totalNotes++;
if (scoreable) Highscore.tallies.totalNotes++;
case 1:
opponentNoteData.push(songNote);
}
@ -2811,14 +2819,13 @@ class PlayState extends MusicBeatSubState
}
// Send the note hit event.
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff,
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak,
note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff,
daRating == 'sick');
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return;
Highscore.tallies.totalNotesHit++;
// Display the hit on the strums
playerStrumline.hitNote(note, !event.isComboBreak);
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
@ -2826,9 +2833,13 @@ class PlayState extends MusicBeatSubState
vocals.playerVolume = 1;
// Display the combo meter and add the calculation to the score.
if (note.scoreable)
{
Highscore.tallies.totalNotesHit++;
applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak);
popUpScore(event.judgement);
}
}
/**
* Called when a note leaves the screen and is considered missed by the player.

View file

@ -527,8 +527,7 @@ 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 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100;
clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
@ -744,6 +743,7 @@ 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,105 +751,6 @@ 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;
@ -1035,8 +936,6 @@ class ResultState extends MusicBeatSubState
#end
}
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
super.update(elapsed);
}

View file

@ -288,33 +288,22 @@ class CharacterDataParser
{
var charPath:String = "freeplay/icons/";
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
switch (char)
final charIDParts:Array<String> = char.split("-");
var iconName:String = "";
for (i in 0...charIDParts.length)
{
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';
iconName += charIDParts[i];
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
{
charPath += '${iconName}pixel';
break;
}
else
{
if (i < charIDParts.length - 1) iconName += '-';
continue;
}
}
if (!Assets.exists(Paths.image(charPath)))

View file

@ -10,6 +10,9 @@ 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;
@ -95,8 +98,23 @@ 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

View file

@ -17,6 +17,7 @@ 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
@ -1103,6 +1104,7 @@ 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);
@ -1127,6 +1129,7 @@ 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;

View file

@ -28,6 +28,13 @@ 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;

View file

@ -11,7 +11,21 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam;
class NoteKindManager
{
static var noteKinds:Map<String, NoteKind> = [];
/**
* 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);
}
public static function loadScripts():Void
{

View file

@ -1,6 +1,7 @@
package funkin.play.scoring;
import funkin.save.Save.SaveScoreData;
import funkin.save.Save.SaveScoreTallyData;
/**
* Which system to use when scoring and judging notes.
@ -374,8 +375,7 @@ class Scoring
if (scoreData.tallies.totalNotes == 0) return null;
// Perfect (Platinum) is a Sick Full Clear
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
if (isPerfectGold)
if (scoreData.tallies.sick == scoreData.tallies.totalNotes)
{
return ScoringRank.PERFECT_GOLD;
}
@ -384,21 +384,21 @@ class Scoring
// Final Grade = (Sick + Good - Miss) / (Total Notes)
var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes;
var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies);
if (grade == Constants.RANK_PERFECT_THRESHOLD)
if (completionAmount == Constants.RANK_PERFECT_THRESHOLD)
{
return ScoringRank.PERFECT;
}
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD)
{
return ScoringRank.EXCELLENT;
}
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD)
{
return ScoringRank.GREAT;
}
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD)
{
return ScoringRank.GOOD;
}
@ -407,6 +407,21 @@ 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)

View file

@ -187,6 +187,7 @@ class Save
theme: ChartEditorTheme.Light,
playtestStartTime: false,
downscroll: false,
showNoteKinds: true,
metronomeVolume: 1.0,
hitsoundVolumePlayer: 1.0,
hitsoundVolumeOpponent: 1.0,
@ -358,6 +359,23 @@ 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
@ -882,14 +900,12 @@ 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 || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
tallies: (previousRank > newRank
|| Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies
};
song.set(difficultyId, newScore);
@ -1844,6 +1860,12 @@ 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`

View file

@ -21,33 +21,25 @@ class PixelatedIcon extends FlxFilteredSprite
{
var charPath:String = "freeplay/icons/";
switch (char)
final charIDParts:Array<String> = char.split("-");
var iconName:String = "";
for (i in 0...charIDParts.length)
{
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';
iconName += charIDParts[i];
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
{
charPath += '${iconName}pixel';
break;
}
else
{
if (i < charIDParts.length - 1) iconName += '-';
continue;
}
}
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
if (!Assets.exists(Paths.image(charPath)))
{
trace('[WARN] Character ${char} has no freeplay icon.');
this.visible = false;
@ -58,7 +50,7 @@ class PixelatedIcon extends FlxFilteredSprite
this.visible = true;
}
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
if (isAnimated)
{

View file

@ -271,6 +271,12 @@ 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"]));
@ -739,6 +745,7 @@ 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});

View file

@ -15,17 +15,18 @@ 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)
{
trace('CreditsData(NULL)');
Sys.println('[INFO] CreditsData(NULL)');
return;
}
if (data.entries == null || data.entries.length == 0)
{
trace('CreditsData(EMPTY)');
Sys.println('[INFO] CreditsData(EMPTY)');
return;
}
@ -36,8 +37,9 @@ class CreditsDataHandler
lineCount += entry?.body?.length ?? 0;
}
trace('CreditsData($entryCount entries containing $lineCount lines)');
Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)');
}
#end
/**
* If for some reason the full credits won't load,

View file

@ -10,7 +10,7 @@ class CreditsDataMacro
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
{
#if !display
trace('Hardcoding credits data...');
Sys.println('[INFO] Hardcoding credits data...');
var json = CreditsDataMacro.fetchJSON();
if (json == null)

View file

@ -4,6 +4,7 @@ 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;
@ -37,7 +38,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));
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x));
menuBG.updateHitbox();
menuBG.screenCenter();
menuBG.scrollFactor.set(0, 0);

View file

@ -100,6 +100,7 @@ class DebugBoundingState extends FlxState
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
offsetEditorDialog.cameras = [hudCam];
offsetEditorDialog.closable = false;
add(offsetEditorDialog);
offsetEditorDialog.showDialog(false);

View file

@ -94,6 +94,9 @@ 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;
@ -631,6 +634,11 @@ 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.
@ -1855,6 +1863,11 @@ 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.
*/
@ -2358,6 +2371,7 @@ 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;
@ -2387,6 +2401,7 @@ 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;
@ -2519,7 +2534,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(gridTiledSprite);
gridTiledSprite.zIndex = 10;
gridGhostNote = new ChartEditorNoteSprite(this);
gridGhostNote = new ChartEditorNoteSprite(this, true);
gridGhostNote.alpha = 0.6;
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
gridGhostNote.visible = false;
@ -3089,6 +3104,9 @@ 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);
@ -3921,6 +3939,9 @@ 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)
@ -5598,8 +5619,19 @@ 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;
}
stopWelcomeMusic();
// TODO: PR Flixel to make onComplete nullable.
if (audioInstTrack != null) audioInstTrack.onComplete = null;
@ -6296,7 +6328,8 @@ 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();
}

View file

@ -1,5 +1,7 @@
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;
@ -10,6 +12,9 @@ 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.
@ -63,11 +68,21 @@ class ChartEditorNoteSprite extends FlxSprite
return overrideData;
}
public function new(parent:ChartEditorState)
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)
{
super();
this.parentState = parent;
this.isGhost = isGhost;
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
@ -89,6 +104,8 @@ 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;
@ -156,6 +173,7 @@ class ChartEditorNoteSprite extends FlxSprite
if (this.noteData == null)
{
this.kill();
updateTooltipPosition();
return this.noteData;
}
@ -167,7 +185,7 @@ class ChartEditorNoteSprite extends FlxSprite
// Update the position to match the note data.
updateNotePosition();
updateTooltipText();
return this.noteData;
}
@ -194,6 +212,50 @@ 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>

View file

@ -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}"/>\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}" rotated="${daFrame.angle == -90}"/>\n';
}
xml += "</TextureAtlas>";

View file

@ -81,8 +81,6 @@ class FreeplayDJ extends FlxAtlasSprite
public override function update(elapsed:Float):Void
{
super.update(elapsed);
switch (currentState)
{
case Intro:
@ -185,6 +183,8 @@ class FreeplayDJ extends FlxAtlasSprite
default:
// I shit myself.
}
super.update(elapsed);
}
function onFinishAnim(name:String):Void

View file

@ -31,6 +31,7 @@ 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;
@ -119,6 +120,11 @@ 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;
@ -169,17 +175,6 @@ 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;
@ -315,7 +310,7 @@ class FreeplayState extends MusicBeatSubState
grpCapsules = new FlxTypedGroup<SongMenuItem>();
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(203, 170);
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(DEFAULT_DOTS_GROUP_POS[0], DEFAULT_DOTS_GROUP_POS[1]);
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'));
@ -1270,7 +1265,6 @@ class FreeplayState extends MusicBeatSubState
});
new FlxTimer().start(2, _ -> {
// dj.fistPump();
prepForNewRank = false;
});
}
@ -1295,9 +1289,22 @@ 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;
@ -1329,7 +1336,16 @@ class FreeplayState extends MusicBeatSubState
}
difficultyDots.group.members[i].visible = true;
difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - shiftAmt);
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++;
}
if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false)
{
@ -1582,12 +1598,10 @@ 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);
calculateCompletion();
lerpScoreDisplays();
handleInputs(elapsed);
@ -1597,7 +1611,7 @@ class FreeplayState extends MusicBeatSubState
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
}
function calculateCompletion():Void
function lerpScoreDisplays():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);
@ -2179,7 +2193,7 @@ class FreeplayState extends MusicBeatSubState
*/
function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void
{
if (!controls.active) return;
if (!controls.active && !force) return;
if (capsuleAnim)
{
@ -2275,11 +2289,10 @@ class FreeplayState extends MusicBeatSubState
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation);
intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : Math.max(0,
((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes));
intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies));
rememberedDifficulty = currentDifficulty;
if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
currentCapsule.refreshDisplay(!prepForNewRank);
}
else
{
@ -2390,11 +2403,6 @@ 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;
@ -2420,6 +2428,10 @@ 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);
@ -2654,6 +2666,7 @@ 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)
@ -2693,26 +2706,18 @@ class FreeplayState extends MusicBeatSubState
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var daSongCapsule:SongMenuItem = currentCapsule;
if (daSongCapsule.freeplayData != null)
{
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation);
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(currentCapsule.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);
}
intendedCompletion = Scoring.tallyCompletion(songScore?.tallies);
rememberedSongId = currentCapsule.freeplayData?.data.id;
if (currentCapsule.freeplayData == null) albumRoll.albumId = null;
changeDiff(0, true);
if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay();
else
{
intendedScore = 0;
intendedCompletion = 0.0;
rememberedSongId = null;
albumRoll.albumId = null;
changeDiff();
daSongCapsule.refreshDisplay();
}
currentCapsule.refreshDisplay(!prepForNewRank);
for (index => capsule in grpCapsules.members)
{
@ -2725,16 +2730,15 @@ 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 #if FEATURE_TOUCH_CONTROLS
&& ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure
if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure
}
if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active)
{
playCurSongPreview(daSongCapsule);
playCurSongPreview(currentCapsule);
currentCapsule.selected = true;
// switchBackingImage(daSongCapsule.freeplayData);
// switchBackingImage(currentCapsule.freeplayData);
}
// Small vibrations every selection change.

View file

@ -147,6 +147,9 @@ class LetterSort extends FlxSpriteGroup
}
public function changeSelection(diff:Int = 0, playSound:Bool = true):Void
{
@:privateAccess
if (instance.controls.active)
{
doLetterChangeAnims(diff);
@ -161,6 +164,7 @@ class LetterSort extends FlxSpriteGroup
});
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
}
}
/**
* Buncho timers and stuff to move the letters and seperators

View file

@ -49,8 +49,6 @@ 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;
@ -69,12 +67,9 @@ class SongMenuItem extends FlxSpriteGroup
public var newText:FlxSprite;
// public var weekType:FlxSprite;
public var bigNumbers:Array<CapsuleNumber> = [];
public var smallNumbers:Array<CapsuleNumber> = [];
public var weekNumbers:Array<CapsuleNumber> = [];
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> = [];
var impactThing:FunkinSprite;
@ -131,18 +126,18 @@ class SongMenuItem extends FlxSpriteGroup
for (i in 0...2)
{
var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
add(bigNumber);
var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
add(num);
bigNumbers.push(bigNumber);
difficultyNumbers.push(num);
}
for (i in 0...3)
{
var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
add(smallNumber);
var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
add(num);
smallNumbers.push(smallNumber);
bpmNumbers.push(num);
}
// doesn't get added, simply is here to help with visibility of things for the pop in!
@ -334,38 +329,38 @@ class SongMenuItem extends FlxSpriteGroup
shiftX = 186;
}
for (i in 0...smallNumbers.length)
for (i in 0...bpmNumbers.length)
{
smallNumbers[i].x = this.x + (shiftX + (i * 11));
bpmNumbers[i].x = this.x + (shiftX + (i * 11));
switch (i)
{
case 0:
if (newBPM < 100)
{
smallNumbers[i].digit = 0;
bpmNumbers[i].digit = 0;
}
else
{
smallNumbers[i].digit = Math.floor(newBPM / 100) % 10;
bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10;
}
case 1:
if (newBPM < 10)
{
smallNumbers[i].digit = 0;
bpmNumbers[i].digit = 0;
}
else
{
smallNumbers[i].digit = Math.floor(newBPM / 10) % 10;
bpmNumbers[i].digit = Math.floor(newBPM / 10) % 10;
if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4;
}
case 2:
smallNumbers[i].digit = newBPM % 10;
bpmNumbers[i].digit = newBPM % 10;
default:
trace('why the fuck is this being called');
}
smallNumbers[i].x += tempShift;
bpmNumbers[i].x += tempShift;
}
// diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
// diffRatingSprite.visible = false;
@ -439,21 +434,21 @@ class SongMenuItem extends FlxSpriteGroup
{
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
for (i in 0...bigNumbers.length)
for (i in 0...difficultyNumbers.length)
{
switch (i)
{
case 0:
if (newRating < 10)
{
bigNumbers[i].digit = 0;
difficultyNumbers[i].digit = 0;
}
else
{
bigNumbers[i].digit = Math.floor(newRating / 10);
difficultyNumbers[i].digit = Math.floor(newRating / 10);
}
case 1:
bigNumbers[i].digit = newRating % 10;
difficultyNumbers[i].digit = newRating % 10;
default:
trace('why the fuck is this being called');
}

View file

@ -16,7 +16,6 @@ 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......

View file

@ -10,7 +10,6 @@ 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
{

View file

@ -268,10 +268,8 @@ 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 (Type.getClass(sub) == FreeplayState)
if (Std.isOfType(sub, FreeplayState))
{
new FlxTimer().start(0.5, _ -> {
magenta.visible = false;

View file

@ -545,6 +545,7 @@ 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;
@ -558,7 +559,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
}
if (calibrating)
{
arrowBeat = lastArrowBeat + 2;
arrowBeat = lastArrowBeat;
}
else
arrowBeat = 4;
@ -566,6 +567,10 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
testStrumline.clean();
testStrumline.noteData = [];
testStrumline.nextNoteIndex = 0;
trace('Restarting conductor');
_lastTime = FlxG.sound.music.time;
return;
}
_lastBeat = b;
@ -608,7 +613,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)
{
@ -629,12 +634,13 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
arrow.sprite.alpha -= elapsed * 5;
}
if (arrow.sprite.alpha <= 0)
if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0)
{
toRemove.push(arrow);
arrow.sprite.kill();
// arrow.debugText.kill();
continue;
}
_lastArrowBeat = arrow.beat;
}
// Remove arrows that are marked for removal.

View file

@ -73,7 +73,7 @@ class OptionsState extends MusicBeatState
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData));
var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu());
var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu());
#if FEATURE_INPUT_OFFSETS
#if FEATURE_LAG_ADJUSTMENT
var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu());
#end
@ -82,7 +82,7 @@ class OptionsState extends MusicBeatState
options.onExit.add(exitToMainMenu);
controls.onExit.add(exitControls);
preferences.onExit.add(optionsCodex.switchPage.bind(Options));
#if FEATURE_INPUT_OFFSETS
#if FEATURE_LAG_ADJUSTMENT
offsets.onExit.add(exitOffsets);
#end
saveData.onExit.add(optionsCodex.switchPage.bind(Options));
@ -174,8 +174,8 @@ class OptionsMenu extends Page<OptionsMenuPageName>
// createItem("CONTROL SCHEMES", function() {
// FlxG.state.openSubState(new ControlsSchemeMenu());
// });
#if FEATURE_INPUT_OFFSETS
createItem("INPUT OFFSETS", function() {
#if FEATURE_LAG_ADJUSTMENT
createItem("LAG ADJUSTMENT", function() {
FlxG.sound.music.fadeOut(0.5, 0, function(tw) {
FunkinSound.playMusic('offsetsLoop',
{

View file

@ -65,7 +65,6 @@ 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;
@ -73,7 +72,6 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
menuCamera.minScrollY = 0;
items.onChange.add(function(selected) {
camFollow.y = selected.y;
itemDesc.text = preferenceDesc[items.selectedIndex];
});
@ -204,6 +202,9 @@ 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;

View file

@ -148,6 +148,6 @@ class NumberPreferenceItem extends TextMenuItem
function toFixed(value:Float):Float
{
var multiplier:Float = Math.pow(10, precision);
return Math.floor(value * multiplier) / multiplier;
return Math.round(value * multiplier) / multiplier;
}
}

View file

@ -5,16 +5,17 @@ import flixel.group.FlxSpriteGroup;
import flixel.util.FlxColor;
import funkin.util.MathUtil;
@:nullSafety
class LevelTitle extends FlxSpriteGroup
{
static final LOCK_PAD:Int = 4;
public final level:Level;
public final level:Null<Level>;
public var targetY:Float;
public var targetY:Float = 0.00;
var title:FlxSprite;
var lock:FlxSprite;
var title:FlxSprite = new FlxSprite();
var lock:Null<FlxSprite>;
public function new(x:Int, y:Int, level:Level)
{
@ -32,13 +33,18 @@ class LevelTitle extends FlxSpriteGroup
{
if (length == 0) return 0;
if (lock.visible)
if (lock != null && lock.visible)
{
return title.width + lock.width + LOCK_PAD;
}
else
{
if (title != null)
{
return title.width;
} else {
return 0;
}
}
}
@ -57,28 +63,37 @@ class LevelTitle extends FlxSpriteGroup
if (flashTick >= 1 / flashFramerate)
{
flashTick %= 1 / flashFramerate;
title.color = (title.color == FlxColor.WHITE) ? 0xFF33ffff : FlxColor.WHITE;
title.color = (title?.color == FlxColor.WHITE) ? 0xFF33ffff : FlxColor.WHITE;
}
}
}
public function showLock():Void
{
if (lock != null)
{
lock.visible = true;
this.x -= (lock.width + LOCK_PAD) / 2;
}
}
public function hideLock():Void
{
if (lock != null)
{
lock.visible = false;
this.x += (lock.width + LOCK_PAD) / 2;
}
}
function buildLevelTitle():Void
{
if (title != null && level != null)
{
title = level.buildTitleGraphic();
add(title);
}
}
function buildLevelLock():Void
{

View file

@ -25,7 +25,6 @@ import funkin.ui.transition.stickers.StickerSubState;
import funkin.util.MathUtil;
import funkin.util.SwipeUtil;
import funkin.util.TouchUtil;
import openfl.utils.Assets;
import funkin.ui.FullScreenScaleMode;
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;

View file

@ -226,11 +226,11 @@ class TitleState extends MusicBeatState
{
FlxG.bitmapLog.add(FlxG.camera.buffer);
#if desktop
#if (desktop || android)
// Pressing BACK on the title screen should close the game.
// This lets you exit without leaving fullscreen mode.
// Only applicable on desktop.
if (controls.BACK)
// Only applicable on desktop and Android.
if (#if android FlxG.android.justReleased.BACK || #end controls.BACK)
{
openfl.Lib.application.window.close();
}

View file

@ -59,13 +59,21 @@ class EnvironmentConfigMacro
for (line in envFile.split('\n'))
{
if (line == "" || line.startsWith("#")) continue;
if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue;
var parts = line.split('=');
if (parts.length != 2) continue;
var index:Int = line.indexOf('=');
envFields.push(parts[0]);
envValues.push(parts[1]);
if (index == -1) continue;
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
if (value == "") continue;
Sys.println('[INFO] Found a key for environment value $field!');
envFields.push(field);
envValues.push(value);
}
var newFields = fields.copy();
@ -100,6 +108,27 @@ class EnvironmentConfigMacro
return newFields;
}
private static function shouldExcludeKey(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 html5
if (mobile || desktop) return true;
#elseif desktop
if (mobile || web) return true;
#elseif android
if (ios || web || desktop) return true;
#elseif ios
if (android || web || desktop) return true;
#end
return false;
}
#end
}

View file

@ -26,7 +26,7 @@ class GitCommit
process.close();
trace('Git Commit ID: ${commitHashSplice}');
Sys.println('[INFO] Git Commit ID: ${commitHashSplice}');
// Generates a string expression
return macro $v{commitHashSplice};
@ -56,7 +56,7 @@ class GitCommit
var branchName:String = branchProcess.stdout.readLine();
branchProcess.close();
trace('Git Branch Name: ${branchName}');
Sys.println('[INFO] Git Branch Name: ${branchName}');
// Generates a string expression
return macro $v{branchName};
@ -103,7 +103,7 @@ class GitCommit
throw e;
}
}
trace('Git Status Output: ${output}');
Sys.println('[INFO] Git Status Output: ${output}');
// Generates a string expression
return macro $v{output.length > 0};

View file

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FDF150</color>
</resources>

View file

@ -1,6 +1,5 @@
package;
import openfl.utils.Assets;
import openfl.errors.Error;
import flixel.FlxG;
import flixel.FlxState;

View file

@ -11,7 +11,6 @@ import massive.munit.async.AsyncFactory;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import flixel.animation.FlxAnimationController;
import openfl.utils.Assets;
import flixel.math.FlxPoint;
@:access(funkin.play.notes.notestyle.NoteStyle)

View file

@ -1,6 +1,5 @@
package funkin.util.assets;
import openfl.utils.Assets;
import massive.munit.util.Timer;
import massive.munit.Assert;
import massive.munit.async.AsyncFactory;