1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-09-05 05:07:39 +00:00

Compare commits

..

17 commits

Author SHA1 Message Date
Lasercar b4d0951dae Variation fallback & chartdata only difficulties in difficulty toolbox 2025-07-23 00:07:56 +10:00
Hyper_ f1c3e99a11 lasercar................................................................................................................................................................................................. 2025-07-22 07:56:29 +08:00
Hyper_ eefe8927c4 The most deranged line loss of all time 2025-07-21 17:43:55 -05:00
cherry 3ff4f14510 Clear key from correct map 2025-07-21 17:13:42 -05:00
Abnormal 955b0db542 THE REALLY COOL STUTTERING FIX!!!!!!!!!!!! 2025-07-21 15:06:19 -05:00
Eric d2df4f0832
Merge pull request #5440 from FunkinCrew/main
Update develop branch
2025-07-21 13:08:44 -04:00
Eric 351719ab13
Merge pull request #5438 from FunkinCrew/EliteMasterEric-patch-4
Update lime, openfl, flixel repo URLs in hmm.json
2025-07-21 13:05:40 -04:00
Eric 4ecfe9863e
Update lime, openfl, flixel repo URLs in hmm.json 2025-07-21 13:05:15 -04:00
Eric 48e140e5c0
Merge pull request #5432 from FunkinCrew/develop-0.7.2
Release the mobile source code already
2025-07-21 12:17:54 -04:00
Hundrec e6bb965d6b
Merge branch 'main' into develop-0.7.2 2025-07-22 00:13:47 +08:00
Baran b00376c88c 0.7.2 bumping is real 2025-07-21 10:33:13 -05:00
CrusherNotDrip 541bb78da9 Bump to 0.7.1 2025-07-16 21:51:46 -05:00
Hundrec 5f5fff870c Update issue templates for Mobile 2025-07-14 08:25:10 -05:00
CrusherNotDrip 52acd86cdf bump versions to 0.7.0 2025-07-14 07:38:31 -05:00
Eric 29f7aca135
Merge pull request #5183 from Hundrec/patch-13
[DOCS] Fix a link in a Changelog entry
2025-06-06 01:14:53 -04:00
Hundrec 35d4d24731
that's not even the right link
what
2025-06-01 01:50:33 -06:00
Hundrec c0dde5c936
missed one
funkin.assets
2025-06-01 01:46:27 -06:00
102 changed files with 944 additions and 1182 deletions

View file

@ -20,13 +20,13 @@ body:
label: Platform label: Platform
description: Which platform are you playing on? description: Which platform are you playing on?
options: options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5) - Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5) - Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows - Windows (Downloadable Build)
- Itch.io (Downloadable Build) - MacOS - MacOS (Downloadable Build)
- Itch.io (Downloadable Build) - Linux - Linux (Downloadable Build)
- Google Playstore - Android
- App Store - iOS
- Compiled from GitHub Source Code - Compiled from GitHub Source Code
validations: validations:
required: true required: true
@ -43,11 +43,23 @@ body:
- Safari - Safari
- Other (Specify in Description field) - Other (Specify in Description field)
- type: input
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
- type: input - type: input
attributes: attributes:
label: Version label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu. description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.6.4 placeholder: ex. 0.7.2
validations: validations:
required: true required: true

View file

@ -21,32 +21,22 @@ body:
label: Platform label: Platform
description: Which platform are you playing on? description: Which platform are you playing on?
options: options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5) - Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5) - Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows - Windows (Downloadable Build)
- Itch.io (Downloadable Build) - MacOS - MacOS (Downloadable Build)
- Itch.io (Downloadable Build) - Linux - Linux (Downloadable Build)
- Compiled from GitHub Source Code - Compiled from GitHub Source Code
validations: validations:
required: true required: true
- type: dropdown
attributes:
label: Browser
description: (Web/HTML5 users only) Which browser are you playing on?
options:
- Google Chrome
- Microsoft Edge
- Firefox
- Opera
- Safari
- Other (Specify in Description field)
- type: input - type: input
attributes: attributes:
label: Version label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu. description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.6.4 placeholder: ex. 0.7.2
validations: validations:
required: true required: true

View file

@ -22,6 +22,8 @@ body:
label: Platform label: Platform
description: Which platform are you compiling for? description: Which platform are you compiling for?
options: options:
- Android
- iOS/iPadOS
- Web/HTML5 - Web/HTML5
- Desktop (Windows) - Desktop (Windows)
- Desktop (Mac) - Desktop (Mac)
@ -34,7 +36,7 @@ body:
attributes: attributes:
label: Version label: Version
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file. description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
placeholder: ex. 0.6.4 placeholder: ex. 0.7.2
validations: validations:
required: true required: true

View file

@ -21,11 +21,13 @@ body:
label: Platform label: Platform
description: Which platform are you playing on? description: Which platform are you playing on?
options: options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5) - Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5) - Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows - Windows (Downloadable Build)
- Itch.io (Downloadable Build) - MacOS - MacOS (Downloadable Build)
- Itch.io (Downloadable Build) - Linux - Linux (Downloadable Build)
- Compiled from GitHub Source Code - Compiled from GitHub Source Code
validations: validations:
required: true required: true
@ -42,11 +44,23 @@ body:
- Safari - Safari
- Other (Specify in Description field) - Other (Specify in Description field)
- type: input
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
- type: input - type: input
attributes: attributes:
label: Version label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu. description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.6.4 placeholder: ex. 0.7.2
validations: validations:
required: true required: true
@ -77,7 +91,7 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: Crash logs label: Crash logs
description: These can be found in the logs folder where Funkin.exe is. description: These can be found in the logs folder where Funkin.exe is, or in your mobile device's file explorer.
placeholder: Upload your logs here... placeholder: Upload your logs here...
validations: validations:
required: true required: true

View file

@ -4,26 +4,6 @@ All notable changes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.3] - 2025-07-21
### Fixed
- Fixed stuttering throughout the game caused by the Polymod upgrade. (Thanks NotHyper-474!)
- [MOBILE] Fixed buttons in the Main Menu not working.
- [iOS] The Upgrade button no longer appears if you have already purchased it (actually this time).
- Fixed the countdown overlapping itself when restarting the song. (Thanks NotHyper-474!)
- Optimized the Week 6 Erect stage.
- Fixed an oversight when clearing the cache. (Thanks cherrythecool!)
- The Input Offset Test menu text now displays in the correct position.
- Fixed script errors appearing in the Week 3 Erect stage.
- Fixed adding variations in the Chart Editor erasing difficulties. (Thanks NotHyper-474!)
## New Contributors for 0.7.3
* @cherrythecool made their first contribution in [#5458](https://github.com/FunkinCrew/Funkin/pull/5458)
## [0.7.2] - 2025-07-18 ## [0.7.2] - 2025-07-18
### Added ### Added
@ -432,7 +412,7 @@ Select. ([3d3e2bd](https://github.com/FunkinCrew/Funkin/commit/3d3e2bd3786b85814
* @JackXson-Real made their first contribution in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346) * @JackXson-Real made their first contribution in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
* @VioletSnowLeopard made their first contribution in [#4382](https://github.com/FunkinCrew/Funkin/pull/4382) * @VioletSnowLeopard made their first contribution in [#4382](https://github.com/FunkinCrew/Funkin/pull/4382)
* @superpowers04 made their first contribution in [#4729](https://github.com/FunkinCrew/Funkin/pull/4729) * @superpowers04 made their first contribution in [#4729](https://github.com/FunkinCrew/Funkin/pull/4729)
* @ShadzXD made their first contribution in [#62](https://github.com/FunkinCrew/Funkin/pull/4729) * @ShadzXD made their first contribution in [funkin.assets#62](https://github.com/FunkinCrew/funkin.assets/pull/62)

2
art

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

2
assets

@ -1 +1 @@
Subproject commit a8d15febf5e37a4fc7ffeb0d3eff9c4d36457a37 Subproject commit 2546f94b8d0822b35e6a11fddda76c51568c4b52

View file

@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
## Requirements ## Requirements
Make sure you're playing: Make sure you're playing:
- the latest version of the game (currently v0.6.4) - the latest version of the game (currently v0.7.2)
- without any mods - without any mods
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin) - on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)

View file

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

View file

@ -5,7 +5,6 @@ import hxp.*;
import lime.tools.*; import lime.tools.*;
import sys.FileSystem; import sys.FileSystem;
import sys.io.File; import sys.io.File;
import haxe.io.Bytes;
import haxe.io.Path; import haxe.io.Path;
import haxe.ds.Map; import haxe.ds.Map;
@ -30,7 +29,7 @@ class Project extends HXProject
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES! * REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
* You only have to change it here, the rest of the game will query this value. * You only have to change it here, the rest of the game will query this value.
*/ */
static final VERSION:String = "0.7.3"; static final VERSION:String = "0.7.2";
/** /**
* The build's number and version code. * The build's number and version code.
@ -331,10 +330,10 @@ class Project extends HXProject
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS"; 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. * 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` * `-DFEATURE_LOG_TRACE`
@ -501,7 +500,13 @@ class Project extends HXProject
configureHaxelibs(); configureHaxelibs();
configureAssets(); configureAssets();
configureIcons(); configureIcons();
configureASTCTextures();
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this)) if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
{ {
@ -824,7 +829,7 @@ class Project extends HXProject
FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile())); FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile()));
// Should be true except on web builds (some asset stuff breaks it) // 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. // Should be true except on web and mobile builds.
// Screenshots doesn't work there, and mobile has its own screenshots anyway. // Screenshots doesn't work there, and mobile has its own screenshots anyway.
@ -885,7 +890,7 @@ class Project extends HXProject
if (FEATURE_LOG_TRACE.isDisabled(this)) if (FEATURE_LOG_TRACE.isDisabled(this))
{ {
setHaxedef("no-traces"); addHaxeFlag("--no-traces");
} }
// Disable the built in pause screen when unfocusing the game. // Disable the built in pause screen when unfocusing the game.
@ -978,7 +983,7 @@ class Project extends HXProject
function configureAndroid() 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()) if (isRelease())
{ {
@ -1246,7 +1251,27 @@ class Project extends HXProject
if (isAndroid()) if (isAndroid())
{ {
// Adaptive icons // 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()) else if (isIOS())
{ {
@ -1278,24 +1303,7 @@ class Project extends HXProject
addIcon("art/icons/icon64.png", 64); addIcon("art/icons/icon64.png", 64);
addIcon("art/icons/iconOG.png"); addIcon("art/icons/iconOG.png");
} info('Done configuring icons.');
info('Done configuring icons.');
}
/**
* Configure the astc textures.
*/
function configureASTCTextures()
{
if (command != "display")
{
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
} }
} }
@ -1695,8 +1703,7 @@ class Project extends HXProject
*/ */
public function error(message:String):Void public function error(message:String):Void
{ {
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}')); Log.error('${message}');
Sys.exit(1);
} }
/** /**
@ -1706,7 +1713,7 @@ class Project extends HXProject
{ {
if (command != "display") if (command != "display")
{ {
Sys.println('[INFO] ${message}'); Log.info('[INFO] ${message}');
} }
} }
@ -1733,37 +1740,17 @@ class Project extends HXProject
var env = new Map<String, Dynamic>(); var env = new Map<String, Dynamic>();
for (line in envFile.split('\n')) 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; env.set(parts[0], parts[1]);
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
env.set(field, value);
} }
return env; 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 public function readASTCExclusion():Void
{ {
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n'); astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');

View file

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

View file

@ -9,16 +9,28 @@ class Prebuild
{ {
static inline final BUILD_TIME_FILE:String = '.build_time'; 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 static function main():Void
{ {
var start:Float = Sys.time(); var start:Float = Sys.time();
Sys.println('[INFO] Performing pre-build tasks...'); trace('[PREBUILD] Performing pre-build tasks...');
saveBuildTime(); saveBuildTime();
buildCredsFile();
var end:Float = Sys.time(); var end:Float = Sys.time();
var duration:Float = end - start; 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 static function saveBuildTime():Void
@ -29,4 +41,20 @@ class Prebuild
fo.writeDouble(now); fo.writeDouble(now);
fo.close(); 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

@ -114,7 +114,7 @@ class InitState extends FlxState
#if ios #if ios
// Setup Audio session // Setup Audio session
funkin.external.ios.AudioSession.initialize(); funkin.mobile.external.ios.AudioSession.initialize();
#end #end
// This ain't a pixel art game! (most of the time) // This ain't a pixel art game! (most of the time)
@ -202,7 +202,7 @@ class InitState extends FlxState
// //
#if android #if android
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK]; FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
funkin.external.android.CallbackUtil.init(); funkin.mobile.external.android.CallbackUtil.init();
#end #end
// //

View file

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

View file

@ -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

View file

@ -1,6 +1,5 @@
package funkin.api.newgrounds; package funkin.api.newgrounds;
import funkin.util.macro.EnvironmentConfigMacro;
import funkin.save.Save; import funkin.save.Save;
import funkin.api.newgrounds.Medals.Medal; import funkin.api.newgrounds.Medals.Medal;
#if FEATURE_NEWGROUNDS #if FEATURE_NEWGROUNDS
@ -11,18 +10,13 @@ import io.newgrounds.NGLite.LoginOutcome;
import io.newgrounds.NGLite.LoginFail; import io.newgrounds.NGLite.LoginFail;
import io.newgrounds.objects.events.Outcome; import io.newgrounds.objects.events.Outcome;
import io.newgrounds.utils.MedalList; import io.newgrounds.utils.MedalList;
import io.newgrounds.utils.SaveSlotList;
import io.newgrounds.utils.ScoreBoardList; import io.newgrounds.utils.ScoreBoardList;
import io.newgrounds.objects.User; import io.newgrounds.objects.User;
@:nullSafety @:nullSafety
class NewgroundsClient 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; public static var instance(get, never):NewgroundsClient;
static var _instance:Null<NewgroundsClient> = null; static var _instance:Null<NewgroundsClient> = null;
static function get_instance():NewgroundsClient static function get_instance():NewgroundsClient
@ -35,15 +29,14 @@ class NewgroundsClient
public var user(get, never):Null<User>; public var user(get, never):Null<User>;
public var medals(get, never):Null<MedalList>; public var medals(get, never):Null<MedalList>;
public var leaderboards(get, never):Null<ScoreBoardList>; public var leaderboards(get, never):Null<ScoreBoardList>;
public var saveSlots(get, never):Null<SaveSlotList>;
private function new() private function new()
{ {
trace('[NEWGROUNDS] Initializing client...'); trace('[NEWGROUNDS] Initializing client...');
#if FEATURE_NEWGROUNDS_DEBUG #if FEATURE_NEWGROUNDS_DEBUG
trace('[NEWGROUNDS] App ID: ${APP_ID}'); trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}'); trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
#end #end
if (!hasValidCredentials()) if (!hasValidCredentials())
@ -52,12 +45,9 @@ class NewgroundsClient
return; return;
} }
@:nullSafety(Off) var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
{ NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved); NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
NG.core.setupEncryption(ENCRYPTION_KEY);
}
} }
public function init() public function init()
@ -176,12 +166,12 @@ class NewgroundsClient
*/ */
static function hasValidCredentials():Bool static function hasValidCredentials():Bool
{ {
return !(APP_ID == null return !(NewgroundsCredentials.APP_ID == null
|| APP_ID == "" || NewgroundsCredentials.APP_ID == ""
|| (APP_ID != null && APP_ID.contains(" ")) || NewgroundsCredentials.APP_ID.contains(" ")
|| ENCRYPTION_KEY == null || NewgroundsCredentials.ENCRYPTION_KEY == null
|| ENCRYPTION_KEY == "" || NewgroundsCredentials.ENCRYPTION_KEY == ""
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" "))); || NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
} }
function onLoginResolved(outcome:LoginOutcome):Void function onLoginResolved(outcome:LoginOutcome):Void
@ -246,8 +236,6 @@ class NewgroundsClient
trace('[NEWGROUNDS] Submitting leaderboard request...'); trace('[NEWGROUNDS] Submitting leaderboard request...');
NG.core.scoreBoards.loadList(onFetchedLeaderboards); NG.core.scoreBoards.loadList(onFetchedLeaderboards);
trace('[NEWGROUNDS] Submitting save slot request...');
NG.core.saveSlots.loadList(onFetchedSaveSlots);
} }
function onLoginFailed(result:LoginFail):Void function onLoginFailed(result:LoginFail):Void
@ -313,13 +301,6 @@ class NewgroundsClient
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData()); // 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> function get_user():Null<User>
{ {
if (NG.core == null || !this.isLoggedIn()) return null; if (NG.core == null || !this.isLoggedIn()) return null;
@ -338,12 +319,6 @@ class NewgroundsClient
return NG.core.scoreBoards; 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> static function getSessionId():Null<String>
{ {
#if js #if js

View file

@ -1117,23 +1117,6 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); + (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

@ -136,9 +136,6 @@ class DropShadowScreenspace extends DropShadowShader
// essentially just stole this from the AngleMask shader but repurposed it to smooth // essentially just stole this from the AngleMask shader but repurposed it to smooth
// the threshold because without any sort of smoothing it produces horrible edges // the threshold because without any sort of smoothing it produces horrible edges
float antialias(vec2 fragCoord, float curThreshold, bool useMask) { float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
if (AA_STAGES == 0.0) {
return intensityPass(fragCoord, curThreshold, useMask);
}
// In GLSL 100, we need to use constant loop bounds // In GLSL 100, we need to use constant loop bounds
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop // Well assume a reasonable maximum for AA_STAGES and use a fixed loop

View file

@ -362,9 +362,6 @@ class DropShadowShader extends FlxShader
// essentially just stole this from the AngleMask shader but repurposed it to smooth // essentially just stole this from the AngleMask shader but repurposed it to smooth
// the threshold because without any sort of smoothing it produces horrible edges // the threshold because without any sort of smoothing it produces horrible edges
float antialias(vec2 fragCoord, float curThreshold, bool useMask) { float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
if (AA_STAGES == 0.0) {
return intensityPass(fragCoord, curThreshold, useMask);
}
// In GLSL 100, we need to use constant loop bounds // In GLSL 100, we need to use constant loop bounds
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop // Well assume a reasonable maximum for AA_STAGES and use a fixed loop

View file

@ -1,6 +1,5 @@
package funkin.external.android; package funkin.mobile.external.android;
#if android
import lime.system.JNI; import lime.system.JNI;
import flixel.util.FlxSignal; import flixel.util.FlxSignal;
@ -10,6 +9,7 @@ import flixel.util.FlxSignal;
@:unreflective @:unreflective
class CallbackUtil class CallbackUtil
{ {
#if android
/** /**
* The result code for `DATA_FOLDER_CLOSED` activity. * The result code for `DATA_FOLDER_CLOSED` activity.
*/ */
@ -42,6 +42,7 @@ class CallbackUtil
initCallBackJNI(new CallbackHandler()); initCallBackJNI(new CallbackHandler());
} }
} }
#end
} }
/** /**
@ -49,7 +50,8 @@ class CallbackUtil
*/ */
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end 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 {} 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); if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
} }
#end
} }
#end

View file

@ -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. * A Utility class to manage the Application's Data folder on Android.
*/ */
@:unreflective @:unreflective
class DataFolderUtil class DataFolderUtil
{ {
#if android
/** /**
* Opens the data folder on an Android device using JNI. * Opens the data folder on an Android device using JNI.
*/ */
@ -19,5 +19,5 @@ class DataFolderUtil
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED); openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
} }
} }
#end
} }
#end

View file

@ -1,4 +1,4 @@
package funkin.external.android; package funkin.mobile.external.android;
#if android #if android
import lime.system.JNI; import lime.system.JNI;

View file

@ -1,6 +1,5 @@
package funkin.external.android; package funkin.mobile.external.android;
#if android
import lime.math.Rectangle; import lime.math.Rectangle;
import lime.system.JNI; import lime.system.JNI;
@ -10,6 +9,7 @@ import lime.system.JNI;
@:unreflective @:unreflective
class ScreenUtil class ScreenUtil
{ {
#if android
/** /**
* Retrieves the dimensions of display cutouts (such as notches) on Android devices. * Retrieves the dimensions of display cutouts (such as notches) on Android devices.
* *
@ -48,5 +48,5 @@ class ScreenUtil
return []; return [];
} }
#end
} }
#end

View file

@ -1,10 +1,9 @@
package funkin.external.ios; package funkin.mobile.external.ios;
#if ios
/** /**
* A Utility class to manage iOS audio. * 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') @:include('AudioSession.hpp')
@:unreflective @:unreflective
extern class AudioSession extern class AudioSession
@ -14,4 +13,3 @@ extern class AudioSession
@:native('setActive') @:native('setActive')
static function setActive(active:Bool):Void; static function setActive(active:Bool):Void;
} }
#end

View file

@ -1,10 +1,9 @@
package funkin.external.ios; package funkin.mobile.external.ios;
#if ios
/** /**
* A Utility class to get iOS screen related informations. * 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') @:include('ScreenUtil.hpp')
@:unreflective @:unreflective
extern class ScreenUtil extern class ScreenUtil
@ -15,4 +14,3 @@ extern class ScreenUtil
@:native('getScreenSize') @:native('getScreenSize')
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void; static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
} }
#end

View 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();
}
}

View file

@ -29,7 +29,7 @@ class AdMobUtil
/** /**
* AdMob publisher ID used for the application. * 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. * Test ad unit IDs for development and testing purposes.

View file

@ -108,21 +108,17 @@ class InAppPurchasesUtil
IAPAndroid.startConnection(); IAPAndroid.startConnection();
#else #else
IAPIOS.onProductDetailsReceived.add(function(productDetails:Array<IAPProductDetails>):Void { IAPIOS.onProductDetailsReceived.add(function(productDetails:Array<IAPProductDetails>):Void {
if (productDetails != null) if (productDetails != null) currentProductDetails = productDetails;
{ hasInitialized = true;
currentProductDetails = productDetails;
}
}); });
IAPIOS.onProductDetailsFailed.add(function(error:IAPError):Void { IAPIOS.onProductDetailsFailed.add(function(error:IAPError):Void {
trace('Failed to request product details: "$error"');
hasInitialized = false; hasInitialized = false;
}); });
IAPIOS.onPurchasesUpdated.add(function(purchases:Array<IAPPurchase>):Void { IAPIOS.onPurchasesUpdated.add(function(purchases:Array<IAPPurchase>):Void {
handlePurchases(purchases); handlePurchases(purchases);
trace("iOS purchases updated: " + purchases.length);
hasInitialized = true;
trace("hasInitialized: " + hasInitialized);
}); });
IAPIOS.init(); IAPIOS.init();

View file

@ -1,9 +1,9 @@
package funkin.mobile.util; package funkin.mobile.util;
#if ios #if ios
import funkin.external.ios.ScreenUtil as NativeScreenUtil; import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil;
#elseif android #elseif android
import funkin.external.android.ScreenUtil as NativeScreenUtil; import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil;
#end #end
import lime.math.Rectangle; import lime.math.Rectangle;
import lime.system.System; import lime.system.System;

View file

@ -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;
}
}

View file

@ -21,6 +21,7 @@ import funkin.util.MathUtil;
import funkin.effects.RetroCameraFade; import funkin.effects.RetroCameraFade;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import funkin.util.TouchUtil; import funkin.util.TouchUtil;
import openfl.utils.Assets;
#if FEATURE_MOBILE_ADVERTISEMENTS #if FEATURE_MOBILE_ADVERTISEMENTS
import funkin.mobile.util.AdMobUtil; import funkin.mobile.util.AdMobUtil;
#end #end
@ -101,6 +102,18 @@ class GameOverSubState extends MusicBeatSubState
var canInput:Bool = false; 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) public function new(params:GameOverParams)
{ {
super(); super();
@ -185,8 +198,6 @@ class GameOverSubState extends MusicBeatSubState
addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack); addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack);
#end #end
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
// Allow input a second later to prevent accidental gameover skips. // Allow input a second later to prevent accidental gameover skips.
new FlxTimer().start(1, function(tmr:FlxTimer) { new FlxTimer().start(1, function(tmr:FlxTimer) {
canInput = true; canInput = true;
@ -319,6 +330,9 @@ class GameOverSubState extends MusicBeatSubState
} }
} }
// Handle vibrations on update.
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
// Start death music before firstDeath gets replaced // Start death music before firstDeath gets replaced
super.update(elapsed); super.update(elapsed);
} }
@ -601,6 +615,108 @@ class GameOverSubState extends MusicBeatSubState
var hasPlayedDeathQuote:Bool = false; 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 public override function destroy():Void
{ {
super.destroy(); super.destroy();

View file

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

View file

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

View file

@ -527,7 +527,8 @@ class ResultState extends MusicBeatSubState
bgFlash.visible = true; bgFlash.visible = true;
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24); FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors. // 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); clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors. // Prevent off-by-one errors.
@ -743,7 +744,6 @@ class ResultState extends MusicBeatSubState
super.draw(); super.draw();
songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height); 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!!! // 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; // 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 override function update(elapsed:Float):Void
{ {
maskShaderDifficulty.swagSprX = difficulty.x; maskShaderDifficulty.swagSprX = difficulty.x;
@ -936,6 +1035,8 @@ class ResultState extends MusicBeatSubState
#end #end
} }
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
super.update(elapsed); super.update(elapsed);
} }

View file

@ -288,22 +288,33 @@ class CharacterDataParser
{ {
var charPath:String = "freeplay/icons/"; var charPath:String = "freeplay/icons/";
final charIDParts:Array<String> = char.split("-"); // FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
var iconName:String = ""; switch (char)
for (i in 0...charIDParts.length)
{ {
iconName += charIDParts[i]; case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
charPath += "bfpixel";
if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) case "monster-christmas":
{ charPath += "monsterpixel";
charPath += '${iconName}pixel'; case "mom" | "mom-car":
break; charPath += "mommypixel";
} case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
else charPath += "picopixel";
{ case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
if (i < charIDParts.length - 1) iconName += '-'; charPath += "gfpixel";
continue; 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))) if (!Assets.exists(Paths.image(charPath)))

View file

@ -10,9 +10,6 @@ class NoteSprite extends FunkinSprite
{ {
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red']; static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
/**
* The hold note sprite for this note.
*/
public var holdNoteSprite:SustainTrail; public var holdNoteSprite:SustainTrail;
var hsvShader:HSVShader; var hsvShader:HSVShader;
@ -98,23 +95,8 @@ class NoteSprite extends FunkinSprite
return this.direction; 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; 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; public var isHoldNote(get, never):Bool;
function get_isHoldNote():Bool function get_isHoldNote():Bool

View file

@ -17,7 +17,6 @@ import funkin.play.notes.NoteVibrationsHandler;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.util.SortUtil; import funkin.util.SortUtil;
import funkin.util.GRhythmUtil; import funkin.util.GRhythmUtil;
import funkin.play.notes.notekind.NoteKind;
import funkin.play.notes.notekind.NoteKindManager; import funkin.play.notes.notekind.NoteKindManager;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
#if mobile #if mobile
@ -1104,7 +1103,6 @@ class Strumline extends FlxSpriteGroup
if (noteSprite != null) if (noteSprite != null)
{ {
var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind);
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle; var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
noteSprite.setupNoteGraphic(noteKindStyle); noteSprite.setupNoteGraphic(noteKindStyle);
@ -1129,7 +1127,6 @@ class Strumline extends FlxSpriteGroup
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
noteSprite.x -= NUDGE; noteSprite.x -= NUDGE;
noteSprite.y = -9999; noteSprite.y = -9999;
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
} }
return noteSprite; return noteSprite;

View file

@ -28,13 +28,6 @@ class NoteKind implements INoteScriptedClass
*/ */
public var params:Array<NoteKindParam>; 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>) public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
{ {
this.noteKind = noteKind; this.noteKind = noteKind;

View file

@ -11,21 +11,7 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam;
class NoteKindManager 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 public static function loadScripts():Void
{ {

View file

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

View file

@ -187,7 +187,6 @@ class Save
theme: ChartEditorTheme.Light, theme: ChartEditorTheme.Light,
playtestStartTime: false, playtestStartTime: false,
downscroll: false, downscroll: false,
showNoteKinds: true,
metronomeVolume: 1.0, metronomeVolume: 1.0,
hitsoundVolumePlayer: 1.0, hitsoundVolumePlayer: 1.0,
hitsoundVolumeOpponent: 1.0, hitsoundVolumeOpponent: 1.0,
@ -359,23 +358,6 @@ class Save
return data.optionsChartEditor.downscroll; 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; public var chartEditorPlaytestStartTime(get, set):Bool;
function get_chartEditorPlaytestStartTime():Bool function get_chartEditorPlaytestStartTime():Bool
@ -900,12 +882,14 @@ class Save
return; 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. // Set the high score and the high rank separately.
var newScore:SaveScoreData = var newScore:SaveScoreData =
{ {
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score, score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
tallies: (previousRank > newRank tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
|| Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies
}; };
song.set(difficultyId, newScore); song.set(difficultyId, newScore);
@ -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...'); 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?';
lime.app.Application.current.window.alert(msg, "Newgrounds Save Slot Failure");
});
}
#end
} }
/** /**
@ -1860,12 +1805,6 @@ typedef SaveDataChartEditorOptions =
*/ */
var ?downscroll:Bool; var ?downscroll:Bool;
/**
* Show Note Kind Indicator in the Chart Editor.
* @default `true`
*/
var ?showNoteKinds:Bool;
/** /**
* Metronome volume in the Chart Editor. * Metronome volume in the Chart Editor.
* @default `1.0` * @default `1.0`

View file

@ -21,25 +21,33 @@ class PixelatedIcon extends FlxFilteredSprite
{ {
var charPath:String = "freeplay/icons/"; var charPath:String = "freeplay/icons/";
final charIDParts:Array<String> = char.split("-"); switch (char)
var iconName:String = "";
for (i in 0...charIDParts.length)
{ {
iconName += charIDParts[i]; case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
charPath += "bfpixel";
if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) case "monster-christmas":
{ charPath += "monsterpixel";
charPath += '${iconName}pixel'; case "mom" | "mom-car":
break; charPath += "mommypixel";
} case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
else charPath += "picopixel";
{ case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
if (i < charIDParts.length - 1) iconName += '-'; charPath += "gfpixel";
continue; 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.'); trace('[WARN] Character ${char} has no freeplay icon.');
this.visible = false; this.visible = false;
@ -50,7 +58,7 @@ class PixelatedIcon extends FlxFilteredSprite
this.visible = true; 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) if (isAnimated)
{ {

View file

@ -271,12 +271,6 @@ class CharSelectSubState extends MusicBeatSubState
nametag.midpointX += cutoutSize; nametag.midpointX += cutoutSize;
add(nametag); add(nametag);
@:privateAccess
{
nametag.midpointY += 200;
FlxTween.tween(nametag, {midpointY: nametag.midpointY - 200}, 1, {ease: FlxEase.expoOut});
}
nametag.scrollFactor.set(); nametag.scrollFactor.set();
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"])); 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(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn}); 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(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 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}); FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn});

View file

@ -15,18 +15,17 @@ class CreditsDataHandler
static final CREDITS_DATA_PATH:String = "assets/data/credits.json"; static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
#end #end
#if macro
public static function debugPrint(data:Null<CreditsData>):Void public static function debugPrint(data:Null<CreditsData>):Void
{ {
if (data == null) if (data == null)
{ {
Sys.println('[INFO] CreditsData(NULL)'); trace('CreditsData(NULL)');
return; return;
} }
if (data.entries == null || data.entries.length == 0) if (data.entries == null || data.entries.length == 0)
{ {
Sys.println('[INFO] CreditsData(EMPTY)'); trace('CreditsData(EMPTY)');
return; return;
} }
@ -37,9 +36,8 @@ class CreditsDataHandler
lineCount += entry?.body?.length ?? 0; 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, * 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> public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
{ {
#if !display #if !display
Sys.println('[INFO] Hardcoding credits data...'); trace('Hardcoding credits data...');
var json = CreditsDataMacro.fetchJSON(); var json = CreditsDataMacro.fetchJSON();
if (json == null) if (json == null)

View file

@ -4,7 +4,6 @@ import flixel.math.FlxPoint;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
import funkin.ui.FullScreenScaleMode;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.ui.TextMenuList; import funkin.ui.TextMenuList;
import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.debug.charting.ChartEditorState;
@ -38,7 +37,7 @@ class DebugMenuSubState extends MusicBeatSubState
// Create the green background. // Create the green background.
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
menuBG.color = 0xFF4CAF50; 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.updateHitbox();
menuBG.screenCenter(); menuBG.screenCenter();
menuBG.scrollFactor.set(0, 0); menuBG.scrollFactor.set(0, 0);

View file

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

View file

@ -94,9 +94,6 @@ import haxe.ui.components.Button;
import haxe.ui.components.DropDown; import haxe.ui.components.DropDown;
import haxe.ui.components.Label; import haxe.ui.components.Label;
import haxe.ui.components.Slider; 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.dialogs.CollapsibleDialog;
import haxe.ui.containers.menus.Menu; import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.menus.MenuBar; import haxe.ui.containers.menus.MenuBar;
@ -634,11 +631,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return isViewDownscroll; 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. * The current theme used by the editor.
* Dictates the appearance of many UI elements. * Dictates the appearance of many UI elements.
@ -1863,11 +1855,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/ */
var menubarItemDownscroll:MenuCheckBox; var menubarItemDownscroll:MenuCheckBox;
/**
* The `View -> Note Kind Indicator` menu item.
*/
var menubarItemViewIndicators:MenuCheckBox;
/** /**
* The `View -> Increase Difficulty` menu item. * The `View -> Increase Difficulty` menu item.
*/ */
@ -2371,7 +2358,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
noteSnapQuantIndex = save.chartEditorNoteQuant; noteSnapQuantIndex = save.chartEditorNoteQuant;
currentLiveInputStyle = save.chartEditorLiveInputStyle; currentLiveInputStyle = save.chartEditorLiveInputStyle;
isViewDownscroll = save.chartEditorDownscroll; isViewDownscroll = save.chartEditorDownscroll;
showNoteKindIndicators = save.chartEditorShowNoteKinds;
playtestStartTime = save.chartEditorPlaytestStartTime; playtestStartTime = save.chartEditorPlaytestStartTime;
currentTheme = save.chartEditorTheme; currentTheme = save.chartEditorTheme;
metronomeVolume = save.chartEditorMetronomeVolume; metronomeVolume = save.chartEditorMetronomeVolume;
@ -2401,7 +2387,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
save.chartEditorNoteQuant = noteSnapQuantIndex; save.chartEditorNoteQuant = noteSnapQuantIndex;
save.chartEditorLiveInputStyle = currentLiveInputStyle; save.chartEditorLiveInputStyle = currentLiveInputStyle;
save.chartEditorDownscroll = isViewDownscroll; save.chartEditorDownscroll = isViewDownscroll;
save.chartEditorShowNoteKinds = showNoteKindIndicators;
save.chartEditorPlaytestStartTime = playtestStartTime; save.chartEditorPlaytestStartTime = playtestStartTime;
save.chartEditorTheme = currentTheme; save.chartEditorTheme = currentTheme;
save.chartEditorMetronomeVolume = metronomeVolume; save.chartEditorMetronomeVolume = metronomeVolume;
@ -2534,7 +2519,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(gridTiledSprite); add(gridTiledSprite);
gridTiledSprite.zIndex = 10; gridTiledSprite.zIndex = 10;
gridGhostNote = new ChartEditorNoteSprite(this, true); gridGhostNote = new ChartEditorNoteSprite(this);
gridGhostNote.alpha = 0.6; gridGhostNote.alpha = 0.6;
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []); gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
gridGhostNote.visible = false; gridGhostNote.visible = false;
@ -3104,9 +3089,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value; menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
menubarItemDownscroll.selected = isViewDownscroll; menubarItemDownscroll.selected = isViewDownscroll;
menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected;
menubarItemViewIndicators.selected = showNoteKindIndicators;
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1); menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1); menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
@ -3939,9 +3921,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectionSquare.width = selectionSquare.height = GRID_SIZE; selectionSquare.width = selectionSquare.height = GRID_SIZE;
selectionSquare.color = FlxColor.RED; selectionSquare.color = FlxColor.RED;
} }
// Additional cleanup on notes.
if (noteTooltipsDirty) noteSprite.updateTooltipText();
} }
for (eventSprite in renderedEvents.members) for (eventSprite in renderedEvents.members)
@ -5620,18 +5599,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
@:nullSafety(Off) @:nullSafety(Off)
function quitChartEditor():Void function quitChartEditor():Void
{ {
if (saveDataDirty) { autoSave();
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(); stopWelcomeMusic();
// TODO: PR Flixel to make onComplete nullable. // TODO: PR Flixel to make onComplete nullable.
if (audioInstTrack != null) audioInstTrack.onComplete = null; if (audioInstTrack != null) audioInstTrack.onComplete = null;
@ -6328,8 +6296,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{ {
currentScrollEase = Math.max(0, targetScrollPosition); currentScrollEase = Math.max(0, targetScrollPosition);
currentScrollEase = Math.min(currentScrollEase, songLengthInPixels); currentScrollEase = Math.min(currentScrollEase, songLengthInPixels);
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000);
1 / 1000), currentScrollEase, 1 / 1000);
moveSongToScrollPosition(); moveSongToScrollPosition();
} }

View file

@ -1,7 +1,5 @@
package funkin.ui.debug.charting.components; package funkin.ui.debug.charting.components;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxFramesCollection;
@ -12,9 +10,6 @@ import funkin.data.song.SongData.SongNoteData;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.NoteDirection; 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. * A sprite that can be used to display a note in a chart.
@ -68,21 +63,11 @@ class ChartEditorNoteSprite extends FlxSprite
return overrideData; return overrideData;
} }
public var isGhost:Bool = false; public function new(parent:ChartEditorState)
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(); super();
this.parentState = parent; this.parentState = parent;
this.isGhost = isGhost;
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds(); var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
@ -104,8 +89,6 @@ class ChartEditorNoteSprite extends FlxSprite
{ {
addNoteStyleAnimations(fetchNoteStyle(entry)); addNoteStyleAnimations(fetchNoteStyle(entry));
} }
kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
} }
static var noteFrameCollection:Null<FlxFramesCollection> = null; static var noteFrameCollection:Null<FlxFramesCollection> = null;
@ -173,7 +156,6 @@ class ChartEditorNoteSprite extends FlxSprite
if (this.noteData == null) if (this.noteData == null)
{ {
this.kill(); this.kill();
updateTooltipPosition();
return this.noteData; return this.noteData;
} }
@ -185,7 +167,7 @@ class ChartEditorNoteSprite extends FlxSprite
// Update the position to match the note data. // Update the position to match the note data.
updateNotePosition(); updateNotePosition();
updateTooltipText();
return this.noteData; return this.noteData;
} }
@ -212,50 +194,6 @@ class ChartEditorNoteSprite extends FlxSprite
this.x += origin.x; this.x += origin.x;
this.y += origin.y; 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> function get_noteStyle():Null<String>

View file

@ -192,7 +192,7 @@ class AssetDataHandler
for (daFrame in obj.frames.frames) 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>"; xml += "</TextureAtlas>";

View file

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

View file

@ -31,7 +31,6 @@ import funkin.input.Controls;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayStatePlaylist; import funkin.play.PlayStatePlaylist;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank; import funkin.play.scoring.Scoring.ScoringRank;
import funkin.play.song.Song; import funkin.play.song.Song;
import funkin.save.Save; import funkin.save.Save;
@ -120,11 +119,6 @@ class FreeplayState extends MusicBeatSubState
*/ */
public static final SONGS_POS_MULTI:Float = 0.75; 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 songs:Array<Null<FreeplaySongData>> = [];
var curSelected:Int = 0; var curSelected:Int = 0;
@ -175,6 +169,17 @@ class FreeplayState extends MusicBeatSubState
return grpCapsules.members[curSelected]; return grpCapsules.members[curSelected];
} }
var coolColors:Array<Int> = [
0xFF9271FD,
0xFF9271FD,
0xFF223344,
0xFF941653,
0xFFFC96D7,
0xFFA0D1FF,
0xFFFF78BF,
0xFFF6B604
];
var grpCapsules:FlxTypedGroup<SongMenuItem>; var grpCapsules:FlxTypedGroup<SongMenuItem>;
var dj:Null<FreeplayDJ> = null; var dj:Null<FreeplayDJ> = null;
@ -310,7 +315,7 @@ class FreeplayState extends MusicBeatSubState
grpCapsules = new FlxTypedGroup<SongMenuItem>(); grpCapsules = new FlxTypedGroup<SongMenuItem>();
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80); 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); letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75);
rankBg = new FunkinSprite(0, 0); rankBg = new FunkinSprite(0, 0);
rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette')); rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette'));
@ -1265,6 +1270,7 @@ class FreeplayState extends MusicBeatSubState
}); });
new FlxTimer().start(2, _ -> { new FlxTimer().start(2, _ -> {
// dj.fistPump();
prepForNewRank = false; prepForNewRank = false;
}); });
} }
@ -1289,22 +1295,9 @@ class FreeplayState extends MusicBeatSubState
function refreshDots(amount:Int, index:Int, prevIndex:Int):Void function refreshDots(amount:Int, index:Int, prevIndex:Int):Void
{ {
var distance:Int = 30; var distance:Int = 30;
var groupOffset:Float = 14.7;
var shiftAmt:Float = (distance * amount) / 2; var shiftAmt:Float = (distance * amount) / 2;
var daSong:Null<FreeplaySongData> = currentCapsule.freeplayData; 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) for (i in 0...difficultyDots.group.members.length)
{ {
// if (difficultyDots.group.members[i] == null) continue; // if (difficultyDots.group.members[i] == null) continue;
@ -1336,16 +1329,7 @@ class FreeplayState extends MusicBeatSubState
} }
difficultyDots.group.members[i].visible = true; 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].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - 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) if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false)
{ {
@ -1598,10 +1582,12 @@ class FreeplayState extends MusicBeatSubState
} }
if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong(); if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong();
if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected); if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected);
if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1); if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1);
lerpScoreDisplays(); calculateCompletion();
handleInputs(elapsed); handleInputs(elapsed);
@ -1611,7 +1597,7 @@ class FreeplayState extends MusicBeatSubState
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); 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); 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); lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100);
@ -2193,7 +2179,7 @@ class FreeplayState extends MusicBeatSubState
*/ */
function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void
{ {
if (!controls.active && !force) return; if (!controls.active) return;
if (capsuleAnim) if (capsuleAnim)
{ {
@ -2289,10 +2275,11 @@ class FreeplayState extends MusicBeatSubState
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation); var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation);
intendedScore = songScore?.score ?? 0; 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; rememberedDifficulty = currentDifficulty;
if (!capsuleAnim) generateSongList(currentFilter, false, true, true); if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
currentCapsule.refreshDisplay(!prepForNewRank); currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
} }
else else
{ {
@ -2403,6 +2390,11 @@ class FreeplayState extends MusicBeatSubState
{ {
trace('RANDOM SELECTED'); 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) { var availableSongCapsules:Array<SongMenuItem> = grpCapsules.members.filter(function(cap:SongMenuItem) {
// Dead capsules are ones which were removed from the list when changing filters. // Dead capsules are ones which were removed from the list when changing filters.
return cap.alive && cap.freeplayData != null; return cap.alive && cap.freeplayData != null;
@ -2428,10 +2420,6 @@ class FreeplayState extends MusicBeatSubState
// Seeing if I can do an animation... // Seeing if I can do an animation...
curSelected = grpCapsules.members.indexOf(targetSong); curSelected = grpCapsules.members.indexOf(targetSong);
changeSelection(0); // Trigger an update. 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. // Act like we hit Confirm on that song.
capsuleOnConfirmDefault(targetSong); capsuleOnConfirmDefault(targetSong);
@ -2599,28 +2587,26 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) { new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) {
FunkinSound.emptyPartialQueue(); FunkinSound.emptyPartialQueue();
funnyCam.fade(FlxColor.BLACK, 0.2, false, function() { Paths.setCurrentLevel(cap?.freeplayData?.levelId);
Paths.setCurrentLevel(cap?.freeplayData?.levelId); LoadingState.loadPlayState(
LoadingState.loadPlayState( {
{ targetSong: targetSong,
targetSong: targetSong, targetDifficulty: currentDifficulty,
targetDifficulty: currentDifficulty, targetVariation: currentVariation,
targetVariation: currentVariation, targetInstrumental: targetInstId,
targetInstrumental: targetInstId, practiceMode: false,
practiceMode: false, minimalMode: false,
minimalMode: false,
#if FEATURE_DEBUG_FUNCTIONS #if FEATURE_DEBUG_FUNCTIONS
botPlayMode: FlxG.keys.pressed.SHIFT, botPlayMode: FlxG.keys.pressed.SHIFT,
#else #else
botPlayMode: false, botPlayMode: false,
#end #end
// TODO: Make these an option! It's currently only accessible via chart editor. // TODO: Make these an option! It's currently only accessible via chart editor.
// startTimestamp: 0.0, // startTimestamp: 0.0,
// playbackRate: 0.5, // playbackRate: 0.5,
// botPlayMode: true, // botPlayMode: true,
}, true); }, true);
});
}); });
} }
@ -2668,7 +2654,6 @@ class FreeplayState extends MusicBeatSubState
capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat); capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat);
capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI); capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI);
if (index + 0.5 < curSelectedFloat) capsule.targetPos.y -= 100;
} }
if (curSelected != prevSelected) if (curSelected != prevSelected)
@ -2708,18 +2693,26 @@ class FreeplayState extends MusicBeatSubState
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(currentCapsule.freeplayData?.data.id ?? "", currentDifficulty, currentVariation); var daSongCapsule:SongMenuItem = currentCapsule;
intendedScore = songScore?.score ?? 0; if (daSongCapsule.freeplayData != null)
{
intendedCompletion = Scoring.tallyCompletion(songScore?.tallies); var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation);
rememberedSongId = currentCapsule.freeplayData?.data.id; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick +
if (currentCapsule.freeplayData == null) albumRoll.albumId = null; songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes);
rememberedSongId = daSongCapsule.freeplayData.data.id;
changeDiff(0, true); changeDiff();
if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay(); daSongCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
}
else else
currentCapsule.refreshDisplay(!prepForNewRank); {
intendedScore = 0;
intendedCompletion = 0.0;
rememberedSongId = null;
albumRoll.albumId = null;
changeDiff();
daSongCapsule.refreshDisplay();
}
for (index => capsule in grpCapsules.members) for (index => capsule in grpCapsules.members)
{ {
@ -2732,15 +2725,16 @@ class FreeplayState extends MusicBeatSubState
capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.y = capsule.intendedY(index - curSelected);
capsule.targetPos.x = capsule.intendedX(index - curSelected) + (CUTOUT_WIDTH * SONGS_POS_MULTI); 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) if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active)
{ {
playCurSongPreview(currentCapsule); playCurSongPreview(daSongCapsule);
currentCapsule.selected = true; currentCapsule.selected = true;
// switchBackingImage(currentCapsule.freeplayData); // switchBackingImage(daSongCapsule.freeplayData);
} }
// Small vibrations every selection change. // Small vibrations every selection change.

View file

@ -148,22 +148,18 @@ class LetterSort extends FlxSpriteGroup
public function changeSelection(diff:Int = 0, playSound:Bool = true):Void public function changeSelection(diff:Int = 0, playSound:Bool = true):Void
{ {
@:privateAccess doLetterChangeAnims(diff);
if (instance.controls.active)
{
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 // if we're moving left (diff < 0), we want control of the right arrow, and vice versa
var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow; var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
arrowToMove.offset.x = 3 * multiPosOrNeg; arrowToMove.offset.x = 3 * multiPosOrNeg;
new FlxTimer().start(2 / 24, function(_) { new FlxTimer().start(2 / 24, function(_) {
arrowToMove.offset.x = 0; arrowToMove.offset.x = 0;
}); });
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
}
} }
/** /**

View file

@ -49,6 +49,8 @@ class SongMenuItem extends FlxSpriteGroup
public var fakeRanking:FreeplayRank; public var fakeRanking:FreeplayRank;
var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect", "perfectsick"];
public var targetPos:FlxPoint = new FlxPoint(); public var targetPos:FlxPoint = new FlxPoint();
public var doLerp:Bool = false; public var doLerp:Bool = false;
public var doJumpIn:Bool = false; public var doJumpIn:Bool = false;
@ -67,9 +69,12 @@ class SongMenuItem extends FlxSpriteGroup
public var newText:FlxSprite; public var newText:FlxSprite;
var difficultyNumbers:Array<CapsuleNumber> = []; // referred to as "bignumbers" in the .fla file! // public var weekType:FlxSprite;
var bpmNumbers:Array<CapsuleNumber> = []; // referred to as "smallnumbers" in the .fla file! public var bigNumbers:Array<CapsuleNumber> = [];
var weekNumbers:Array<CapsuleNumber> = [];
public var smallNumbers:Array<CapsuleNumber> = [];
public var weekNumbers:Array<CapsuleNumber> = [];
var impactThing:FunkinSprite; var impactThing:FunkinSprite;
@ -126,18 +131,18 @@ class SongMenuItem extends FlxSpriteGroup
for (i in 0...2) for (i in 0...2)
{ {
var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0); var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
add(num); add(bigNumber);
difficultyNumbers.push(num); bigNumbers.push(bigNumber);
} }
for (i in 0...3) for (i in 0...3)
{ {
var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0); var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
add(num); 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! // 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; 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) switch (i)
{ {
case 0: case 0:
if (newBPM < 100) if (newBPM < 100)
{ {
bpmNumbers[i].digit = 0; smallNumbers[i].digit = 0;
} }
else else
{ {
bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10; smallNumbers[i].digit = Math.floor(newBPM / 100) % 10;
} }
case 1: case 1:
if (newBPM < 10) if (newBPM < 10)
{ {
bpmNumbers[i].digit = 0; smallNumbers[i].digit = 0;
} }
else 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; if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4;
} }
case 2: case 2:
bpmNumbers[i].digit = newBPM % 10; smallNumbers[i].digit = newBPM % 10;
default: default:
trace('why the fuck is this being called'); 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.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
// diffRatingSprite.visible = false; // diffRatingSprite.visible = false;
@ -434,21 +439,21 @@ class SongMenuItem extends FlxSpriteGroup
{ {
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
for (i in 0...difficultyNumbers.length) for (i in 0...bigNumbers.length)
{ {
switch (i) switch (i)
{ {
case 0: case 0:
if (newRating < 10) if (newRating < 10)
{ {
difficultyNumbers[i].digit = 0; bigNumbers[i].digit = 0;
} }
else else
{ {
difficultyNumbers[i].digit = Math.floor(newRating / 10); bigNumbers[i].digit = Math.floor(newRating / 10);
} }
case 1: case 1:
difficultyNumbers[i].digit = newRating % 10; bigNumbers[i].digit = newRating % 10;
default: default:
trace('why the fuck is this being called'); trace('why the fuck is this being called');
} }

View file

@ -16,6 +16,7 @@ import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.ui.FullScreenScaleMode; import funkin.ui.FullScreenScaleMode;
import funkin.util.BitmapUtil; import funkin.util.BitmapUtil;
import openfl.utils.Assets;
/** /**
* A class for the backing cards so they dont have to be part of freeplayState...... * A class for the backing cards so they dont have to be part of freeplayState......

View file

@ -10,6 +10,7 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import openfl.display.BlendMode; import openfl.display.BlendMode;
import funkin.util.BitmapUtil; import funkin.util.BitmapUtil;
import openfl.utils.Assets;
class NewCharacterCard extends BackingCard class NewCharacterCard extends BackingCard
{ {

View file

@ -58,20 +58,7 @@ class MainMenuState extends MusicBeatState
var overrideMusic:Bool = false; var overrideMusic:Bool = false;
var goingToOptions:Bool = false; var goingToOptions:Bool = false;
var goingBack:Bool = false; var goingBack:Bool = false;
var canInteract(get, set):Bool; var canInteract:Bool = false;
var _canInteract:Bool = false;
function get_canInteract():Bool
{
return _canInteract;
}
function set_canInteract(value:Bool):Bool
{
_canInteract = value;
trace('canInteract set to: ' + value);
return value;
}
static var rememberedSelectedIndex:Int = 0; static var rememberedSelectedIndex:Int = 0;
@ -101,7 +88,6 @@ class MainMenuState extends MusicBeatState
transOut = FlxTransitionableState.defaultTransOut; transOut = FlxTransitionableState.defaultTransOut;
#if FEATURE_MOBILE_IAP #if FEATURE_MOBILE_IAP
trace("hasInitialized: " + InAppPurchasesUtil.hasInitialized);
if (InAppPurchasesUtil.hasInitialized) Preferences.noAds = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID); if (InAppPurchasesUtil.hasInitialized) Preferences.noAds = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID);
// If the user is faster than their shit wifi, it gets the saved noAds instead. // If the user is faster than their shit wifi, it gets the saved noAds instead.
hasUpgraded = Preferences.noAds; hasUpgraded = Preferences.noAds;
@ -142,7 +128,7 @@ class MainMenuState extends MusicBeatState
add(menuItems); add(menuItems);
menuItems.onChange.add(onMenuItemChange); menuItems.onChange.add(onMenuItemChange);
menuItems.onAcceptPress.add(function(_) { menuItems.onAcceptPress.add(function(_) {
// canInteract = false; canInteract = false;
FlxFlicker.flicker(magenta, 1.1, 0.15, false, true); FlxFlicker.flicker(magenta, 1.1, 0.15, false, true);
}); });
@ -268,8 +254,10 @@ class MainMenuState extends MusicBeatState
// reset camera when debug menu is closed // reset camera when debug menu is closed
subStateClosed.add(_ -> resetCamStuff(false)); subStateClosed.add(_ -> resetCamStuff(false));
// TODO: Why does this specific function break with null safety?
@:nullSafety(Off)
subStateOpened.add((sub:FlxSubState) -> { subStateOpened.add((sub:FlxSubState) -> {
if (Std.isOfType(sub, FreeplayState)) if (Type.getClass(sub) == FreeplayState)
{ {
new FlxTimer().start(0.5, _ -> { new FlxTimer().start(0.5, _ -> {
magenta.visible = false; magenta.visible = false;
@ -290,7 +278,7 @@ class MainMenuState extends MusicBeatState
if (!ControlsHandler.usingExternalInputDevice) if (!ControlsHandler.usingExternalInputDevice)
{ {
addOptionsButton(35, FlxG.height - 210, function() { addOptionsButton(35, FlxG.height - 210, function() {
if (!canInteract || menuItems != null && menuItems.busy) return; if (!canInteract) return;
trace("OPTIONS: Interact complete."); trace("OPTIONS: Interact complete.");
startExitState(() -> new funkin.ui.options.OptionsState()); startExitState(() -> new funkin.ui.options.OptionsState());
@ -396,18 +384,12 @@ class MainMenuState extends MusicBeatState
} }
#end #end
super.closeSubState(); super.closeSubState();
canInteract = true;
} }
override function finishTransIn():Void override function finishTransIn():Void
{ {
super.finishTransIn(); super.finishTransIn();
canInteract = true; canInteract = true;
if (menuItems != null)
{
menuItems.busy = false;
menuItems.enabled = true;
}
} }
function onMenuItemChange(selected:MenuListItem) function onMenuItemChange(selected:MenuListItem)
@ -445,7 +427,6 @@ class MainMenuState extends MusicBeatState
function startExitState(state:NextState):Void function startExitState(state:NextState):Void
{ {
#if mobile #if mobile
// This just softlocks the menu items and prevents any further interaction.. needs testing with keyboard.
if (!canInteract && !ControlsHandler.usingExternalInputDevice) return; if (!canInteract && !ControlsHandler.usingExternalInputDevice) return;
#end #end
@ -475,7 +456,6 @@ class MainMenuState extends MusicBeatState
new FlxTimer().start(duration, function(_) { new FlxTimer().start(duration, function(_) {
trace('Exiting MainMenuState...'); trace('Exiting MainMenuState...');
FlxG.switchState(state); FlxG.switchState(state);
canInteract = true;
}); });
} }
} }
@ -639,13 +619,15 @@ class MainMenuState extends MusicBeatState
public function goBack():Void public function goBack():Void
{ {
if (menuItems == null) return; if (canInteract)
if (canInteract && !menuItems.busy)
{ {
trace("BACK: Interact complete."); trace("BACK: Interact complete.");
canInteract = false; canInteract = false;
menuItems.busy = true; if (menuItems != null)
rememberedSelectedIndex = menuItems.selectedIndex; {
menuItems.busy = true;
rememberedSelectedIndex = menuItems.selectedIndex;
}
FlxG.switchState(() -> new TitleState()); FlxG.switchState(() -> new TitleState());
FunkinSound.playOnce(Paths.sound('cancelMenu')); FunkinSound.playOnce(Paths.sound('cancelMenu'));
} }

View file

@ -353,7 +353,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
jumpInText.text = 'Hit the notes as they come in!'; jumpInText.text = 'Hit the notes as they come in!';
#if mobile #if mobile
if (OptionsState.instance.hitbox != null) OptionsState.instance.hitbox.visible = true; if (OptionsState.instance.hitbox != null) OptionsState.instance.hitbox.visible = true;
if (!ControlsHandler.usingExternalInputDevice) if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows && !ControlsHandler.usingExternalInputDevice)
{ {
final amplification:Float = (FlxG.width / FlxG.height) / (FlxG.initialWidth / FlxG.initialHeight); final amplification:Float = (FlxG.width / FlxG.height) / (FlxG.initialWidth / FlxG.initialHeight);
final playerStrumlineScale:Float = ((FlxG.height / FlxG.width) * 1.95) * amplification; final playerStrumlineScale:Float = ((FlxG.height / FlxG.width) * 1.95) * amplification;
@ -388,22 +388,18 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
canExit = false; canExit = false;
differences = []; differences = [];
jumpInText.y = 350; jumpInText.y = testStrumline.y + 175;
#if mobile #if mobile
if (ControlsHandler.usingExternalInputDevice) if (Preferences.controlsScheme != FunkinHitboxControlSchemes.Arrows || ControlsHandler.usingExternalInputDevice)
{ {
#end #end
testStrumline.y = Preferences.downscroll ? FlxG.height - (testStrumline.height + 45) - Constants.STRUMLINE_Y_OFFSET : (testStrumline.height / 2) testStrumline.y = Preferences.downscroll ? FlxG.height - (testStrumline.height + 45) - Constants.STRUMLINE_Y_OFFSET : (testStrumline.height / 2)
- Constants.STRUMLINE_Y_OFFSET; - Constants.STRUMLINE_Y_OFFSET;
if (Preferences.downscroll) jumpInText.y = FlxG.height - 425; if (Preferences.downscroll) jumpInText.y = testStrumline.y - 175;
testStrumline.isDownscroll = Preferences.downscroll; testStrumline.isDownscroll = Preferences.downscroll;
#if mobile #if mobile
} }
else
{
jumpInText.y = FlxG.height - 425;
}
#end #end
}); });
PreciseInputManager.instance.onInputPressed.add(onKeyPress); PreciseInputManager.instance.onInputPressed.add(onKeyPress);
@ -545,7 +541,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
if (FlxG.sound.music.time < _lastTime) if (FlxG.sound.music.time < _lastTime)
{ {
localConductor.update(FlxG.sound.music.time, !calibrating); localConductor.update(FlxG.sound.music.time, !calibrating);
b = localConductor.currentBeatTime;
// Update arrows to be the correct distance away from the receptor. // Update arrows to be the correct distance away from the receptor.
var lastArrowBeat:Float = 0; var lastArrowBeat:Float = 0;
@ -559,7 +554,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
} }
if (calibrating) if (calibrating)
{ {
arrowBeat = lastArrowBeat; arrowBeat = lastArrowBeat + 2;
} }
else else
arrowBeat = 4; arrowBeat = 4;
@ -567,10 +562,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
testStrumline.clean(); testStrumline.clean();
testStrumline.noteData = []; testStrumline.noteData = [];
testStrumline.nextNoteIndex = 0; testStrumline.nextNoteIndex = 0;
trace('Restarting conductor');
_lastTime = FlxG.sound.music.time;
return;
} }
_lastBeat = b; _lastBeat = b;
@ -613,7 +604,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms'; countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms';
var toRemove:Array<ArrowData> = []; var toRemove:Array<ArrowData> = [];
var _lastArrowBeat:Float = 0;
// Update arrows // Update arrows
for (i in 0...arrows.length) for (i in 0...arrows.length)
{ {
@ -634,13 +625,12 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
arrow.sprite.alpha -= elapsed * 5; arrow.sprite.alpha -= elapsed * 5;
} }
if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0) if (arrow.sprite.alpha <= 0)
{ {
toRemove.push(arrow); toRemove.push(arrow);
arrow.sprite.kill(); arrow.sprite.kill();
continue; // arrow.debugText.kill();
} }
_lastArrowBeat = arrow.beat;
} }
// Remove arrows that are marked for removal. // Remove arrows that are marked for removal.

View file

@ -69,11 +69,10 @@ class OptionsState extends MusicBeatState
optionsCodex = new Codex<OptionsMenuPageName>(Options); optionsCodex = new Codex<OptionsMenuPageName>(Options);
add(optionsCodex); add(optionsCodex);
var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu());
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData));
var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu()); var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu());
var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu()); var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu());
#if FEATURE_LAG_ADJUSTMENT #if FEATURE_INPUT_OFFSETS
var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu());
#end #end
@ -82,10 +81,9 @@ class OptionsState extends MusicBeatState
options.onExit.add(exitToMainMenu); options.onExit.add(exitToMainMenu);
controls.onExit.add(exitControls); controls.onExit.add(exitControls);
preferences.onExit.add(optionsCodex.switchPage.bind(Options)); preferences.onExit.add(optionsCodex.switchPage.bind(Options));
#if FEATURE_LAG_ADJUSTMENT #if FEATURE_INPUT_OFFSETS
offsets.onExit.add(exitOffsets); offsets.onExit.add(exitOffsets);
#end #end
saveData.onExit.add(optionsCodex.switchPage.bind(Options));
} }
else else
{ {
@ -161,7 +159,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
final CAMERA_MARGIN:Int = 150; final CAMERA_MARGIN:Int = 150;
public function new(saveDataMenu:SaveDataMenu) public function new()
{ {
super(); super();
add(items = new TextMenuList()); add(items = new TextMenuList());
@ -174,8 +172,8 @@ class OptionsMenu extends Page<OptionsMenuPageName>
// createItem("CONTROL SCHEMES", function() { // createItem("CONTROL SCHEMES", function() {
// FlxG.state.openSubState(new ControlsSchemeMenu()); // FlxG.state.openSubState(new ControlsSchemeMenu());
// }); // });
#if FEATURE_LAG_ADJUSTMENT #if FEATURE_INPUT_OFFSETS
createItem("LAG ADJUSTMENT", function() { createItem("INPUT OFFSETS", function() {
FlxG.sound.music.fadeOut(0.5, 0, function(tw) { FlxG.sound.music.fadeOut(0.5, 0, function(tw) {
FunkinSound.playMusic('offsetsLoop', FunkinSound.playMusic('offsetsLoop',
{ {
@ -198,7 +196,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
#end #end
#if android #if android
createItem("OPEN DATA FOLDER", function() { createItem("OPEN DATA FOLDER", function() {
funkin.external.android.DataFolderUtil.openDataFolder(); funkin.mobile.external.android.DataFolderUtil.openDataFolder();
}); });
#end #end
#if FEATURE_NEWGROUNDS #if FEATURE_NEWGROUNDS
@ -229,19 +227,9 @@ class OptionsMenu extends Page<OptionsMenuPageName>
}); });
} }
#end #end
createItem("CLEAR SAVE DATA", function() {
// no need to show an entire new menu for just one option promptClearSaveData();
if (saveDataMenu.hasMultipleOptions()) });
{
createItem("SAVE DATA OPTIONS", function() {
codex.switchPage(SaveData);
});
}
else
{
createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt);
}
#if NO_FEATURE_TOUCH_CONTROLS #if NO_FEATURE_TOUCH_CONTROLS
createItem("EXIT", exit); createItem("EXIT", exit);
#else #else
@ -289,6 +277,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
override function update(elapsed:Float):Void override function update(elapsed:Float):Void
{ {
enabled = (prompt == null);
#if FEATURE_TOUCH_CONTROLS #if FEATURE_TOUCH_CONTROLS
backButton.active = (!goingBack) ? !items.busy : true; backButton.active = (!goingBack) ? !items.busy : true;
#end #end
@ -309,6 +298,31 @@ class OptionsMenu extends Page<OptionsMenuPageName>
{ {
return items.length > 2; 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 enum abstract OptionsMenuPageName(String) to PageName
@ -319,5 +333,4 @@ enum abstract OptionsMenuPageName(String) to PageName
var Mods = "mods"; var Mods = "mods";
var Preferences = "preferences"; var Preferences = "preferences";
var Offsets = "offsets"; var Offsets = "offsets";
var SaveData = "saveData";
} }

View file

@ -65,6 +65,7 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
createPrefDescription(); createPrefDescription();
camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70); camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70);
if (items != null) camFollow.y = items.selectedItem.y;
menuCamera.follow(camFollow, null, 0.085); menuCamera.follow(camFollow, null, 0.085);
var margin = 160; var margin = 160;
@ -72,6 +73,7 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
menuCamera.minScrollY = 0; menuCamera.minScrollY = 0;
items.onChange.add(function(selected) { items.onChange.add(function(selected) {
camFollow.y = selected.y;
itemDesc.text = preferenceDesc[items.selectedIndex]; itemDesc.text = preferenceDesc[items.selectedIndex];
}); });
@ -202,9 +204,6 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
{ {
super.update(elapsed); super.update(elapsed);
// Positions the camera to the selected item.
if (items != null) camFollow.y = items.selectedItem.y;
// Indent the selected item. // Indent the selected item.
items.forEach(function(daItem:TextMenuItem) { items.forEach(function(daItem:TextMenuItem) {
var thyOffset:Int = 0; var thyOffset:Int = 0;

View file

@ -1,125 +0,0 @@
package funkin.ui.options;
#if FEATURE_NEWGROUNDS
import funkin.api.newgrounds.NewgroundsClient;
#end
import funkin.save.Save;
class SaveDataMenu extends Page<OptionsState.OptionsMenuPageName>
{
var items:TextMenuList;
public function new()
{
super();
add(items = new TextMenuList());
createItem("CLEAR SAVE DATA", openSaveDataPrompt);
#if FEATURE_NEWGROUNDS
if (NewgroundsClient.instance.isLoggedIn())
{
createItem("LOAD FROM NG", function() {
openConfirmPrompt("This will overwrite
\nALL your save data.
\nAre you sure?
", "Overwrite", function() {
Save.loadFromNewgrounds(function() {
FlxG.switchState(() -> new funkin.InitState());
});
});
});
createItem("SAVE TO NG", function() {
openConfirmPrompt("This will overwrite
\nALL save data saved
\non NG. Are you sure?", "Overwrite", function() {
Save.saveToNewgrounds();
});
});
createItem("CLEAR NG SAVE DATA", function() {
openConfirmPrompt("This will delete
\nALL save data saved
\non NG. Are you sure?", "Delete", function() {
funkin.api.newgrounds.NGSaveSlot.instance.clear();
});
});
}
#end
createItem("EXIT", exit);
}
function createItem(name:String, callback:Void->Void, fireInstantly = false)
{
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
item.fireInstantly = fireInstantly;
item.screenCenter(X);
return item;
}
override function update(elapsed:Float)
{
enabled = (prompt == null);
super.update(elapsed);
}
override function set_enabled(value:Bool)
{
items.enabled = value;
return super.set_enabled(value);
}
var prompt:Prompt;
function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void, ?groupToOpenOn:Null<flixel.group.FlxGroup>):Void
{
if (prompt != null) return;
prompt = new Prompt(text, Custom(yesText, "Cancel"));
prompt.create();
prompt.createBgFromMargin(100, 0xFFFAFD6D);
prompt.back.scrollFactor.set(0, 0);
FlxG.state.add(prompt);
prompt.onYes = function() {
onYes();
if (prompt != null)
{
prompt.close();
prompt.destroy();
prompt = null;
}
};
prompt.onNo = function() {
prompt.close();
prompt.destroy();
prompt = null;
}
}
public function openSaveDataPrompt()
{
openConfirmPrompt("This will delete
\nALL your save data.
\nAre you sure?
", "Delete", function() {
// Clear the save data.
Save.clearData();
FlxG.switchState(() -> new funkin.InitState());
});
}
/**
* True if this page has multiple options, excluding the exit option.
* If false, there's no reason to ever show this page.
*/
public function hasMultipleOptions():Bool
{
return items.length > 2;
}
}

View file

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

View file

@ -25,6 +25,7 @@ import funkin.ui.transition.stickers.StickerSubState;
import funkin.util.MathUtil; import funkin.util.MathUtil;
import funkin.util.SwipeUtil; import funkin.util.SwipeUtil;
import funkin.util.TouchUtil; import funkin.util.TouchUtil;
import openfl.utils.Assets;
import funkin.ui.FullScreenScaleMode; import funkin.ui.FullScreenScaleMode;
#if FEATURE_DISCORD_RPC #if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient; import funkin.api.discord.DiscordClient;
@ -604,14 +605,12 @@ class StoryMenuState extends MusicBeatState
var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty); var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty);
FlxG.camera.fade(FlxColor.BLACK, 0.2, false, function() { LoadingState.loadPlayState(
LoadingState.loadPlayState( {
{ targetSong: targetSong,
targetSong: targetSong, targetDifficulty: PlayStatePlaylist.campaignDifficulty,
targetDifficulty: PlayStatePlaylist.campaignDifficulty, targetVariation: targetVariation
targetVariation: targetVariation }, true);
}, true);
});
}); });
} }

View file

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

View file

@ -102,19 +102,12 @@ class WindowUtil
*/ */
public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>(); public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>();
/**
* Has `initWindowEvents()` been called already?
* This is to prevent multiple instances of the same function.
*/
private static var _initializedWindowEvents:Bool = false;
/** /**
* Wires up FlxSignals that happen based on window activity. * Wires up FlxSignals that happen based on window activity.
* For example, we can run a callback when the window is closed. * For example, we can run a callback when the window is closed.
*/ */
public static function initWindowEvents():Void public static function initWindowEvents():Void
{ {
if (_initializedWindowEvents) return; // Fix that annoying
// onUpdate is called every frame just before rendering. // onUpdate is called every frame just before rendering.
// onExit is called when the game window is closed. // onExit is called when the game window is closed.
@ -144,7 +137,6 @@ class WindowUtil
} }
}); });
#end #end
_initializedWindowEvents = true;
} }
/** /**

View file

@ -59,21 +59,13 @@ class EnvironmentConfigMacro
for (line in envFile.split('\n')) for (line in envFile.split('\n'))
{ {
if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(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; envFields.push(parts[0]);
envValues.push(parts[1]);
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(); var newFields = fields.copy();
@ -108,27 +100,6 @@ class EnvironmentConfigMacro
return newFields; 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 #end
} }

View file

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

View file

@ -1,44 +0,0 @@
package funkin.util.macro;
/**
* 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 fileName 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(?fileName:String = 'Build.xml'):Array<haxe.macro.Expr.Field>
{
final fields:Array<haxe.macro.Expr.Field> = haxe.macro.Context.getBuildFields();
final cls:haxe.macro.Type.ClassType = haxe.macro.Context.getLocalClass().get();
final pos:haxe.macro.Expr.Position = haxe.macro.Context.currentPos();
final sourcePath:String = haxe.io.Path.directory(haxe.macro.Context.getPosInfos(pos).file);
final absSourcePath:String = haxe.io.Path.removeTrailingSlashes(sys.FileSystem.absolutePath(sourcePath));
final fileToInclude:String = haxe.io.Path.join([absSourcePath, fileName?.length > 0 ? fileName : 'Build.xml']);
if (!sys.FileSystem.exists(fileToInclude))
{
haxe.macro.Context.error('The specified file "$fileToInclude" could not be found at "$absSourcePath".', pos);
}
final includeElement:Xml = Xml.createElement('include');
includeElement.set('name', fileToInclude);
cls.meta.add(':buildXml', [
{
expr: EConst(CString(haxe.xml.Printer.print(includeElement, true))),
pos: pos
}
], pos);
return fields;
}
}

View file

@ -5,7 +5,7 @@ import flixel.FlxBasic;
import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatState;
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
#if android #if android
import funkin.external.android.CallbackUtil; import funkin.mobile.external.android.CallbackUtil;
#end #end
/** /**

View file

@ -0,0 +1,170 @@
<?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

@ -0,0 +1,30 @@
<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

@ -0,0 +1,5 @@
<?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

@ -0,0 +1,5 @@
<?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.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,10 @@
<?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

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

View file

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

Some files were not shown because too many files have changed in this diff Show more