1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-08-31 02:45:13 +00:00

Compare commits

..

33 commits

Author SHA1 Message Date
SrtHero278 b5b336316e include the camera parameter in the comment 2025-07-27 15:47:08 -05:00
SrtHero278 fc0e6712d8 Fix touch pointer positioning when in PlayState 2025-07-27 15:47:08 -05:00
Abnormal d0f0974d1e erm...soggy!!! compile error fixie 2025-07-27 15:46:26 -05:00
Abnormal 369b936056 i love mergy conflicties 2025-07-27 01:09:57 -05:00
TechnikTil c40e1e3958 fix fullscreen bug and show clear save data if logged out of ng 2025-07-27 01:09:57 -05:00
TechnikTil 0ade24bac4 HOPEFULLY FIX EVERYTHING WRONG (this time it was tested between two computers) 2025-07-27 01:09:57 -05:00
TechnikTil 7370ef3249 hopefully fix everything with load from ng 2025-07-27 01:09:57 -05:00
TechnikTil b2f162a8ae move clear save data to the top, add clear ng save data 2025-07-27 01:09:57 -05:00
TechnikTil 11fa9b4050 rewrite NGSaveSlot.load to be async, dont precache assets, error handling 2025-07-27 01:09:57 -05:00
TechnikTil 89bd7a3f43 make newgrounds functions in Save static and only available if newgrounds feature flag is enabled, make sure base save slot is loaded and is not empty, and recover save data in case newgrounds doesnt wanna play nice 2025-07-27 01:09:57 -05:00
TechnikTil 942fb5efc9 Add Save Data Options, also allow user to load and save with Newgrounds Save Slots. 2025-07-27 01:09:57 -05:00
Lasercar c2eff142bd Bunker infiltrated 2025-07-22 14:45:47 -07:00
Eric 2784fa18c0
Merge pull request #5476 from Trofem/api-version-example-mods-for-0.7.0
[CHORE] Updates example_mods's API version up to 0.7.0
2025-07-22 12:03:19 -04:00
Eric ad07fddf89
Merge pull request #5489 from FunkinCrew/main
Update develop to latest main
2025-07-22 11:59:29 -04:00
Eric 2586154376
Merge pull request #5488 from FunkinCrew/develop-v0.7.3
Update main branch to v0.7.3
2025-07-22 11:59:01 -04:00
trofim.al 4768eedd5b api version 0.7.0
Update example mod's API version so it actually loads. part 2.
2025-07-22 15:04:20 +11:00
charlesisfeline 7c675ecf17 the psych confusion continues! 2025-07-22 10:03:25 +08: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
97 changed files with 905 additions and 799 deletions

View file

@ -20,13 +20,13 @@ body:
label: Platform
description: Which platform are you playing on?
options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows
- Itch.io (Downloadable Build) - MacOS
- Itch.io (Downloadable Build) - Linux
- Google Playstore - Android
- App Store - iOS
- Windows (Downloadable Build)
- MacOS (Downloadable Build)
- Linux (Downloadable Build)
- Compiled from GitHub Source Code
validations:
required: true
@ -43,11 +43,23 @@ body:
- Safari
- 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
attributes:
label: Version
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.4
validations:
required: true

View file

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
- type: checkboxes
attributes:
label: Issue Checklist
@ -21,35 +21,25 @@ body:
label: Platform
description: Which platform are you playing on?
options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows
- Itch.io (Downloadable Build) - MacOS
- Itch.io (Downloadable Build) - Linux
- Windows (Downloadable Build)
- MacOS (Downloadable Build)
- Linux (Downloadable Build)
- Compiled from GitHub Source Code
validations:
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
attributes:
label: Version
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
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
validations:
required: true
- type: markdown
attributes:
value: "## Describe the charting issue(s)."

View file

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
- type: checkboxes
attributes:
label: Issue Checklist
@ -22,6 +22,8 @@ body:
label: Platform
description: Which platform are you compiling for?
options:
- Android
- iOS/iPadOS
- Web/HTML5
- Desktop (Windows)
- Desktop (Mac)
@ -33,11 +35,11 @@ body:
- type: input
attributes:
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.
placeholder: ex. 0.6.4
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.7.4
validations:
required: true
- type: markdown
attributes:
value: "## Describe your compiling issue."

View file

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
- type: checkboxes
attributes:
label: Issue Checklist
@ -21,11 +21,13 @@ body:
label: Platform
description: Which platform are you playing on?
options:
- Android
- iOS/iPadOS
- Newgrounds (Web/HTML5)
- Itch.io (Web/HTML5)
- Itch.io (Downloadable Build) - Windows
- Itch.io (Downloadable Build) - MacOS
- Itch.io (Downloadable Build) - Linux
- Windows (Downloadable Build)
- MacOS (Downloadable Build)
- Linux (Downloadable Build)
- Compiled from GitHub Source Code
validations:
required: true
@ -42,11 +44,23 @@ body:
- Safari
- 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
attributes:
label: Version
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
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
validations:
required: true
@ -61,7 +75,7 @@ body:
- type: textarea
attributes:
label: Description
description: Include screenshots or videos of the crash happening. Provide as much detail as you can.
description: Include screenshots or videos of the crash happening. Provide as much detail as you can.
placeholder: Describe the crash here...
validations:
required: true
@ -77,7 +91,7 @@ body:
- type: textarea
attributes:
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...
validations:
required: true

View file

@ -432,7 +432,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)
* @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)
* @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 69c02fa5019f603324a7d2ae362327a1eef9d109

View file

@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
## Requirements
Make sure you're playing:
- the latest version of the game (currently v0.6.4)
- the latest version of the game (currently v0.7.4)
- without any mods
- 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",
"type": "git",
"dir": null,
"ref": "02334589ff9603a5f483077a44395009644f6274",
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
"name": "extension-androidtools",
"type": "haxelib",
"version": "2.2.2"
"version": "2.2.1"
},
{
"name": "extension-haptics",
"type": "haxelib",
"version": "1.0.4"
"version": "1.0.3"
},
{
"name": "extension-iapcore",
"type": "haxelib",
"version": "1.0.4"
"version": "1.0.3"
},
{
"name": "extension-iarcore",
"type": "haxelib",
"version": "1.0.3"
"version": "1.0.2"
},
{
"name": "flixel",
@ -59,7 +59,7 @@
"name": "flxanimate",
"type": "git",
"dir": null,
"ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c",
"ref": "39c1572add28869c558b218fffed13df1b64f376",
"url": "https://github.com/FunkinCrew/flxanimate"
},
{
@ -111,7 +111,7 @@
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
"ref": "4e24283a047f11bded6affabbc9ec405156e026e",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
@ -170,7 +170,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "e5f8c27124598505917a001588b560244731adfb",
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
"url": "https://github.com/FunkinCrew/lime"
},
{
@ -219,7 +219,7 @@
"name": "polymod",
"type": "git",
"dir": null,
"ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b",
"ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1",
"url": "https://github.com/larsiusprime/polymod"
},
{

View file

@ -5,7 +5,6 @@ import hxp.*;
import lime.tools.*;
import sys.FileSystem;
import sys.io.File;
import haxe.io.Bytes;
import haxe.io.Path;
import haxe.ds.Map;
@ -331,10 +330,10 @@ class Project extends HXProject
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS";
/**
* `-DFEATURE_LAG_ADJUSTMENT`
* `-DFEATURE_INPUT_OFFSETS`
* If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets.
*/
static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT";
static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS";
/**
* `-DFEATURE_LOG_TRACE`
@ -501,7 +500,13 @@ class Project extends HXProject
configureHaxelibs();
configureAssets();
configureIcons();
configureASTCTextures();
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
{
@ -824,7 +829,7 @@ class Project extends HXProject
FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile()));
// Should be true except on web builds (some asset stuff breaks it)
FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb());
FEATURE_INPUT_OFFSETS.apply(this, !isWeb());
// Should be true except on web and mobile builds.
// Screenshots doesn't work there, and mobile has its own screenshots anyway.
@ -885,7 +890,7 @@ class Project extends HXProject
if (FEATURE_LOG_TRACE.isDisabled(this))
{
setHaxedef("no-traces");
addHaxeFlag("--no-traces");
}
// Disable the built in pause screen when unfocusing the game.
@ -978,7 +983,7 @@ class Project extends HXProject
function configureAndroid()
{
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java']));
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
if (isRelease())
{
@ -1246,7 +1251,27 @@ class Project extends HXProject
if (isAndroid())
{
// Adaptive icons
adaptiveIcon = new AdaptiveIcon('art/icons/android/', true);
// TODO: Add Adapative Icons in Lime
templatePaths.push("templates");
var androidTheme:String = "@style/LimeAppMainTheme";
if (window.fullscreen != null && window.fullscreen)
{
androidTheme += "Fullscreen";
}
final iconXmlChild:Dynamic = {
"android:label": meta.title,
"android:allowBackup": "true",
"android:theme": androidTheme,
"android:hardwareAccelerated": "true",
"android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null,
"android:largeHeap": "true",
"android:icon": "@mipmap/ic_launcher",
"android:roundIcon": "@mipmap/ic_launcher_round"
};
config.set('android.application', iconXmlChild);
}
else if (isIOS())
{
@ -1278,24 +1303,7 @@ class Project extends HXProject
addIcon("art/icons/icon64.png", 64);
addIcon("art/icons/iconOG.png");
}
info('Done configuring icons.');
}
/**
* Configure the astc textures.
*/
function configureASTCTextures()
{
if (command != "display")
{
readASTCExclusion();
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
{
runASTCCompressor();
}
info('Done configuring icons.');
}
}
@ -1695,8 +1703,7 @@ class Project extends HXProject
*/
public function error(message:String):Void
{
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}'));
Sys.exit(1);
Log.error('${message}');
}
/**
@ -1706,7 +1713,7 @@ class Project extends HXProject
{
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>();
for (line in envFile.split('\n'))
{
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
if (line == "" || line.startsWith("#")) continue;
var index:Int = line.indexOf('=');
var parts = line.split('=');
if (parts.length != 2) continue;
if (index == -1) continue;
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
env.set(field, value);
env.set(parts[0], parts[1]);
}
return env;
}
private function shouldExcludeEnvKey(key:String):Bool
{
final android:Bool = key.startsWith('ANDROID_');
final ios:Bool = key.startsWith('IOS_');
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
final web:Bool = key.startsWith('WEB_');
final desktop:Bool = key.startsWith('DESKTOP_');
if (isWeb() && (mobile || desktop)) return true;
if (isDesktop() && (mobile || web)) return true;
if (isAndroid() && (ios || web || desktop)) return true;
if (isIOS() && (android || web || desktop)) return true;
return false;
}
public function readASTCExclusion():Void
{
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');

View file

@ -29,7 +29,7 @@ class Postbuild
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 final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
class NewgroundsCredentials
{
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
}";
static function main():Void
{
var start:Float = Sys.time();
Sys.println('[INFO] Performing pre-build tasks...');
trace('[PREBUILD] Performing pre-build tasks...');
saveBuildTime();
buildCredsFile();
var end:Float = Sys.time();
var duration:Float = end - start;
Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
trace('[PREBUILD] Finished pre-build tasks in $duration seconds.');
}
static function saveBuildTime():Void
@ -29,4 +41,20 @@ class Prebuild
fo.writeDouble(now);
fo.close();
}
static function buildCredsFile():Void
{
if (sys.FileSystem.exists(NG_CREDS_PATH))
{
trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.');
}
else
{
trace('[PREBUILD] Creating NewgroundsCredentials.hx...');
var fileContents:String = NG_CREDS_TEMPLATE;
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
}
}
}

View file

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

View file

@ -1,6 +1,5 @@
package funkin.api.discord;
import funkin.util.macro.EnvironmentConfigMacro;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types.DiscordButton;
@ -12,7 +11,7 @@ import sys.thread.Thread;
@:nullSafety
class DiscordClient
{
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
static final CLIENT_ID:String = "816168432860790794";
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
@ -41,28 +40,12 @@ class DiscordClient
{
trace('[DISCORD] Initializing connection...');
if (!hasValidCredentials())
{
FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!");
return;
}
@:nullSafety(Off)
{
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
}
// Discord.initialize(CLIENT_ID, handlers, true, null);
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
createDaemon();
}
/**
* @returns `false` if the client ID is invalid.
*/
static function hasValidCredentials():Bool
{
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
}
var daemon:Null<Thread> = null;
function createDaemon():Void

View file

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

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}'
+ (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

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

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

View file

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

View file

@ -1,10 +1,9 @@
package funkin.external.ios;
package funkin.mobile.external.ios;
#if ios
/**
* A Utility class to manage iOS audio.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
@:include('AudioSession.hpp')
@:unreflective
extern class AudioSession
@ -14,4 +13,3 @@ extern class AudioSession
@:native('setActive')
static function setActive(active:Bool):Void;
}
#end

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.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
@:include('ScreenUtil.hpp')
@:unreflective
extern class ScreenUtil
@ -15,4 +14,3 @@ extern class ScreenUtil
@:native('getScreenSize')
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
}
#end

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.
*/
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.

View file

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

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 flixel.math.FlxPoint;
import funkin.util.TouchUtil;
import openfl.utils.Assets;
#if FEATURE_MOBILE_ADVERTISEMENTS
import funkin.mobile.util.AdMobUtil;
#end
@ -101,6 +102,18 @@ class GameOverSubState extends MusicBeatSubState
var canInput:Bool = false;
var justDied:Bool = true;
var isSpecialAnimation:Bool = false;
var gameOverVibrationPreset:VibrationPreset =
{
period: 0,
duration: Constants.DEFAULT_VIBRATION_DURATION,
amplitude: Constants.MIN_VIBRATION_AMPLITUDE,
sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS
};
public function new(params:GameOverParams)
{
super();
@ -185,8 +198,6 @@ class GameOverSubState extends MusicBeatSubState
addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack);
#end
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
// Allow input a second later to prevent accidental gameover skips.
new FlxTimer().start(1, function(tmr:FlxTimer) {
canInput = true;
@ -319,6 +330,9 @@ class GameOverSubState extends MusicBeatSubState
}
}
// Handle vibrations on update.
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
// Start death music before firstDeath gets replaced
super.update(elapsed);
}
@ -601,6 +615,108 @@ class GameOverSubState extends MusicBeatSubState
var hasPlayedDeathQuote:Bool = false;
/**
* Used for death haptics.
*/
var startedTimerHaptics:Bool = false;
/**
* Unique vibrations for each death animation.
*/
function handleAnimationVibrations():Void
{
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
if (justDied)
{
if (isSpecialAnimation)
{
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5);
trace("It's a special game over animation.");
}
else
{
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
}
justDied = false;
}
if (boyfriend.animation == null) return;
final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1;
if (boyfriend.characterId.startsWith("bf"))
{
// BF's mic drops.
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27)
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
// BF's balls pulsating.
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18))
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
return;
}
// Pico dies because of Darnell beating him up.
if (boyfriend.characterId == "pico-blazin")
{
if (!startedTimerHaptics)
{
startedTimerHaptics = true;
new FlxTimer().start(0.5, function(tmr:FlxTimer) {
// Pico falls on his knees.
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
new FlxTimer().start(0.6, function(tmr:FlxTimer) {
// Pico falls "asleep". :)
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
});
});
return;
}
}
else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene")
{
if (isSpecialAnimation)
{
if (startedTimerHaptics) return;
startedTimerHaptics = true;
// Death by Darnell's can.
new FlxTimer().start(1.85, function(tmr:FlxTimer) {
// Pico falls on his knees.
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
});
}
else
{
// Pico falls on his back.
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20)
{
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
}
// Blood firework woohoo!!!!
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0)
{
final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE);
final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION);
HapticUtil.vibrate(0, randomDuration, randomAmplitude);
}
}
return;
}
}
public override function destroy():Void
{
super.destroy();

View file

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

View file

@ -43,7 +43,6 @@ import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
import funkin.play.notes.NoteDirection;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.notes.notekind.NoteKind;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.Strumline;
@ -1062,10 +1061,10 @@ class PlayState extends MusicBeatSubState
// If, after updating the conductor, the instrumental has finished, end the song immediately.
// This helps prevent a major bug where the level suddenly loops back to the start or middle.
// if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
// {
// if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition);
// }
if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
{
if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition);
}
}
var pauseButtonCheck:Bool = false;
@ -1228,17 +1227,19 @@ class PlayState extends MusicBeatSubState
function pause(?mode:PauseMode = Standard):Void
{
if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return;
if (!mayPauseGame || justUnpaused || isGamePaused) return;
switch (mode)
{
case Conversation:
currentConversation.pauseMusic();
preparePauseUI();
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation.pauseMusic());
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
case Cutscene:
VideoCutscene.pauseVideo();
preparePauseUI();
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> VideoCutscene.pauseVideo());
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
default: // also known as standard
if (!isInCountdown || isInCutscene) return;
@ -1296,9 +1297,9 @@ class PlayState extends MusicBeatSubState
#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.skipNextTransOut = true;
pauseSubState.camera = cam;
@ -1499,9 +1500,7 @@ class PlayState extends MusicBeatSubState
musicPausedBySubState = false;
}
// The logic here is that if this sound doesn't auto-destroy
// then it's gonna be reused somewhere, so we just stop it instead.
forEachPausedSound(s -> needsReset ? (s.autoDestroy ? s.destroy() : s.stop()) : s.resume());
forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume());
// Resume camera tweens if we paused any.
for (camTween in cameraTweensPausedBySubState)
@ -2240,13 +2239,6 @@ class PlayState extends MusicBeatSubState
var strumTime:Float = songNote.time;
if (strumTime < startTime) continue; // Skip notes that are before the start time.
var scoreable = true;
if (songNote.kind != null)
{
var noteKind:NoteKind = NoteKindManager.getNoteKind(songNote.kind);
if (noteKind != null) scoreable = noteKind.scoreable;
}
var noteData:Int = songNote.getDirection();
var playerNote:Bool = true;
@ -2257,7 +2249,7 @@ class PlayState extends MusicBeatSubState
case 0:
playerNoteData.push(songNote);
// increment totalNotes for total possible notes able to be hit by the player
if (scoreable) Highscore.tallies.totalNotes++;
Highscore.tallies.totalNotes++;
case 1:
opponentNoteData.push(songNote);
}
@ -2816,13 +2808,14 @@ class PlayState extends MusicBeatSubState
}
// Send the note hit event.
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak,
note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff,
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff,
daRating == 'sick');
dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return;
Highscore.tallies.totalNotesHit++;
// Display the hit on the strums
playerStrumline.hitNote(note, !event.isComboBreak);
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
@ -2830,12 +2823,8 @@ class PlayState extends MusicBeatSubState
vocals.playerVolume = 1;
// Display the combo meter and add the calculation to the score.
if (note.scoreable)
{
Highscore.tallies.totalNotesHit++;
applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak);
popUpScore(event.judgement);
}
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;
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100;
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
@ -743,7 +744,6 @@ class ResultState extends MusicBeatSubState
super.draw();
songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height));
// PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
@ -751,6 +751,105 @@ class ResultState extends MusicBeatSubState
// maskShaderSongName.frameUV = songName.frame.uv;
}
private function handleAnimationVibrations()
{
for (atlas in characterAtlasAnimations)
{
if (atlas == null || atlas.sprite == null) continue;
switch (rank)
{
case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD:
switch (playerCharacterId)
{
// Feel the bed fun :freaky:
case "bf":
if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0)
{
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
// GF slams into the wall.
if (atlas.sprite.anim.curFrame == 51)
{
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
// Pico drop-kicking Nene.
case "pico":
if (atlas.sprite.anim.curFrame == 52)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.GREAT | ScoringRank.EXCELLENT:
switch (playerCharacterId)
{
// Pico explodes the targets with a rocket launcher.
case "pico":
// Pico shoots.
if (atlas.sprite.anim.curFrame == 45)
{
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
// The targets explode.
if (atlas.sprite.anim.curFrame == 50)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.GOOD:
switch (playerCharacterId)
{
// Pico shooting the targets.
case "pico":
if (atlas.sprite.anim.curFrame % 2 != 0) continue;
final frames:Array<Array<Int>> = [[40, 50], [80, 90], [140, 157]];
for (i in 0...frames.length)
{
if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue;
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
case ScoringRank.SHIT:
switch (playerCharacterId)
{
// BF falling and GF slams on BF with her ass.
case "bf":
if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90)
{
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE);
break;
}
default:
break;
}
}
}
}
override function update(elapsed:Float):Void
{
maskShaderDifficulty.swagSprX = difficulty.x;
@ -936,6 +1035,8 @@ class ResultState extends MusicBeatSubState
#end
}
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
super.update(elapsed);
}

View file

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

View file

@ -10,9 +10,6 @@ class NoteSprite extends FunkinSprite
{
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
/**
* The hold note sprite for this note.
*/
public var holdNoteSprite:SustainTrail;
var hsvShader:HSVShader;
@ -98,23 +95,8 @@ class NoteSprite extends FunkinSprite
return this.direction;
}
/**
* The note data associated with this note sprite.
* This is used to store the strum time, length, and other properties.
*/
public var noteData:SongNoteData;
/**
* If this note kind is scoreable (i.e., counted towards score and accuracy)
* Only accessible in scripts
* Defaults to true
*/
public var scoreable:Bool = true;
/**
* Whether this note is a hold note.
* This is true if the length is greater than 0.
*/
public var isHoldNote(get, never):Bool;
function get_isHoldNote():Bool

View file

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

View file

@ -28,13 +28,6 @@ class NoteKind implements INoteScriptedClass
*/
public var params:Array<NoteKindParam>;
/**
* If this note kind is scoreable (ie, counted towards score and accuracy)
* Only accessible in scripts
* Defaults to true
*/
public var scoreable:Bool = true;
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
{
this.noteKind = noteKind;

View file

@ -11,21 +11,7 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam;
class NoteKindManager
{
/**
* A map of all note kinds, keyed by their name.
* This is used to retrieve note kinds by their name.
*/
public static var noteKinds:Map<String, NoteKind> = [];
/**
* Retrieve a note kind by its name.
* @param noteKind The name of the note kind.
* @return The note kind, or null if it doesn't exist.
*/
public static function getNoteKind(noteKind:String):Null<NoteKind>
{
return noteKinds.get(noteKind);
}
static var noteKinds:Map<String, NoteKind> = [];
public static function loadScripts():Void
{

View file

@ -1,7 +1,6 @@
package funkin.play.scoring;
import funkin.save.Save.SaveScoreData;
import funkin.save.Save.SaveScoreTallyData;
/**
* Which system to use when scoring and judging notes.
@ -375,7 +374,8 @@ class Scoring
if (scoreData.tallies.totalNotes == 0) return null;
// Perfect (Platinum) is a Sick Full Clear
if (scoreData.tallies.sick == scoreData.tallies.totalNotes)
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
if (isPerfectGold)
{
return ScoringRank.PERFECT_GOLD;
}
@ -384,21 +384,21 @@ class Scoring
// Final Grade = (Sick + Good - Miss) / (Total Notes)
var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies);
var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes;
if (completionAmount == Constants.RANK_PERFECT_THRESHOLD)
if (grade == Constants.RANK_PERFECT_THRESHOLD)
{
return ScoringRank.PERFECT;
}
else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD)
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
{
return ScoringRank.EXCELLENT;
}
else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD)
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
{
return ScoringRank.GREAT;
}
else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD)
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
{
return ScoringRank.GOOD;
}
@ -407,21 +407,6 @@ class Scoring
return ScoringRank.SHIT;
}
}
/**
* Calculates the "completion" of a song, based on how many GOOD and SICK notes were hit, minus how many were missed
* Top secret funkin crew patented algorithm
* TODO: Could possibly move more of the "tallying" related handling here.
* In FreeplayState we make sure it's clamped between 0 and 1, and we probably always want to assume that?
*
* @param tallies
* @return Float Completion, as a float value between 0 and 1. If `tallies` is `null`, we return 0;
*/
public static function tallyCompletion(?tallies:SaveScoreTallyData):Float
{
if (tallies == null) return 0.0;
return (tallies.sick + tallies.good - tallies.missed) / tallies.totalNotes;
}
}
enum abstract ScoringRank(String)

View file

@ -187,7 +187,6 @@ class Save
theme: ChartEditorTheme.Light,
playtestStartTime: false,
downscroll: false,
showNoteKinds: true,
metronomeVolume: 1.0,
hitsoundVolumePlayer: 1.0,
hitsoundVolumeOpponent: 1.0,
@ -359,23 +358,6 @@ class Save
return data.optionsChartEditor.downscroll;
}
public var chartEditorShowNoteKinds(get, set):Bool;
function get_chartEditorShowNoteKinds():Bool
{
if (data.optionsChartEditor.showNoteKinds == null) data.optionsChartEditor.showNoteKinds = true;
return data.optionsChartEditor.showNoteKinds;
}
function set_chartEditorShowNoteKinds(value:Bool):Bool
{
// Set and apply.
data.optionsChartEditor.showNoteKinds = value;
flush();
return data.optionsChartEditor.showNoteKinds;
}
public var chartEditorPlaytestStartTime(get, set):Bool;
function get_chartEditorPlaytestStartTime():Bool
@ -900,12 +882,14 @@ class Save
return;
}
var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes;
var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes;
// Set the high score and the high rank separately.
var newScore:SaveScoreData =
{
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
tallies: (previousRank > newRank
|| Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies
tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
};
song.set(difficultyId, newScore);
@ -1860,12 +1844,6 @@ typedef SaveDataChartEditorOptions =
*/
var ?downscroll:Bool;
/**
* Show Note Kind Indicator in the Chart Editor.
* @default `true`
*/
var ?showNoteKinds:Bool;
/**
* Metronome volume in the Chart Editor.
* @default `1.0`

View file

@ -21,25 +21,33 @@ class PixelatedIcon extends FlxFilteredSprite
{
var charPath:String = "freeplay/icons/";
final charIDParts:Array<String> = char.split("-");
var iconName:String = "";
for (i in 0...charIDParts.length)
switch (char)
{
iconName += charIDParts[i];
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
{
charPath += '${iconName}pixel';
break;
}
else
{
if (i < charIDParts.length - 1) iconName += '-';
continue;
}
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
charPath += "bfpixel";
case "monster-christmas":
charPath += "monsterpixel";
case "mom" | "mom-car":
charPath += "mommypixel";
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
charPath += "picopixel";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
charPath += "gfpixel";
case "dad":
charPath += "dadpixel";
case "darnell-blazin":
charPath += "darnellpixel";
case "senpai-angry":
charPath += "senpaipixel";
case "spooky-dark":
charPath += "spookypixel";
case "tankman-atlas" | "tankman-bloody":
charPath += "tankmanpixel";
default:
charPath += '${char}pixel';
}
if (!Assets.exists(Paths.image(charPath)))
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
{
trace('[WARN] Character ${char} has no freeplay icon.');
this.visible = false;
@ -50,7 +58,7 @@ class PixelatedIcon extends FlxFilteredSprite
this.visible = true;
}
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
if (isAnimated)
{

View file

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

View file

@ -15,18 +15,17 @@ class CreditsDataHandler
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
#end
#if macro
public static function debugPrint(data:Null<CreditsData>):Void
{
if (data == null)
{
Sys.println('[INFO] CreditsData(NULL)');
trace('CreditsData(NULL)');
return;
}
if (data.entries == null || data.entries.length == 0)
{
Sys.println('[INFO] CreditsData(EMPTY)');
trace('CreditsData(EMPTY)');
return;
}
@ -37,9 +36,8 @@ class CreditsDataHandler
lineCount += entry?.body?.length ?? 0;
}
Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)');
trace('CreditsData($entryCount entries containing $lineCount lines)');
}
#end
/**
* If for some reason the full credits won't load,

View file

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

View file

@ -4,7 +4,6 @@ import flixel.math.FlxPoint;
import flixel.FlxObject;
import flixel.FlxSprite;
import funkin.ui.MusicBeatSubState;
import funkin.ui.FullScreenScaleMode;
import funkin.audio.FunkinSound;
import funkin.ui.TextMenuList;
import funkin.ui.debug.charting.ChartEditorState;
@ -38,7 +37,7 @@ class DebugMenuSubState extends MusicBeatSubState
// Create the green background.
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
menuBG.color = 0xFF4CAF50;
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x));
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
menuBG.updateHitbox();
menuBG.screenCenter();
menuBG.scrollFactor.set(0, 0);

View file

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

View file

@ -94,9 +94,6 @@ import haxe.ui.components.Button;
import haxe.ui.components.DropDown;
import haxe.ui.components.Label;
import haxe.ui.components.Slider;
import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.containers.dialogs.Dialog.DialogButton;
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
import haxe.ui.containers.dialogs.CollapsibleDialog;
import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.menus.MenuBar;
@ -634,11 +631,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return isViewDownscroll;
}
/**
* Whether to show an indicator if a note is of a non-default kind.
*/
var showNoteKindIndicators:Bool = false;
/**
* The current theme used by the editor.
* Dictates the appearance of many UI elements.
@ -1863,11 +1855,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var menubarItemDownscroll:MenuCheckBox;
/**
* The `View -> Note Kind Indicator` menu item.
*/
var menubarItemViewIndicators:MenuCheckBox;
/**
* The `View -> Increase Difficulty` menu item.
*/
@ -2371,7 +2358,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
noteSnapQuantIndex = save.chartEditorNoteQuant;
currentLiveInputStyle = save.chartEditorLiveInputStyle;
isViewDownscroll = save.chartEditorDownscroll;
showNoteKindIndicators = save.chartEditorShowNoteKinds;
playtestStartTime = save.chartEditorPlaytestStartTime;
currentTheme = save.chartEditorTheme;
metronomeVolume = save.chartEditorMetronomeVolume;
@ -2401,7 +2387,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
save.chartEditorNoteQuant = noteSnapQuantIndex;
save.chartEditorLiveInputStyle = currentLiveInputStyle;
save.chartEditorDownscroll = isViewDownscroll;
save.chartEditorShowNoteKinds = showNoteKindIndicators;
save.chartEditorPlaytestStartTime = playtestStartTime;
save.chartEditorTheme = currentTheme;
save.chartEditorMetronomeVolume = metronomeVolume;
@ -2534,7 +2519,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(gridTiledSprite);
gridTiledSprite.zIndex = 10;
gridGhostNote = new ChartEditorNoteSprite(this, true);
gridGhostNote = new ChartEditorNoteSprite(this);
gridGhostNote.alpha = 0.6;
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
gridGhostNote.visible = false;
@ -3104,9 +3089,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
menubarItemDownscroll.selected = isViewDownscroll;
menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected;
menubarItemViewIndicators.selected = showNoteKindIndicators;
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
@ -3939,9 +3921,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectionSquare.width = selectionSquare.height = GRID_SIZE;
selectionSquare.color = FlxColor.RED;
}
// Additional cleanup on notes.
if (noteTooltipsDirty) noteSprite.updateTooltipText();
}
for (eventSprite in renderedEvents.members)
@ -5184,7 +5163,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var variationMetadata:Null<SongMetadata> = songMetadata.get(selectedVariation);
if (variationMetadata != null)
variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
var difficultyToolbox:ChartEditorDifficultyToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
if (difficultyToolbox == null) return;
@ -5620,20 +5599,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
@:nullSafety(Off)
function quitChartEditor():Void
{
if (saveDataDirty)
{
Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true,
function(button:DialogButton) {
if (button == DialogButton.YES)
{
autoSave();
quitChartEditor();
}
});
return;
}
autoSave();
stopWelcomeMusic();
// TODO: PR Flixel to make onComplete nullable.
if (audioInstTrack != null) audioInstTrack.onComplete = null;
@ -5893,9 +5859,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var startTimestamp:Float = 0;
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200%
var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0;
playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5%
playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200%
var targetSong:Song;
try
@ -6330,8 +6296,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
currentScrollEase = Math.max(0, targetScrollPosition);
currentScrollEase = Math.min(currentScrollEase, songLengthInPixels);
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION,
1 / 1000), currentScrollEase, 1 / 1000);
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000);
moveSongToScrollPosition();
}
@ -6366,30 +6331,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION);
// Reapply the volume and playback rate.
var instTargetVolume:Float = (menubarItemVolumeInstrumental.value / 100.0) ?? 1.0;
var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0;
var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0;
var playbackRate = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5%
playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200%
// Reapply the volume.
var instTargetVolume:Float = menubarItemVolumeInstrumental.value / 100.0 ?? 1.0;
var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value / 100.0 ?? 1.0;
var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value / 100.0 ?? 1.0;
if (audioInstTrack != null)
{
audioInstTrack.volume = instTargetVolume;
#if FLX_PITCH
audioInstTrack.pitch = playbackRate;
#end
audioInstTrack.onComplete = null;
}
if (audioVocalTrackGroup != null)
{
audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume;
audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume;
#if FLX_PITCH
audioVocalTrackGroup.pitch = playbackRate;
#end
}
}
@ -6544,11 +6499,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
public function postLoadInstrumental():Void
{
// Reapply the volume and playback rate.
var instTargetVolume:Float = ((menubarItemVolumeInstrumental.value / 100) ?? 1.0);
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5%
playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200%
if (audioInstTrack != null)
{
// Prevent the time from skipping back to 0 when the song ends.
@ -6561,10 +6511,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
audioVocalTrackGroup.pause();
};
audioInstTrack.volume = instTargetVolume;
#if FLX_PITCH
audioInstTrack.pitch = playbackRate;
#end
}
else
{
@ -6581,25 +6527,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
healthIconsDirty = true;
}
public function postLoadVocals():Void
{
// Reapply the volume and playback rate.
var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0;
var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0;
var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0;
playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5%
playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200%
if (audioVocalTrackGroup != null)
{
audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume;
audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume;
#if FLX_PITCH
audioVocalTrackGroup.pitch = playbackRate;
#end
}
}
function hardRefreshOffsetsToolbox():Void
{
var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);

View file

@ -1,7 +1,5 @@
package funkin.ui.debug.charting.components;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxFramesCollection;
@ -12,9 +10,6 @@ import funkin.data.song.SongData.SongNoteData;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.NoteDirection;
import haxe.ui.tooltips.ToolTipRegionOptions;
import funkin.util.HaxeUIUtil;
import haxe.ui.tooltips.ToolTipManager;
/**
* A sprite that can be used to display a note in a chart.
@ -68,21 +63,11 @@ class ChartEditorNoteSprite extends FlxSprite
return overrideData;
}
public var isGhost:Bool = false;
public var tooltip:ToolTipRegionOptions;
/**
* An indicator if the note is a note kind different than Default ("").
*/
public var kindIndicator:FlxText = new FlxText(5, 5, 100, '*', 16);
public function new(parent:ChartEditorState, isGhost:Bool = false)
public function new(parent:ChartEditorState)
{
super();
this.parentState = parent;
this.isGhost = isGhost;
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
@ -104,8 +89,6 @@ class ChartEditorNoteSprite extends FlxSprite
{
addNoteStyleAnimations(fetchNoteStyle(entry));
}
kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
}
static var noteFrameCollection:Null<FlxFramesCollection> = null;
@ -173,7 +156,6 @@ class ChartEditorNoteSprite extends FlxSprite
if (this.noteData == null)
{
this.kill();
updateTooltipPosition();
return this.noteData;
}
@ -185,7 +167,7 @@ class ChartEditorNoteSprite extends FlxSprite
// Update the position to match the note data.
updateNotePosition();
updateTooltipText();
return this.noteData;
}
@ -212,50 +194,6 @@ class ChartEditorNoteSprite extends FlxSprite
this.x += origin.x;
this.y += origin.y;
}
this.updateTooltipPosition();
}
public function updateTooltipText():Void
{
if (this.noteData == null) return;
if (this.isGhost) return;
this.tooltip.tipData = {text: this.noteData.buildTooltip()};
}
public function updateTooltipPosition():Void
{
// No tooltip for ghost sprites.
if (this.isGhost) return;
if (this.noteData == null || (this.tooltip.tipData?.text ?? "").length == 0)
{
// Disable the tooltip.
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
}
else
{
// Update the position.
this.tooltip.left = this.x;
this.tooltip.top = this.y;
this.tooltip.width = this.width;
this.tooltip.height = this.height;
// Enable the tooltip.
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
}
}
override public function draw()
{
super.draw();
if (!parentState.showNoteKindIndicators) return;
if ((this.noteData?.kind ?? "").length == 0) return; // Do not render the note kind indicator if the note kind is default.
kindIndicator.x = this.x;
kindIndicator.y = this.y;
kindIndicator.draw();
}
function get_noteStyle():Null<String>

View file

@ -137,8 +137,6 @@ class ChartEditorAudioHandler
result = playVocals(state, DAD, opponentId, instId);
// if (!result) return false;
state.postLoadVocals();
state.hardRefreshOffsetsToolbox();
state.hardRefreshFreeplayToolbox();

View file

@ -192,7 +192,7 @@ class AssetDataHandler
for (daFrame in obj.frames.frames)
{
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}" 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>";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -268,8 +268,10 @@ class MainMenuState extends MusicBeatState
// reset camera when debug menu is closed
subStateClosed.add(_ -> resetCamStuff(false));
// TODO: Why does this specific function break with null safety?
@:nullSafety(Off)
subStateOpened.add((sub:FlxSubState) -> {
if (Std.isOfType(sub, FreeplayState))
if (Type.getClass(sub) == FreeplayState)
{
new FlxTimer().start(0.5, _ -> {
magenta.visible = false;

View file

@ -545,7 +545,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
if (FlxG.sound.music.time < _lastTime)
{
localConductor.update(FlxG.sound.music.time, !calibrating);
b = localConductor.currentBeatTime;
// Update arrows to be the correct distance away from the receptor.
var lastArrowBeat:Float = 0;
@ -559,7 +558,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
}
if (calibrating)
{
arrowBeat = lastArrowBeat;
arrowBeat = lastArrowBeat + 2;
}
else
arrowBeat = 4;
@ -567,10 +566,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
testStrumline.clean();
testStrumline.noteData = [];
testStrumline.nextNoteIndex = 0;
trace('Restarting conductor');
_lastTime = FlxG.sound.music.time;
return;
}
_lastBeat = b;
@ -613,7 +608,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms';
var toRemove:Array<ArrowData> = [];
var _lastArrowBeat:Float = 0;
// Update arrows
for (i in 0...arrows.length)
{
@ -634,13 +629,12 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
arrow.sprite.alpha -= elapsed * 5;
}
if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0)
if (arrow.sprite.alpha <= 0)
{
toRemove.push(arrow);
arrow.sprite.kill();
continue;
// arrow.debugText.kill();
}
_lastArrowBeat = arrow.beat;
}
// Remove arrows that are marked for removal.

View file

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

View file

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

View file

@ -148,6 +148,6 @@ class NumberPreferenceItem extends TextMenuItem
function toFixed(value:Float):Float
{
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.SwipeUtil;
import funkin.util.TouchUtil;
import openfl.utils.Assets;
import funkin.ui.FullScreenScaleMode;
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;
@ -604,14 +605,12 @@ class StoryMenuState extends MusicBeatState
var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty);
FlxG.camera.fade(FlxColor.BLACK, 0.2, false, function() {
LoadingState.loadPlayState(
{
targetSong: targetSong,
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
targetVariation: targetVariation
}, true);
});
LoadingState.loadPlayState(
{
targetSong: targetSong,
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
targetVariation: targetVariation
}, true);
});
}

View file

@ -228,11 +228,11 @@ class TitleState extends MusicBeatState
{
FlxG.bitmapLog.add(FlxG.camera.buffer);
#if (desktop || android)
#if desktop
// Pressing BACK on the title screen should close the game.
// This lets you exit without leaving fullscreen mode.
// Only applicable on desktop and Android.
if (#if android FlxG.android.justReleased.BACK || #end controls.BACK)
// Only applicable on desktop.
if (controls.BACK)
{
openfl.Lib.application.window.close();
}
@ -282,7 +282,7 @@ class TitleState extends MusicBeatState
if (gamepad != null)
{
if (gamepad.justPressed.START || gamepad.justPressed.ACCEPT) pressedEnter = true;
if (gamepad.justPressed.START) pressedEnter = true;
}
// If you spam Enter, we should skip the transition.

View file

@ -59,21 +59,13 @@ class EnvironmentConfigMacro
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;
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);
envFields.push(parts[0]);
envValues.push(parts[1]);
}
var newFields = fields.copy();
@ -108,27 +100,6 @@ class EnvironmentConfigMacro
return newFields;
}
private static function shouldExcludeKey(key:String):Bool
{
final android:Bool = key.startsWith('ANDROID_');
final ios:Bool = key.startsWith('IOS_');
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
final web:Bool = key.startsWith('WEB_');
final desktop:Bool = key.startsWith('DESKTOP_');
#if html5
if (mobile || desktop) return true;
#elseif desktop
if (mobile || web) return true;
#elseif android
if (ios || web || desktop) return true;
#elseif ios
if (android || web || desktop) return true;
#end
return false;
}
#end
}

View file

@ -26,7 +26,7 @@ class GitCommit
process.close();
Sys.println('[INFO] Git Commit ID: ${commitHashSplice}');
trace('Git Commit ID: ${commitHashSplice}');
// Generates a string expression
return macro $v{commitHashSplice};
@ -56,7 +56,7 @@ class GitCommit
var branchName:String = branchProcess.stdout.readLine();
branchProcess.close();
Sys.println('[INFO] Git Branch Name: ${branchName}');
trace('Git Branch Name: ${branchName}');
// Generates a string expression
return macro $v{branchName};
@ -103,7 +103,7 @@ class GitCommit
throw e;
}
}
Sys.println('[INFO] Git Status Output: ${output}');
trace('Git Status Output: ${output}');
// Generates a string expression
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.MusicBeatSubState;
#if android
import funkin.external.android.CallbackUtil;
import funkin.mobile.external.android.CallbackUtil;
#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;
import openfl.utils.Assets;
import openfl.errors.Error;
import flixel.FlxG;
import flixel.FlxState;

View file

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

View file

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