Compare commits
76 commits
b5b336316e
...
c3a01d09c2
Author | SHA1 | Date | |
---|---|---|---|
|
c3a01d09c2 | ||
|
10af3fa729 | ||
|
e281bdddca | ||
|
19fdda7cd9 | ||
|
d72b989bf5 | ||
|
cd99905bdf | ||
|
3f82f3792b | ||
|
1d8ed5198c | ||
|
8cd3785e3c | ||
|
7e01e7332b | ||
|
c3a837b054 | ||
|
a92567886c | ||
|
4cbfaefc1a | ||
|
705e6b3308 | ||
|
56878befef | ||
|
44cdbb08c4 | ||
|
d6fe54ee79 | ||
|
32c775ef3f | ||
|
1605514424 | ||
|
743e8b13c2 | ||
|
661ddb15f7 | ||
|
3170346b83 | ||
|
e6d737125e | ||
|
3d88fbb11f | ||
|
0d6bc2bedd | ||
|
da5f691865 | ||
|
f63cbf073a | ||
|
30721c3a30 | ||
|
2b1f346097 | ||
|
2dcc528847 | ||
|
5cb33e895c | ||
|
1b91f3b57c | ||
|
e5e3270fef | ||
|
e003a38cab | ||
|
e6fd5d9b12 | ||
|
967397311b | ||
|
542f59ef72 | ||
|
065ee74aea | ||
|
312b5c8812 | ||
|
e9aa6872a3 | ||
|
4f9b63f22c | ||
|
962519e7f0 | ||
|
34bae4a431 | ||
|
79478080f6 | ||
|
65a3ccc8fe | ||
|
4ac851c387 | ||
|
70d5730b70 | ||
|
c527fed70c | ||
|
054a5cd00b | ||
|
19c5de8fe2 | ||
|
dbc26fd621 | ||
|
eb1986b246 | ||
|
d661d93184 | ||
|
4a07d0a347 | ||
|
832f01345f | ||
|
e8d41c938c | ||
|
a94bd7f36c | ||
|
c185f10e65 | ||
|
52f3c63071 | ||
|
e71062b914 | ||
|
3aa6a36b5e | ||
|
a89fa9c121 | ||
|
cb2015f51a | ||
|
139ce3847c | ||
|
87b40c1203 | ||
|
b9852c316b | ||
|
444697620c | ||
|
6abeefb15d | ||
|
530bd1f11d | ||
|
76c0dea92b | ||
|
2d7a2202f5 | ||
|
1f18a3d1f4 | ||
|
0d57f61ffa | ||
|
9fa70775b9 | ||
|
463afb43c0 | ||
|
2833827fbf |
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723
|
||||
Subproject commit 490e97f4c6e673a52ee4f9af98325b1aa2d0c3fe
|
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109
|
||||
Subproject commit a8d15febf5e37a4fc7ffeb0d3eff9c4d36457a37
|
|
@ -6,7 +6,7 @@
|
|||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.5.0",
|
||||
"api_version": "0.7.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.5.0",
|
||||
"api_version": "0.7.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
24
hmm.json
|
@ -18,35 +18,35 @@
|
|||
"name": "extension-admob",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
|
||||
"ref": "02334589ff9603a5f483077a44395009644f6274",
|
||||
"url": "https://github.com/FunkinCrew/extension-admob"
|
||||
},
|
||||
{
|
||||
"name": "extension-androidtools",
|
||||
"type": "haxelib",
|
||||
"version": "2.2.1"
|
||||
"version": "2.2.2"
|
||||
},
|
||||
{
|
||||
"name": "extension-haptics",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.4"
|
||||
},
|
||||
{
|
||||
"name": "extension-iapcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.4"
|
||||
},
|
||||
{
|
||||
"name": "extension-iarcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.2"
|
||||
"version": "1.0.3"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "08fc955ca87f192a971719a675f1d3b21709725d",
|
||||
"url": "https://github.com/FunkinCrew/flixel-mobile"
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
|
@ -59,7 +59,7 @@
|
|||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "39c1572add28869c558b218fffed13df1b64f376",
|
||||
"ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
|
@ -111,7 +111,7 @@
|
|||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "4e24283a047f11bded6affabbc9ec405156e026e",
|
||||
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp"
|
||||
},
|
||||
{
|
||||
|
@ -170,8 +170,8 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
|
||||
"url": "https://github.com/FunkinCrew/lime-mobile"
|
||||
"ref": "e5f8c27124598505917a001588b560244731adfb",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
"name": "mconsole",
|
||||
|
@ -213,13 +213,13 @@
|
|||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131",
|
||||
"url": "https://github.com/FunkinCrew/openfl-mobile"
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1",
|
||||
"ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
|
|
93
project.hxp
|
@ -5,6 +5,7 @@ import hxp.*;
|
|||
import lime.tools.*;
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import haxe.ds.Map;
|
||||
|
||||
|
@ -330,10 +331,10 @@ class Project extends HXProject
|
|||
static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_INPUT_OFFSETS`
|
||||
* `-DFEATURE_LAG_ADJUSTMENT`
|
||||
* If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets.
|
||||
*/
|
||||
static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS";
|
||||
static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_LOG_TRACE`
|
||||
|
@ -500,13 +501,7 @@ class Project extends HXProject
|
|||
configureHaxelibs();
|
||||
configureAssets();
|
||||
configureIcons();
|
||||
|
||||
readASTCExclusion();
|
||||
|
||||
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
|
||||
{
|
||||
runASTCCompressor();
|
||||
}
|
||||
configureASTCTextures();
|
||||
|
||||
if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this))
|
||||
{
|
||||
|
@ -829,7 +824,7 @@ class Project extends HXProject
|
|||
FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile()));
|
||||
|
||||
// Should be true except on web builds (some asset stuff breaks it)
|
||||
FEATURE_INPUT_OFFSETS.apply(this, !isWeb());
|
||||
FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb());
|
||||
|
||||
// Should be true except on web and mobile builds.
|
||||
// Screenshots doesn't work there, and mobile has its own screenshots anyway.
|
||||
|
@ -890,7 +885,7 @@ class Project extends HXProject
|
|||
|
||||
if (FEATURE_LOG_TRACE.isDisabled(this))
|
||||
{
|
||||
addHaxeFlag("--no-traces");
|
||||
setHaxedef("no-traces");
|
||||
}
|
||||
|
||||
// Disable the built in pause screen when unfocusing the game.
|
||||
|
@ -983,7 +978,7 @@ class Project extends HXProject
|
|||
|
||||
function configureAndroid()
|
||||
{
|
||||
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
|
||||
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java']));
|
||||
|
||||
if (isRelease())
|
||||
{
|
||||
|
@ -1251,27 +1246,7 @@ class Project extends HXProject
|
|||
if (isAndroid())
|
||||
{
|
||||
// Adaptive icons
|
||||
// TODO: Add Adapative Icons in Lime
|
||||
templatePaths.push("templates");
|
||||
|
||||
var androidTheme:String = "@style/LimeAppMainTheme";
|
||||
if (window.fullscreen != null && window.fullscreen)
|
||||
{
|
||||
androidTheme += "Fullscreen";
|
||||
}
|
||||
|
||||
final iconXmlChild:Dynamic = {
|
||||
"android:label": meta.title,
|
||||
"android:allowBackup": "true",
|
||||
"android:theme": androidTheme,
|
||||
"android:hardwareAccelerated": "true",
|
||||
"android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null,
|
||||
"android:largeHeap": "true",
|
||||
"android:icon": "@mipmap/ic_launcher",
|
||||
"android:roundIcon": "@mipmap/ic_launcher_round"
|
||||
};
|
||||
|
||||
config.set('android.application', iconXmlChild);
|
||||
adaptiveIcon = new AdaptiveIcon('art/icons/android/', true);
|
||||
}
|
||||
else if (isIOS())
|
||||
{
|
||||
|
@ -1303,7 +1278,24 @@ class Project extends HXProject
|
|||
addIcon("art/icons/icon64.png", 64);
|
||||
addIcon("art/icons/iconOG.png");
|
||||
|
||||
info('Done configuring icons.');
|
||||
}
|
||||
|
||||
info('Done configuring icons.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the astc textures.
|
||||
*/
|
||||
function configureASTCTextures()
|
||||
{
|
||||
if (command != "display")
|
||||
{
|
||||
readASTCExclusion();
|
||||
|
||||
if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this))
|
||||
{
|
||||
runASTCCompressor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1703,7 +1695,8 @@ class Project extends HXProject
|
|||
*/
|
||||
public function error(message:String):Void
|
||||
{
|
||||
Log.error('${message}');
|
||||
Sys.stderr().write(Bytes.ofString('[ERROR] ${message}'));
|
||||
Sys.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1713,7 +1706,7 @@ class Project extends HXProject
|
|||
{
|
||||
if (command != "display")
|
||||
{
|
||||
Log.info('[INFO] ${message}');
|
||||
Sys.println('[INFO] ${message}');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1740,17 +1733,37 @@ class Project extends HXProject
|
|||
var env = new Map<String, Dynamic>();
|
||||
for (line in envFile.split('\n'))
|
||||
{
|
||||
if (line == "" || line.startsWith("#")) continue;
|
||||
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
|
||||
|
||||
var parts = line.split('=');
|
||||
if (parts.length != 2) continue;
|
||||
var index:Int = line.indexOf('=');
|
||||
|
||||
env.set(parts[0], parts[1]);
|
||||
if (index == -1) continue;
|
||||
|
||||
var field:String = line.substr(0, index);
|
||||
var value:String = line.substr(index + 1);
|
||||
|
||||
env.set(field, value);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
private function shouldExcludeEnvKey(key:String):Bool
|
||||
{
|
||||
final android:Bool = key.startsWith('ANDROID_');
|
||||
final ios:Bool = key.startsWith('IOS_');
|
||||
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
|
||||
final web:Bool = key.startsWith('WEB_');
|
||||
final desktop:Bool = key.startsWith('DESKTOP_');
|
||||
|
||||
if (isWeb() && (mobile || desktop)) return true;
|
||||
if (isDesktop() && (mobile || web)) return true;
|
||||
if (isAndroid() && (ios || web || desktop)) return true;
|
||||
if (isIOS() && (android || web || desktop)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function readASTCExclusion():Void
|
||||
{
|
||||
astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n');
|
||||
|
|
|
@ -29,7 +29,7 @@ class Postbuild
|
|||
|
||||
var buildTime:Float = roundToTwoDecimals(end - start);
|
||||
|
||||
trace('Build took: ${buildTime} seconds');
|
||||
Sys.println('[INFO] Build took: ${buildTime} seconds');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,28 +9,16 @@ class Prebuild
|
|||
{
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||
|
||||
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||
|
||||
class NewgroundsCredentials
|
||||
{
|
||||
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||
}";
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
var start:Float = Sys.time();
|
||||
trace('[PREBUILD] Performing pre-build tasks...');
|
||||
Sys.println('[INFO] Performing pre-build tasks...');
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
buildCredsFile();
|
||||
|
||||
var end:Float = Sys.time();
|
||||
var duration:Float = end - start;
|
||||
trace('[PREBUILD] Finished pre-build tasks in $duration seconds.');
|
||||
Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
|
@ -41,20 +29,4 @@ class NewgroundsCredentials
|
|||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
|
||||
static function buildCredsFile():Void
|
||||
{
|
||||
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||
{
|
||||
trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[PREBUILD] Creating NewgroundsCredentials.hx...');
|
||||
|
||||
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||
|
||||
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ class InitState extends FlxState
|
|||
|
||||
#if ios
|
||||
// Setup Audio session
|
||||
funkin.mobile.external.ios.AudioSession.initialize();
|
||||
funkin.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.mobile.external.android.CallbackUtil.init();
|
||||
funkin.external.android.CallbackUtil.init();
|
||||
#end
|
||||
|
||||
//
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.api.discord;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types.DiscordButton;
|
||||
|
@ -11,7 +12,7 @@ import sys.thread.Thread;
|
|||
@:nullSafety
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
@ -40,12 +41,28 @@ class DiscordClient
|
|||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
if (!hasValidCredentials())
|
||||
{
|
||||
FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
}
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `false` if the client ID is invalid.
|
||||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
|
||||
}
|
||||
|
||||
var daemon:Null<Thread> = null;
|
||||
|
||||
function createDaemon():Void
|
||||
|
|
156
source/funkin/api/newgrounds/NGSaveSlot.hx
Normal file
|
@ -0,0 +1,156 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.objects.SaveSlot;
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import funkin.save.Save;
|
||||
|
||||
@:nullSafety
|
||||
@:access(funkin.save.Save)
|
||||
class NGSaveSlot
|
||||
{
|
||||
public static var instance(get, never):NGSaveSlot;
|
||||
static var _instance:Null<NGSaveSlot> = null;
|
||||
|
||||
static function get_instance():NGSaveSlot
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
return loadInstance();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public static function loadInstance():NGSaveSlot
|
||||
{
|
||||
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
|
||||
if (_instance == null) _instance = loadedSave;
|
||||
|
||||
return loadedSave;
|
||||
}
|
||||
|
||||
static function loadSlot(slot:Int):NGSaveSlot
|
||||
{
|
||||
trace('[NEWGROUNDS] Getting save slot from ID $slot');
|
||||
|
||||
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
|
||||
|
||||
var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot);
|
||||
return saveSlotObj;
|
||||
}
|
||||
|
||||
public var ngSaveSlot:Null<SaveSlot> = null;
|
||||
|
||||
public function new(?ngSaveSlot:Null<SaveSlot>)
|
||||
{
|
||||
this.ngSaveSlot = ngSaveSlot;
|
||||
|
||||
#if FLX_DEBUG
|
||||
FlxG.console.registerClass(NGSaveSlot);
|
||||
FlxG.console.registerClass(Save);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves `data` to the newgrounds save slot.
|
||||
* @param data The raw save data.
|
||||
*/
|
||||
public function save(data:RawSaveData):Void
|
||||
{
|
||||
var encodedData:String = haxe.Serializer.run(data);
|
||||
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function load(?onComplete:Null<Dynamic->Void>, ?onError:Null<CallError->Void>):Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(value):
|
||||
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Save Slot Data:');
|
||||
trace(value);
|
||||
#end
|
||||
|
||||
if (onComplete != null && value != null)
|
||||
{
|
||||
var decodedData:Dynamic = haxe.Unserializer.run(value);
|
||||
onComplete(decodedData);
|
||||
}
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(RESPONSE({message: error, code: 500}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function clear():Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully cleared save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSlot():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
|
||||
|
||||
trace(' Is null? ${ngSaveSlot == null}');
|
||||
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
import funkin.save.Save;
|
||||
import funkin.api.newgrounds.Medals.Medal;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
@ -10,13 +11,18 @@ import io.newgrounds.NGLite.LoginOutcome;
|
|||
import io.newgrounds.NGLite.LoginFail;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
import io.newgrounds.objects.User;
|
||||
|
||||
@:nullSafety
|
||||
class NewgroundsClient
|
||||
{
|
||||
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
|
||||
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
|
||||
|
||||
public static var instance(get, never):NewgroundsClient;
|
||||
|
||||
static var _instance:Null<NewgroundsClient> = null;
|
||||
|
||||
static function get_instance():NewgroundsClient
|
||||
|
@ -29,14 +35,15 @@ class NewgroundsClient
|
|||
public var user(get, never):Null<User>;
|
||||
public var medals(get, never):Null<MedalList>;
|
||||
public var leaderboards(get, never):Null<ScoreBoardList>;
|
||||
public var saveSlots(get, never):Null<SaveSlotList>;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[NEWGROUNDS] Initializing client...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
|
||||
trace('[NEWGROUNDS] App ID: ${APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
|
||||
#end
|
||||
|
||||
if (!hasValidCredentials())
|
||||
|
@ -45,9 +52,12 @@ class NewgroundsClient
|
|||
return;
|
||||
}
|
||||
|
||||
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
|
||||
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
|
||||
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
|
||||
|
||||
NG.core.setupEncryption(ENCRYPTION_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
public function init()
|
||||
|
@ -166,12 +176,12 @@ class NewgroundsClient
|
|||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(NewgroundsCredentials.APP_ID == null
|
||||
|| NewgroundsCredentials.APP_ID == ""
|
||||
|| NewgroundsCredentials.APP_ID.contains(" ")
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
|
||||
return !(APP_ID == null
|
||||
|| APP_ID == ""
|
||||
|| (APP_ID != null && APP_ID.contains(" "))
|
||||
|| ENCRYPTION_KEY == null
|
||||
|| ENCRYPTION_KEY == ""
|
||||
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
|
||||
}
|
||||
|
||||
function onLoginResolved(outcome:LoginOutcome):Void
|
||||
|
@ -236,6 +246,8 @@ class NewgroundsClient
|
|||
|
||||
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||
trace('[NEWGROUNDS] Submitting save slot request...');
|
||||
NG.core.saveSlots.loadList(onFetchedSaveSlots);
|
||||
}
|
||||
|
||||
function onLoginFailed(result:LoginFail):Void
|
||||
|
@ -301,6 +313,13 @@ class NewgroundsClient
|
|||
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||
}
|
||||
|
||||
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched save slots!');
|
||||
|
||||
NGSaveSlot.instance.checkSlot();
|
||||
}
|
||||
|
||||
function get_user():Null<User>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
|
@ -319,6 +338,12 @@ class NewgroundsClient
|
|||
return NG.core.scoreBoards;
|
||||
}
|
||||
|
||||
function get_saveSlots():Null<SaveSlotList>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.saveSlots;
|
||||
}
|
||||
|
||||
static function getSessionId():Null<String>
|
||||
{
|
||||
#if js
|
||||
|
|
|
@ -1117,6 +1117,23 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
|
||||
public function buildTooltip():String
|
||||
{
|
||||
if ((this.kind?.length ?? 0) == 0) return "";
|
||||
|
||||
var result:String = 'Kind: ${this.kind}';
|
||||
if (this.params.length == 0) return result;
|
||||
|
||||
result += "\nParams:";
|
||||
|
||||
for (param in params)
|
||||
{
|
||||
result += '\n- ${param.name}: ${param.value}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.mobile.external.android;
|
||||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
||||
import flixel.util.FlxSignal;
|
||||
|
||||
|
@ -9,7 +10,6 @@ import flixel.util.FlxSignal;
|
|||
@:unreflective
|
||||
class CallbackUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* The result code for `DATA_FOLDER_CLOSED` activity.
|
||||
*/
|
||||
|
@ -42,7 +42,6 @@ class CallbackUtil
|
|||
initCallBackJNI(new CallbackHandler());
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,8 +49,7 @@ class CallbackUtil
|
|||
*/
|
||||
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
|
||||
{
|
||||
#if android
|
||||
@:allow(funkin.mobile.external.android.CallbackUtil)
|
||||
@:allow(funkin.external.android.CallbackUtil)
|
||||
function new():Void {}
|
||||
|
||||
/**
|
||||
|
@ -68,5 +66,5 @@ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
|
|||
{
|
||||
if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,12 +1,12 @@
|
|||
package funkin.mobile.external.android;
|
||||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
/**
|
||||
* A Utility class to manage the Application's Data folder on Android.
|
||||
*/
|
||||
@:unreflective
|
||||
class DataFolderUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* Opens the data folder on an Android device using JNI.
|
||||
*/
|
||||
|
@ -19,5 +19,5 @@ class DataFolderUtil
|
|||
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,4 +1,4 @@
|
|||
package funkin.mobile.external.android;
|
||||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.mobile.external.android;
|
||||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.JNI;
|
||||
|
||||
|
@ -9,7 +10,6 @@ import lime.system.JNI;
|
|||
@:unreflective
|
||||
class ScreenUtil
|
||||
{
|
||||
#if android
|
||||
/**
|
||||
* Retrieves the dimensions of display cutouts (such as notches) on Android devices.
|
||||
*
|
||||
|
@ -48,5 +48,5 @@ class ScreenUtil
|
|||
|
||||
return [];
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
|
@ -1,9 +1,10 @@
|
|||
package funkin.mobile.external.ios;
|
||||
package funkin.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to manage iOS audio.
|
||||
*/
|
||||
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('AudioSession.hpp')
|
||||
@:unreflective
|
||||
extern class AudioSession
|
||||
|
@ -13,3 +14,4 @@ extern class AudioSession
|
|||
@:native('setActive')
|
||||
static function setActive(active:Bool):Void;
|
||||
}
|
||||
#end
|
|
@ -1,9 +1,10 @@
|
|||
package funkin.mobile.external.ios;
|
||||
package funkin.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to get iOS screen related informations.
|
||||
*/
|
||||
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('ScreenUtil.hpp')
|
||||
@:unreflective
|
||||
extern class ScreenUtil
|
||||
|
@ -14,3 +15,4 @@ extern class ScreenUtil
|
|||
@:native('getScreenSize')
|
||||
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
|
||||
}
|
||||
#end
|
|
@ -1,37 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ class AdMobUtil
|
|||
/**
|
||||
* AdMob publisher ID used for the application.
|
||||
*/
|
||||
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER");
|
||||
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER");
|
||||
|
||||
/**
|
||||
* Test ad unit IDs for development and testing purposes.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package funkin.mobile.util;
|
||||
|
||||
#if ios
|
||||
import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil;
|
||||
import funkin.external.ios.ScreenUtil as NativeScreenUtil;
|
||||
#elseif android
|
||||
import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil;
|
||||
import funkin.external.android.ScreenUtil as NativeScreenUtil;
|
||||
#end
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.System;
|
||||
|
|
54
source/funkin/modding/ModStore.hx
Normal file
|
@ -0,0 +1,54 @@
|
|||
package funkin.modding;
|
||||
|
||||
import haxe.ds.StringMap;
|
||||
|
||||
/**
|
||||
* Temporary persistent data storage for mods to use.
|
||||
*/
|
||||
@:nullSafety
|
||||
class ModStore
|
||||
{
|
||||
/**
|
||||
* All registered stores for this session.
|
||||
*/
|
||||
public static final stores:StringMap<Dynamic> = new StringMap<Dynamic>();
|
||||
|
||||
/**
|
||||
* Attempts to register a new store with the given ID and return it.
|
||||
* If a store with the same ID already exists, that store will be returned instead (discards `data`).
|
||||
*
|
||||
* @id The unique ID for this store.
|
||||
* @data Optional initial data, uses an empty object by default.
|
||||
* @return The store data at the given ID.
|
||||
*/
|
||||
public static function register(id:String, ?data:Dynamic):Dynamic
|
||||
{
|
||||
if (stores.exists(id)) return stores.get(id);
|
||||
stores.set(id, data ??= {});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a store by ID.
|
||||
*
|
||||
* @id The target ID of the store.
|
||||
* @return The store data, or `null` if the store did not exist.
|
||||
*/
|
||||
public static function get(id:String):Null<Dynamic>
|
||||
{
|
||||
return stores.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to remove a store by ID and return it.
|
||||
*
|
||||
* @id The target ID of the store.
|
||||
* @return The store data, or `null` if the store did not exist.
|
||||
*/
|
||||
public static function remove(id:String):Null<Dynamic>
|
||||
{
|
||||
var data:Null<Dynamic> = stores.get(id);
|
||||
stores.remove(id);
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ import funkin.util.MathUtil;
|
|||
import funkin.effects.RetroCameraFade;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.util.TouchUtil;
|
||||
import openfl.utils.Assets;
|
||||
#if FEATURE_MOBILE_ADVERTISEMENTS
|
||||
import funkin.mobile.util.AdMobUtil;
|
||||
#end
|
||||
|
@ -102,18 +101,6 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
var canInput:Bool = false;
|
||||
|
||||
var justDied:Bool = true;
|
||||
|
||||
var isSpecialAnimation:Bool = false;
|
||||
|
||||
var gameOverVibrationPreset:VibrationPreset =
|
||||
{
|
||||
period: 0,
|
||||
duration: Constants.DEFAULT_VIBRATION_DURATION,
|
||||
amplitude: Constants.MIN_VIBRATION_AMPLITUDE,
|
||||
sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS
|
||||
};
|
||||
|
||||
public function new(params:GameOverParams)
|
||||
{
|
||||
super();
|
||||
|
@ -198,6 +185,8 @@ class GameOverSubState extends MusicBeatSubState
|
|||
addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack);
|
||||
#end
|
||||
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
|
||||
// Allow input a second later to prevent accidental gameover skips.
|
||||
new FlxTimer().start(1, function(tmr:FlxTimer) {
|
||||
canInput = true;
|
||||
|
@ -330,9 +319,6 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
// Handle vibrations on update.
|
||||
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
|
||||
|
||||
// Start death music before firstDeath gets replaced
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
@ -615,108 +601,6 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
var hasPlayedDeathQuote:Bool = false;
|
||||
|
||||
/**
|
||||
* Used for death haptics.
|
||||
*/
|
||||
var startedTimerHaptics:Bool = false;
|
||||
|
||||
/**
|
||||
* Unique vibrations for each death animation.
|
||||
*/
|
||||
function handleAnimationVibrations():Void
|
||||
{
|
||||
if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return;
|
||||
|
||||
if (justDied)
|
||||
{
|
||||
if (isSpecialAnimation)
|
||||
{
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5);
|
||||
trace("It's a special game over animation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
}
|
||||
justDied = false;
|
||||
}
|
||||
|
||||
if (boyfriend.animation == null) return;
|
||||
|
||||
final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1;
|
||||
if (boyfriend.characterId.startsWith("bf"))
|
||||
{
|
||||
// BF's mic drops.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27)
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
// BF's balls pulsating.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18))
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Pico dies because of Darnell beating him up.
|
||||
if (boyfriend.characterId == "pico-blazin")
|
||||
{
|
||||
if (!startedTimerHaptics)
|
||||
{
|
||||
startedTimerHaptics = true;
|
||||
|
||||
new FlxTimer().start(0.5, function(tmr:FlxTimer) {
|
||||
// Pico falls on his knees.
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
|
||||
new FlxTimer().start(0.6, function(tmr:FlxTimer) {
|
||||
// Pico falls "asleep". :)
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene")
|
||||
{
|
||||
if (isSpecialAnimation)
|
||||
{
|
||||
if (startedTimerHaptics) return;
|
||||
|
||||
startedTimerHaptics = true;
|
||||
|
||||
// Death by Darnell's can.
|
||||
new FlxTimer().start(1.85, function(tmr:FlxTimer) {
|
||||
// Pico falls on his knees.
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pico falls on his back.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20)
|
||||
{
|
||||
HapticUtil.vibrateByPreset(gameOverVibrationPreset);
|
||||
}
|
||||
|
||||
// Blood firework woohoo!!!!
|
||||
if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0)
|
||||
{
|
||||
final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE);
|
||||
final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION);
|
||||
|
||||
HapticUtil.vibrate(0, randomDuration, randomAmplitude);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
|
|
@ -213,6 +213,11 @@ class PauseSubState extends MusicBeatSubState
|
|||
*/
|
||||
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
|
||||
|
||||
/**
|
||||
* Callback that gets called once substate gets open.
|
||||
*/
|
||||
var onPause:Void->Void;
|
||||
|
||||
// ===============
|
||||
// Audio Variables
|
||||
// ===============
|
||||
|
@ -222,10 +227,11 @@ class PauseSubState extends MusicBeatSubState
|
|||
// Constructor
|
||||
// ===============
|
||||
|
||||
public function new(?params:PauseSubStateParams)
|
||||
public function new(?params:PauseSubStateParams, ?onPause:Void->Void)
|
||||
{
|
||||
super();
|
||||
this.currentMode = params?.mode ?? Standard;
|
||||
this.onPause = onPause;
|
||||
}
|
||||
|
||||
// ===============
|
||||
|
@ -244,6 +250,8 @@ class PauseSubState extends MusicBeatSubState
|
|||
AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT);
|
||||
#end
|
||||
|
||||
if (onPause != null) onPause();
|
||||
|
||||
super.create();
|
||||
|
||||
startPauseMusic();
|
||||
|
@ -286,6 +294,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
hapticTimer.cancel();
|
||||
hapticTimer = null;
|
||||
pauseMusic.stop();
|
||||
onPause = null;
|
||||
}
|
||||
|
||||
// ===============
|
||||
|
@ -444,7 +453,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
offsetText.y = FlxG.height - (offsetText.height + offsetText.height + 40);
|
||||
offsetTextInfo.y = offsetText.y + offsetText.height + 4;
|
||||
|
||||
#if !mobile
|
||||
#if (!mobile && FEATURE_LAG_ADJUSTMENT)
|
||||
metadata.add(offsetText);
|
||||
metadata.add(offsetTextInfo);
|
||||
#end
|
||||
|
|
|
@ -43,6 +43,7 @@ import funkin.play.cutscene.VanillaCutscenes;
|
|||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.notes.notekind.NoteKind;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.Strumline;
|
||||
|
@ -714,8 +715,6 @@ class PlayState extends MusicBeatSubState
|
|||
#if mobile
|
||||
// Force allowScreenTimeout to be disabled
|
||||
lime.system.System.allowScreenTimeout = false;
|
||||
// TODO: For some reason the touch pointer's positioning gets weird in playstate, find a way to fix it! -Zack
|
||||
funkin.util.plugins.TouchPointerPlugin.enabled = false;
|
||||
#end
|
||||
|
||||
// This state receives update() even when a substate is active.
|
||||
|
@ -1063,10 +1062,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;
|
||||
|
@ -1229,19 +1228,17 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
function pause(?mode:PauseMode = Standard):Void
|
||||
{
|
||||
if (!mayPauseGame || justUnpaused || isGamePaused) return;
|
||||
if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Conversation:
|
||||
currentConversation.pauseMusic();
|
||||
preparePauseUI();
|
||||
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
|
||||
openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation.pauseMusic());
|
||||
|
||||
case Cutscene:
|
||||
VideoCutscene.pauseVideo();
|
||||
preparePauseUI();
|
||||
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene);
|
||||
openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> VideoCutscene.pauseVideo());
|
||||
|
||||
default: // also known as standard
|
||||
if (!isInCountdown || isInCutscene) return;
|
||||
|
@ -1299,9 +1296,9 @@ class PlayState extends MusicBeatSubState
|
|||
#end
|
||||
}
|
||||
|
||||
function openPauseSubState(mode:PauseMode, cam:FlxCamera):Void
|
||||
function openPauseSubState(mode:PauseMode, cam:FlxCamera, ?onPause:Void->Void):Void
|
||||
{
|
||||
final pauseSubState = new PauseSubState({mode: mode});
|
||||
final pauseSubState = new PauseSubState({mode: mode}, onPause);
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
pauseSubState.camera = cam;
|
||||
|
@ -1502,7 +1499,9 @@ class PlayState extends MusicBeatSubState
|
|||
musicPausedBySubState = false;
|
||||
}
|
||||
|
||||
forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume());
|
||||
// The logic here is that if this sound doesn't auto-destroy
|
||||
// then it's gonna be reused somewhere, so we just stop it instead.
|
||||
forEachPausedSound(s -> needsReset ? (s.autoDestroy ? s.destroy() : s.stop()) : s.resume());
|
||||
|
||||
// Resume camera tweens if we paused any.
|
||||
for (camTween in cameraTweensPausedBySubState)
|
||||
|
@ -1761,7 +1760,6 @@ class PlayState extends MusicBeatSubState
|
|||
#if mobile
|
||||
// Syncing allowScreenTimeout with Preferences option.
|
||||
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
|
||||
funkin.util.plugins.TouchPointerPlugin.enabled = true;
|
||||
#end
|
||||
|
||||
#if !mobile
|
||||
|
@ -2242,6 +2240,13 @@ class PlayState extends MusicBeatSubState
|
|||
var strumTime:Float = songNote.time;
|
||||
if (strumTime < startTime) continue; // Skip notes that are before the start time.
|
||||
|
||||
var scoreable = true;
|
||||
if (songNote.kind != null)
|
||||
{
|
||||
var noteKind:NoteKind = NoteKindManager.getNoteKind(songNote.kind);
|
||||
if (noteKind != null) scoreable = noteKind.scoreable;
|
||||
}
|
||||
|
||||
var noteData:Int = songNote.getDirection();
|
||||
var playerNote:Bool = true;
|
||||
|
||||
|
@ -2252,7 +2257,7 @@ class PlayState extends MusicBeatSubState
|
|||
case 0:
|
||||
playerNoteData.push(songNote);
|
||||
// increment totalNotes for total possible notes able to be hit by the player
|
||||
Highscore.tallies.totalNotes++;
|
||||
if (scoreable) Highscore.tallies.totalNotes++;
|
||||
case 1:
|
||||
opponentNoteData.push(songNote);
|
||||
}
|
||||
|
@ -2811,14 +2816,13 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
// Send the note hit event.
|
||||
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff,
|
||||
var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak,
|
||||
note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff,
|
||||
daRating == 'sick');
|
||||
dispatchEvent(event);
|
||||
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
// Display the hit on the strums
|
||||
playerStrumline.hitNote(note, !event.isComboBreak);
|
||||
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
|
||||
|
@ -2826,8 +2830,12 @@ class PlayState extends MusicBeatSubState
|
|||
vocals.playerVolume = 1;
|
||||
|
||||
// Display the combo meter and add the calculation to the score.
|
||||
applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak);
|
||||
popUpScore(event.judgement);
|
||||
if (note.scoreable)
|
||||
{
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak);
|
||||
popUpScore(event.judgement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -527,8 +527,7 @@ class ResultState extends MusicBeatSubState
|
|||
bgFlash.visible = true;
|
||||
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
||||
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good
|
||||
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
|
||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100;
|
||||
clearPercentTarget = Math.floor(clearPercentFloat);
|
||||
// Prevent off-by-one errors.
|
||||
|
||||
|
@ -744,6 +743,7 @@ class ResultState extends MusicBeatSubState
|
|||
super.draw();
|
||||
|
||||
songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
|
||||
clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height));
|
||||
|
||||
// PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
|
||||
|
||||
|
@ -751,105 +751,6 @@ class ResultState extends MusicBeatSubState
|
|||
// maskShaderSongName.frameUV = songName.frame.uv;
|
||||
}
|
||||
|
||||
private function handleAnimationVibrations()
|
||||
{
|
||||
for (atlas in characterAtlasAnimations)
|
||||
{
|
||||
if (atlas == null || atlas.sprite == null) continue;
|
||||
|
||||
switch (rank)
|
||||
{
|
||||
case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Feel the bed fun :freaky:
|
||||
case "bf":
|
||||
if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
// GF slams into the wall.
|
||||
if (atlas.sprite.anim.curFrame == 51)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pico drop-kicking Nene.
|
||||
case "pico":
|
||||
if (atlas.sprite.anim.curFrame == 52)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.GREAT | ScoringRank.EXCELLENT:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Pico explodes the targets with a rocket launcher.
|
||||
case "pico":
|
||||
// Pico shoots.
|
||||
if (atlas.sprite.anim.curFrame == 45)
|
||||
{
|
||||
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
break;
|
||||
}
|
||||
|
||||
// The targets explode.
|
||||
if (atlas.sprite.anim.curFrame == 50)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.GOOD:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// Pico shooting the targets.
|
||||
case "pico":
|
||||
if (atlas.sprite.anim.curFrame % 2 != 0) continue;
|
||||
|
||||
final frames:Array<Array<Int>> = [[40, 50], [80, 90], [140, 157]];
|
||||
for (i in 0...frames.length)
|
||||
{
|
||||
if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue;
|
||||
|
||||
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case ScoringRank.SHIT:
|
||||
switch (playerCharacterId)
|
||||
{
|
||||
// BF falling and GF slams on BF with her ass.
|
||||
case "bf":
|
||||
if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90)
|
||||
{
|
||||
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||
|
@ -1035,8 +936,6 @@ class ResultState extends MusicBeatSubState
|
|||
#end
|
||||
}
|
||||
|
||||
if (HapticUtil.hapticsAvailable) handleAnimationVibrations();
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
|
|
|
@ -288,33 +288,22 @@ class CharacterDataParser
|
|||
{
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
|
||||
switch (char)
|
||||
final charIDParts:Array<String> = char.split("-");
|
||||
var iconName:String = "";
|
||||
for (i in 0...charIDParts.length)
|
||||
{
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas" | "tankman-bloody":
|
||||
charPath += "tankmanpixel";
|
||||
case "pico-christmas" | "pico-dark":
|
||||
charPath += "picopixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
iconName += charIDParts[i];
|
||||
|
||||
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
|
||||
{
|
||||
charPath += '${iconName}pixel';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < charIDParts.length - 1) iconName += '-';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
|
|
|
@ -10,6 +10,9 @@ class NoteSprite extends FunkinSprite
|
|||
{
|
||||
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
|
||||
|
||||
/**
|
||||
* The hold note sprite for this note.
|
||||
*/
|
||||
public var holdNoteSprite:SustainTrail;
|
||||
|
||||
var hsvShader:HSVShader;
|
||||
|
@ -95,8 +98,23 @@ class NoteSprite extends FunkinSprite
|
|||
return this.direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The note data associated with this note sprite.
|
||||
* This is used to store the strum time, length, and other properties.
|
||||
*/
|
||||
public var noteData:SongNoteData;
|
||||
|
||||
/**
|
||||
* If this note kind is scoreable (i.e., counted towards score and accuracy)
|
||||
* Only accessible in scripts
|
||||
* Defaults to true
|
||||
*/
|
||||
public var scoreable:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether this note is a hold note.
|
||||
* This is true if the length is greater than 0.
|
||||
*/
|
||||
public var isHoldNote(get, never):Bool;
|
||||
|
||||
function get_isHoldNote():Bool
|
||||
|
|
|
@ -17,6 +17,7 @@ import funkin.play.notes.NoteVibrationsHandler;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.GRhythmUtil;
|
||||
import funkin.play.notes.notekind.NoteKind;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import flixel.math.FlxPoint;
|
||||
#if mobile
|
||||
|
@ -1103,6 +1104,7 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
if (noteSprite != null)
|
||||
{
|
||||
var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind);
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||
noteSprite.setupNoteGraphic(noteKindStyle);
|
||||
|
||||
|
@ -1127,6 +1129,7 @@ class Strumline extends FlxSpriteGroup
|
|||
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
||||
noteSprite.x -= NUDGE;
|
||||
noteSprite.y = -9999;
|
||||
if (noteKind != null) noteSprite.scoreable = noteKind.scoreable;
|
||||
}
|
||||
|
||||
return noteSprite;
|
||||
|
|
|
@ -28,6 +28,13 @@ class NoteKind implements INoteScriptedClass
|
|||
*/
|
||||
public var params:Array<NoteKindParam>;
|
||||
|
||||
/**
|
||||
* If this note kind is scoreable (ie, counted towards score and accuracy)
|
||||
* Only accessible in scripts
|
||||
* Defaults to true
|
||||
*/
|
||||
public var scoreable:Bool = true;
|
||||
|
||||
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
|
||||
{
|
||||
this.noteKind = noteKind;
|
||||
|
|
|
@ -11,7 +11,21 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam;
|
|||
|
||||
class NoteKindManager
|
||||
{
|
||||
static var noteKinds:Map<String, NoteKind> = [];
|
||||
/**
|
||||
* A map of all note kinds, keyed by their name.
|
||||
* This is used to retrieve note kinds by their name.
|
||||
*/
|
||||
public static var noteKinds:Map<String, NoteKind> = [];
|
||||
|
||||
/**
|
||||
* Retrieve a note kind by its name.
|
||||
* @param noteKind The name of the note kind.
|
||||
* @return The note kind, or null if it doesn't exist.
|
||||
*/
|
||||
public static function getNoteKind(noteKind:String):Null<NoteKind>
|
||||
{
|
||||
return noteKinds.get(noteKind);
|
||||
}
|
||||
|
||||
public static function loadScripts():Void
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.play.scoring;
|
||||
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.save.Save.SaveScoreTallyData;
|
||||
|
||||
/**
|
||||
* Which system to use when scoring and judging notes.
|
||||
|
@ -374,8 +375,7 @@ class Scoring
|
|||
if (scoreData.tallies.totalNotes == 0) return null;
|
||||
|
||||
// Perfect (Platinum) is a Sick Full Clear
|
||||
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
|
||||
if (isPerfectGold)
|
||||
if (scoreData.tallies.sick == scoreData.tallies.totalNotes)
|
||||
{
|
||||
return ScoringRank.PERFECT_GOLD;
|
||||
}
|
||||
|
@ -384,21 +384,21 @@ class Scoring
|
|||
|
||||
// Final Grade = (Sick + Good - Miss) / (Total Notes)
|
||||
|
||||
var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes;
|
||||
var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies);
|
||||
|
||||
if (grade == Constants.RANK_PERFECT_THRESHOLD)
|
||||
if (completionAmount == Constants.RANK_PERFECT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.PERFECT;
|
||||
}
|
||||
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
|
||||
else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.EXCELLENT;
|
||||
}
|
||||
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
|
||||
else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.GREAT;
|
||||
}
|
||||
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
|
||||
else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD)
|
||||
{
|
||||
return ScoringRank.GOOD;
|
||||
}
|
||||
|
@ -407,6 +407,21 @@ class Scoring
|
|||
return ScoringRank.SHIT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the "completion" of a song, based on how many GOOD and SICK notes were hit, minus how many were missed
|
||||
* Top secret funkin crew patented algorithm
|
||||
* TODO: Could possibly move more of the "tallying" related handling here.
|
||||
* In FreeplayState we make sure it's clamped between 0 and 1, and we probably always want to assume that?
|
||||
*
|
||||
* @param tallies
|
||||
* @return Float Completion, as a float value between 0 and 1. If `tallies` is `null`, we return 0;
|
||||
*/
|
||||
public static function tallyCompletion(?tallies:SaveScoreTallyData):Float
|
||||
{
|
||||
if (tallies == null) return 0.0;
|
||||
return (tallies.sick + tallies.good - tallies.missed) / tallies.totalNotes;
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract ScoringRank(String)
|
||||
|
|
|
@ -187,6 +187,7 @@ class Save
|
|||
theme: ChartEditorTheme.Light,
|
||||
playtestStartTime: false,
|
||||
downscroll: false,
|
||||
showNoteKinds: true,
|
||||
metronomeVolume: 1.0,
|
||||
hitsoundVolumePlayer: 1.0,
|
||||
hitsoundVolumeOpponent: 1.0,
|
||||
|
@ -358,6 +359,23 @@ class Save
|
|||
return data.optionsChartEditor.downscroll;
|
||||
}
|
||||
|
||||
public var chartEditorShowNoteKinds(get, set):Bool;
|
||||
|
||||
function get_chartEditorShowNoteKinds():Bool
|
||||
{
|
||||
if (data.optionsChartEditor.showNoteKinds == null) data.optionsChartEditor.showNoteKinds = true;
|
||||
|
||||
return data.optionsChartEditor.showNoteKinds;
|
||||
}
|
||||
|
||||
function set_chartEditorShowNoteKinds(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsChartEditor.showNoteKinds = value;
|
||||
flush();
|
||||
return data.optionsChartEditor.showNoteKinds;
|
||||
}
|
||||
|
||||
public var chartEditorPlaytestStartTime(get, set):Bool;
|
||||
|
||||
function get_chartEditorPlaytestStartTime():Bool
|
||||
|
@ -882,14 +900,12 @@ class Save
|
|||
return;
|
||||
}
|
||||
|
||||
var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes;
|
||||
var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes;
|
||||
|
||||
// Set the high score and the high rank separately.
|
||||
var newScore:SaveScoreData =
|
||||
{
|
||||
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
|
||||
tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
|
||||
tallies: (previousRank > newRank
|
||||
|| Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies
|
||||
};
|
||||
|
||||
song.set(difficultyId, newScore);
|
||||
|
@ -1356,6 +1372,45 @@ class Save
|
|||
{
|
||||
FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...');
|
||||
}
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
public static function saveToNewgrounds():Void
|
||||
{
|
||||
if (_instance == null) return;
|
||||
trace('[SAVE] Saving Save Data to Newgrounds...');
|
||||
funkin.api.newgrounds.NGSaveSlot.instance.save(_instance.data);
|
||||
}
|
||||
|
||||
public static function loadFromNewgrounds(onFinish:Void->Void):Void
|
||||
{
|
||||
trace('[SAVE] Loading Save Data from Newgrounds...');
|
||||
funkin.api.newgrounds.NGSaveSlot.instance.load(function(data:Dynamic) {
|
||||
FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH);
|
||||
|
||||
if (FlxG.save.status != EMPTY)
|
||||
{
|
||||
// best i can do in case the NG file is corrupted or something along those lines
|
||||
var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data);
|
||||
trace('[SAVE] Backed up current save data in case of emergency to $backupSlot!');
|
||||
}
|
||||
|
||||
FlxG.save.erase();
|
||||
FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); // forces regeneration of the file as erase deletes it
|
||||
|
||||
var gameSave = SaveDataMigrator.migrate(data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
_instance = gameSave;
|
||||
onFinish();
|
||||
}, function(error:io.newgrounds.Call.CallError) {
|
||||
var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error);
|
||||
|
||||
var msg = 'There was an error loading your save data from Newgrounds.';
|
||||
msg += '\n${errorMsg}';
|
||||
msg += '\nAre you sure you are connected to the internet?';
|
||||
lime.app.Application.current.window.alert(msg, "Newgrounds Save Slot Failure");
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1805,6 +1860,12 @@ typedef SaveDataChartEditorOptions =
|
|||
*/
|
||||
var ?downscroll:Bool;
|
||||
|
||||
/**
|
||||
* Show Note Kind Indicator in the Chart Editor.
|
||||
* @default `true`
|
||||
*/
|
||||
var ?showNoteKinds:Bool;
|
||||
|
||||
/**
|
||||
* Metronome volume in the Chart Editor.
|
||||
* @default `1.0`
|
||||
|
|
|
@ -21,33 +21,25 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||
{
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
switch (char)
|
||||
final charIDParts:Array<String> = char.split("-");
|
||||
var iconName:String = "";
|
||||
for (i in 0...charIDParts.length)
|
||||
{
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas" | "tankman-bloody":
|
||||
charPath += "tankmanpixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
iconName += charIDParts[i];
|
||||
|
||||
if (Assets.exists(Paths.image(charPath + '${iconName}pixel')))
|
||||
{
|
||||
charPath += '${iconName}pixel';
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < charIDParts.length - 1) iconName += '-';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
this.visible = false;
|
||||
|
@ -58,7 +50,7 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||
this.visible = true;
|
||||
}
|
||||
|
||||
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
|
|
|
@ -271,6 +271,12 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
nametag.midpointX += cutoutSize;
|
||||
add(nametag);
|
||||
|
||||
@:privateAccess
|
||||
{
|
||||
nametag.midpointY += 200;
|
||||
FlxTween.tween(nametag, {midpointY: nametag.midpointY - 200}, 1, {ease: FlxEase.expoOut});
|
||||
}
|
||||
|
||||
nametag.scrollFactor.set();
|
||||
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"]));
|
||||
|
@ -739,6 +745,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
FlxTween.tween(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
|
||||
|
||||
FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(nametag, {y: nametag.y + 80}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 0.8, {ease: FlxEase.backIn});
|
||||
FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn});
|
||||
|
|
|
@ -15,17 +15,18 @@ class CreditsDataHandler
|
|||
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||
#end
|
||||
|
||||
#if macro
|
||||
public static function debugPrint(data:Null<CreditsData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
trace('CreditsData(NULL)');
|
||||
Sys.println('[INFO] CreditsData(NULL)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.entries == null || data.entries.length == 0)
|
||||
{
|
||||
trace('CreditsData(EMPTY)');
|
||||
Sys.println('[INFO] CreditsData(EMPTY)');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,8 +37,9 @@ class CreditsDataHandler
|
|||
lineCount += entry?.body?.length ?? 0;
|
||||
}
|
||||
|
||||
trace('CreditsData($entryCount entries containing $lineCount lines)');
|
||||
Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)');
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* If for some reason the full credits won't load,
|
||||
|
|
|
@ -10,7 +10,7 @@ class CreditsDataMacro
|
|||
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||
{
|
||||
#if !display
|
||||
trace('Hardcoding credits data...');
|
||||
Sys.println('[INFO] Hardcoding credits data...');
|
||||
var json = CreditsDataMacro.fetchJSON();
|
||||
|
||||
if (json == null)
|
||||
|
|
|
@ -4,6 +4,7 @@ import flixel.math.FlxPoint;
|
|||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.ui.TextMenuList;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
|
@ -37,7 +38,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
// Create the green background.
|
||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||
menuBG.color = 0xFF4CAF50;
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x));
|
||||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
menuBG.scrollFactor.set(0, 0);
|
||||
|
|
|
@ -100,6 +100,7 @@ class DebugBoundingState extends FlxState
|
|||
offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
|
||||
|
||||
offsetEditorDialog.cameras = [hudCam];
|
||||
offsetEditorDialog.closable = false;
|
||||
|
||||
add(offsetEditorDialog);
|
||||
offsetEditorDialog.showDialog(false);
|
||||
|
|
|
@ -94,6 +94,9 @@ import haxe.ui.components.Button;
|
|||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
|
@ -631,6 +634,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return isViewDownscroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show an indicator if a note is of a non-default kind.
|
||||
*/
|
||||
var showNoteKindIndicators:Bool = false;
|
||||
|
||||
/**
|
||||
* The current theme used by the editor.
|
||||
* Dictates the appearance of many UI elements.
|
||||
|
@ -1855,6 +1863,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var menubarItemDownscroll:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `View -> Note Kind Indicator` menu item.
|
||||
*/
|
||||
var menubarItemViewIndicators:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `View -> Increase Difficulty` menu item.
|
||||
*/
|
||||
|
@ -2358,6 +2371,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
noteSnapQuantIndex = save.chartEditorNoteQuant;
|
||||
currentLiveInputStyle = save.chartEditorLiveInputStyle;
|
||||
isViewDownscroll = save.chartEditorDownscroll;
|
||||
showNoteKindIndicators = save.chartEditorShowNoteKinds;
|
||||
playtestStartTime = save.chartEditorPlaytestStartTime;
|
||||
currentTheme = save.chartEditorTheme;
|
||||
metronomeVolume = save.chartEditorMetronomeVolume;
|
||||
|
@ -2387,6 +2401,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
save.chartEditorNoteQuant = noteSnapQuantIndex;
|
||||
save.chartEditorLiveInputStyle = currentLiveInputStyle;
|
||||
save.chartEditorDownscroll = isViewDownscroll;
|
||||
save.chartEditorShowNoteKinds = showNoteKindIndicators;
|
||||
save.chartEditorPlaytestStartTime = playtestStartTime;
|
||||
save.chartEditorTheme = currentTheme;
|
||||
save.chartEditorMetronomeVolume = metronomeVolume;
|
||||
|
@ -2519,7 +2534,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
add(gridTiledSprite);
|
||||
gridTiledSprite.zIndex = 10;
|
||||
|
||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||
gridGhostNote = new ChartEditorNoteSprite(this, true);
|
||||
gridGhostNote.alpha = 0.6;
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []);
|
||||
gridGhostNote.visible = false;
|
||||
|
@ -3089,6 +3104,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
|
||||
menubarItemDownscroll.selected = isViewDownscroll;
|
||||
|
||||
menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected;
|
||||
menubarItemViewIndicators.selected = showNoteKindIndicators;
|
||||
|
||||
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
|
||||
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
|
||||
|
||||
|
@ -3921,6 +3939,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
selectionSquare.width = selectionSquare.height = GRID_SIZE;
|
||||
selectionSquare.color = FlxColor.RED;
|
||||
}
|
||||
|
||||
// Additional cleanup on notes.
|
||||
if (noteTooltipsDirty) noteSprite.updateTooltipText();
|
||||
}
|
||||
|
||||
for (eventSprite in renderedEvents.members)
|
||||
|
@ -5163,7 +5184,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;
|
||||
|
@ -5599,7 +5620,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
@:nullSafety(Off)
|
||||
function quitChartEditor():Void
|
||||
{
|
||||
autoSave();
|
||||
if (saveDataDirty)
|
||||
{
|
||||
Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true,
|
||||
function(button:DialogButton) {
|
||||
if (button == DialogButton.YES)
|
||||
{
|
||||
autoSave();
|
||||
quitChartEditor();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stopWelcomeMusic();
|
||||
// TODO: PR Flixel to make onComplete nullable.
|
||||
if (audioInstTrack != null) audioInstTrack.onComplete = null;
|
||||
|
@ -5859,9 +5893,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
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 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 targetSong:Song;
|
||||
try
|
||||
|
@ -6296,7 +6330,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
currentScrollEase = Math.max(0, targetScrollPosition);
|
||||
currentScrollEase = Math.min(currentScrollEase, songLengthInPixels);
|
||||
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000);
|
||||
scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION,
|
||||
1 / 1000), currentScrollEase, 1 / 1000);
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
|
@ -6331,20 +6366,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION);
|
||||
|
||||
// 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;
|
||||
// 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%
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6499,6 +6544,11 @@ 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.
|
||||
|
@ -6511,6 +6561,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
audioVocalTrackGroup.pause();
|
||||
};
|
||||
audioInstTrack.volume = instTargetVolume;
|
||||
#if FLX_PITCH
|
||||
audioInstTrack.pitch = playbackRate;
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -6527,6 +6581,25 @@ 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);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
|
@ -10,6 +12,9 @@ import funkin.data.song.SongData.SongNoteData;
|
|||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import haxe.ui.tooltips.ToolTipRegionOptions;
|
||||
import funkin.util.HaxeUIUtil;
|
||||
import haxe.ui.tooltips.ToolTipManager;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a note in a chart.
|
||||
|
@ -63,11 +68,21 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
return overrideData;
|
||||
}
|
||||
|
||||
public function new(parent:ChartEditorState)
|
||||
public var isGhost:Bool = false;
|
||||
public var tooltip:ToolTipRegionOptions;
|
||||
|
||||
/**
|
||||
* An indicator if the note is a note kind different than Default ("").
|
||||
*/
|
||||
public var kindIndicator:FlxText = new FlxText(5, 5, 100, '*', 16);
|
||||
|
||||
public function new(parent:ChartEditorState, isGhost:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
this.parentState = parent;
|
||||
this.isGhost = isGhost;
|
||||
this.tooltip = HaxeUIUtil.buildTooltip('N/A');
|
||||
|
||||
var entries:Array<String> = NoteStyleRegistry.instance.listEntryIds();
|
||||
|
||||
|
@ -89,6 +104,8 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
{
|
||||
addNoteStyleAnimations(fetchNoteStyle(entry));
|
||||
}
|
||||
|
||||
kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
}
|
||||
|
||||
static var noteFrameCollection:Null<FlxFramesCollection> = null;
|
||||
|
@ -156,6 +173,7 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
if (this.noteData == null)
|
||||
{
|
||||
this.kill();
|
||||
updateTooltipPosition();
|
||||
return this.noteData;
|
||||
}
|
||||
|
||||
|
@ -167,7 +185,7 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
|
||||
// Update the position to match the note data.
|
||||
updateNotePosition();
|
||||
|
||||
updateTooltipText();
|
||||
return this.noteData;
|
||||
}
|
||||
|
||||
|
@ -194,6 +212,50 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
this.updateTooltipPosition();
|
||||
}
|
||||
|
||||
public function updateTooltipText():Void
|
||||
{
|
||||
if (this.noteData == null) return;
|
||||
if (this.isGhost) return;
|
||||
this.tooltip.tipData = {text: this.noteData.buildTooltip()};
|
||||
}
|
||||
|
||||
public function updateTooltipPosition():Void
|
||||
{
|
||||
// No tooltip for ghost sprites.
|
||||
if (this.isGhost) return;
|
||||
|
||||
if (this.noteData == null || (this.tooltip.tipData?.text ?? "").length == 0)
|
||||
{
|
||||
// Disable the tooltip.
|
||||
ToolTipManager.instance.unregisterTooltipRegion(this.tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the position.
|
||||
this.tooltip.left = this.x;
|
||||
this.tooltip.top = this.y;
|
||||
this.tooltip.width = this.width;
|
||||
this.tooltip.height = this.height;
|
||||
|
||||
// Enable the tooltip.
|
||||
ToolTipManager.instance.registerTooltipRegion(this.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
override public function draw()
|
||||
{
|
||||
super.draw();
|
||||
|
||||
if (!parentState.showNoteKindIndicators) return;
|
||||
if ((this.noteData?.kind ?? "").length == 0) return; // Do not render the note kind indicator if the note kind is default.
|
||||
|
||||
kindIndicator.x = this.x;
|
||||
kindIndicator.y = this.y;
|
||||
kindIndicator.draw();
|
||||
}
|
||||
|
||||
function get_noteStyle():Null<String>
|
||||
|
|
|
@ -137,6 +137,8 @@ class ChartEditorAudioHandler
|
|||
result = playVocals(state, DAD, opponentId, instId);
|
||||
// if (!result) return false;
|
||||
|
||||
state.postLoadVocals();
|
||||
|
||||
state.hardRefreshOffsetsToolbox();
|
||||
|
||||
state.hardRefreshFreeplayToolbox();
|
||||
|
|
|
@ -192,7 +192,7 @@ class AssetDataHandler
|
|||
|
||||
for (daFrame in obj.frames.frames)
|
||||
{
|
||||
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}"/>\n';
|
||||
xml += ' <SubTexture name="${daFrame.name}" x="${daFrame.frame.x}" y="${daFrame.frame.y}" width="${daFrame.frame.width}" height="${daFrame.frame.height}" frameX="${- daFrame.offset.x}" frameY="${- daFrame.offset.y}" frameWidth="${daFrame.sourceSize.x}" frameHeight="${daFrame.sourceSize.y}" flipX="${daFrame.flipX}" flipY="${daFrame.flipY}" rotated="${daFrame.angle == -90}"/>\n';
|
||||
}
|
||||
|
||||
xml += "</TextureAtlas>";
|
||||
|
|
|
@ -81,8 +81,6 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case Intro:
|
||||
|
@ -185,6 +183,8 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
default:
|
||||
// I shit myself.
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
function onFinishAnim(name:String):Void
|
||||
|
|
|
@ -31,6 +31,7 @@ import funkin.input.Controls;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.scoring.Scoring.ScoringRank;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.save.Save;
|
||||
|
@ -119,6 +120,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
*/
|
||||
public static final SONGS_POS_MULTI:Float = 0.75;
|
||||
|
||||
/**
|
||||
* For positioning the difficulty dots.
|
||||
*/
|
||||
public static final DEFAULT_DOTS_GROUP_POS:Array<Int> = [260, 170];
|
||||
|
||||
var songs:Array<Null<FreeplaySongData>> = [];
|
||||
|
||||
var curSelected:Int = 0;
|
||||
|
@ -169,17 +175,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
return grpCapsules.members[curSelected];
|
||||
}
|
||||
|
||||
var coolColors:Array<Int> = [
|
||||
0xFF9271FD,
|
||||
0xFF9271FD,
|
||||
0xFF223344,
|
||||
0xFF941653,
|
||||
0xFFFC96D7,
|
||||
0xFFA0D1FF,
|
||||
0xFFFF78BF,
|
||||
0xFFF6B604
|
||||
];
|
||||
|
||||
var grpCapsules:FlxTypedGroup<SongMenuItem>;
|
||||
|
||||
var dj:Null<FreeplayDJ> = null;
|
||||
|
@ -315,7 +310,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
grpCapsules = new FlxTypedGroup<SongMenuItem>();
|
||||
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
|
||||
|
||||
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(203, 170);
|
||||
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(DEFAULT_DOTS_GROUP_POS[0], DEFAULT_DOTS_GROUP_POS[1]);
|
||||
letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75);
|
||||
rankBg = new FunkinSprite(0, 0);
|
||||
rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette'));
|
||||
|
@ -1270,7 +1265,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
|
||||
new FlxTimer().start(2, _ -> {
|
||||
// dj.fistPump();
|
||||
prepForNewRank = false;
|
||||
});
|
||||
}
|
||||
|
@ -1295,9 +1289,22 @@ class FreeplayState extends MusicBeatSubState
|
|||
function refreshDots(amount:Int, index:Int, prevIndex:Int):Void
|
||||
{
|
||||
var distance:Int = 30;
|
||||
var groupOffset:Float = 14.7;
|
||||
var shiftAmt:Float = (distance * amount) / 2;
|
||||
var daSong:Null<FreeplaySongData> = currentCapsule.freeplayData;
|
||||
final maxDotsPerRow:Int = 8;
|
||||
|
||||
if (difficultyDots.group.members.length > maxDotsPerRow)
|
||||
{
|
||||
difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (maxDotsPerRow - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (difficultyDots.group.members.length - 1);
|
||||
}
|
||||
|
||||
var curRow:Int = 0;
|
||||
var curDot:Int = 0;
|
||||
for (i in 0...difficultyDots.group.members.length)
|
||||
{
|
||||
// if (difficultyDots.group.members[i] == null) continue;
|
||||
|
@ -1329,7 +1336,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
difficultyDots.group.members[i].visible = true;
|
||||
difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - shiftAmt);
|
||||
difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * curDot)) - shiftAmt);
|
||||
difficultyDots.group.members[i].y = DEFAULT_DOTS_GROUP_POS[1] + distance * curRow;
|
||||
|
||||
curDot++;
|
||||
|
||||
if (curDot >= maxDotsPerRow)
|
||||
{
|
||||
curDot = 0;
|
||||
curRow++;
|
||||
}
|
||||
|
||||
if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false)
|
||||
{
|
||||
|
@ -1582,12 +1598,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong();
|
||||
|
||||
if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected);
|
||||
|
||||
if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1);
|
||||
|
||||
calculateCompletion();
|
||||
lerpScoreDisplays();
|
||||
|
||||
handleInputs(elapsed);
|
||||
|
||||
|
@ -1597,7 +1611,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
|
||||
}
|
||||
|
||||
function calculateCompletion():Void
|
||||
function lerpScoreDisplays():Void
|
||||
{
|
||||
lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, FlxG.elapsed, 0.2), intendedScore, 1);
|
||||
lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100);
|
||||
|
@ -2179,7 +2193,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
*/
|
||||
function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void
|
||||
{
|
||||
if (!controls.active) return;
|
||||
if (!controls.active && !force) return;
|
||||
|
||||
if (capsuleAnim)
|
||||
{
|
||||
|
@ -2275,11 +2289,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore == null ? 0.0 : Math.max(0,
|
||||
((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes));
|
||||
intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies));
|
||||
rememberedDifficulty = currentDifficulty;
|
||||
if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
|
||||
currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
|
||||
currentCapsule.refreshDisplay(!prepForNewRank);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2390,11 +2403,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
trace('RANDOM SELECTED');
|
||||
|
||||
controls.active = false;
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
letterSort.inputEnabled = false;
|
||||
#end
|
||||
|
||||
var availableSongCapsules:Array<SongMenuItem> = grpCapsules.members.filter(function(cap:SongMenuItem) {
|
||||
// Dead capsules are ones which were removed from the list when changing filters.
|
||||
return cap.alive && cap.freeplayData != null;
|
||||
|
@ -2420,6 +2428,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
// Seeing if I can do an animation...
|
||||
curSelected = grpCapsules.members.indexOf(targetSong);
|
||||
changeSelection(0); // Trigger an update.
|
||||
controls.active = false;
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
letterSort.inputEnabled = false;
|
||||
#end
|
||||
|
||||
// Act like we hit Confirm on that song.
|
||||
capsuleOnConfirmDefault(targetSong);
|
||||
|
@ -2587,26 +2599,28 @@ class FreeplayState extends MusicBeatSubState
|
|||
new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) {
|
||||
FunkinSound.emptyPartialQueue();
|
||||
|
||||
Paths.setCurrentLevel(cap?.freeplayData?.levelId);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: currentDifficulty,
|
||||
targetVariation: currentVariation,
|
||||
targetInstrumental: targetInstId,
|
||||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
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,
|
||||
|
||||
#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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2654,6 +2668,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat);
|
||||
capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI);
|
||||
if (index + 0.5 < curSelectedFloat) capsule.targetPos.y -= 100;
|
||||
}
|
||||
|
||||
if (curSelected != prevSelected)
|
||||
|
@ -2693,26 +2708,18 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
|
||||
var daSongCapsule:SongMenuItem = currentCapsule;
|
||||
if (daSongCapsule.freeplayData != null)
|
||||
{
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation);
|
||||
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);
|
||||
}
|
||||
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();
|
||||
else
|
||||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
rememberedSongId = null;
|
||||
albumRoll.albumId = null;
|
||||
changeDiff();
|
||||
daSongCapsule.refreshDisplay();
|
||||
}
|
||||
currentCapsule.refreshDisplay(!prepForNewRank);
|
||||
|
||||
for (index => capsule in grpCapsules.members)
|
||||
{
|
||||
|
@ -2725,16 +2732,15 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
capsule.targetPos.y = capsule.intendedY(index - curSelected);
|
||||
capsule.targetPos.x = capsule.intendedX(index - curSelected) + (CUTOUT_WIDTH * SONGS_POS_MULTI);
|
||||
if (index < curSelected #if FEATURE_TOUCH_CONTROLS
|
||||
&& ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure
|
||||
if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure
|
||||
}
|
||||
|
||||
if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active)
|
||||
{
|
||||
playCurSongPreview(daSongCapsule);
|
||||
playCurSongPreview(currentCapsule);
|
||||
currentCapsule.selected = true;
|
||||
|
||||
// switchBackingImage(daSongCapsule.freeplayData);
|
||||
// switchBackingImage(currentCapsule.freeplayData);
|
||||
}
|
||||
|
||||
// Small vibrations every selection change.
|
||||
|
|
|
@ -148,18 +148,22 @@ class LetterSort extends FlxSpriteGroup
|
|||
|
||||
public function changeSelection(diff:Int = 0, playSound:Bool = true):Void
|
||||
{
|
||||
doLetterChangeAnims(diff);
|
||||
@:privateAccess
|
||||
if (instance.controls.active)
|
||||
{
|
||||
doLetterChangeAnims(diff);
|
||||
|
||||
var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
|
||||
var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
|
||||
|
||||
// if we're moving left (diff < 0), we want control of the right arrow, and vice versa
|
||||
var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
|
||||
arrowToMove.offset.x = 3 * multiPosOrNeg;
|
||||
// if we're moving left (diff < 0), we want control of the right arrow, and vice versa
|
||||
var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
|
||||
arrowToMove.offset.x = 3 * multiPosOrNeg;
|
||||
|
||||
new FlxTimer().start(2 / 24, function(_) {
|
||||
arrowToMove.offset.x = 0;
|
||||
});
|
||||
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
new FlxTimer().start(2 / 24, function(_) {
|
||||
arrowToMove.offset.x = 0;
|
||||
});
|
||||
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,8 +49,6 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
public var fakeRanking:FreeplayRank;
|
||||
|
||||
var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect", "perfectsick"];
|
||||
|
||||
public var targetPos:FlxPoint = new FlxPoint();
|
||||
public var doLerp:Bool = false;
|
||||
public var doJumpIn:Bool = false;
|
||||
|
@ -69,12 +67,9 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
public var newText:FlxSprite;
|
||||
|
||||
// public var weekType:FlxSprite;
|
||||
public var bigNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
public var smallNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
public var weekNumbers:Array<CapsuleNumber> = [];
|
||||
var difficultyNumbers:Array<CapsuleNumber> = []; // referred to as "bignumbers" in the .fla file!
|
||||
var bpmNumbers:Array<CapsuleNumber> = []; // referred to as "smallnumbers" in the .fla file!
|
||||
var weekNumbers:Array<CapsuleNumber> = [];
|
||||
|
||||
var impactThing:FunkinSprite;
|
||||
|
||||
|
@ -131,18 +126,18 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
for (i in 0...2)
|
||||
{
|
||||
var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
|
||||
add(bigNumber);
|
||||
var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0);
|
||||
add(num);
|
||||
|
||||
bigNumbers.push(bigNumber);
|
||||
difficultyNumbers.push(num);
|
||||
}
|
||||
|
||||
for (i in 0...3)
|
||||
{
|
||||
var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
|
||||
add(smallNumber);
|
||||
var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0);
|
||||
add(num);
|
||||
|
||||
smallNumbers.push(smallNumber);
|
||||
bpmNumbers.push(num);
|
||||
}
|
||||
|
||||
// doesn't get added, simply is here to help with visibility of things for the pop in!
|
||||
|
@ -334,38 +329,38 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
shiftX = 186;
|
||||
}
|
||||
|
||||
for (i in 0...smallNumbers.length)
|
||||
for (i in 0...bpmNumbers.length)
|
||||
{
|
||||
smallNumbers[i].x = this.x + (shiftX + (i * 11));
|
||||
bpmNumbers[i].x = this.x + (shiftX + (i * 11));
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (newBPM < 100)
|
||||
{
|
||||
smallNumbers[i].digit = 0;
|
||||
bpmNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
smallNumbers[i].digit = Math.floor(newBPM / 100) % 10;
|
||||
bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10;
|
||||
}
|
||||
|
||||
case 1:
|
||||
if (newBPM < 10)
|
||||
{
|
||||
smallNumbers[i].digit = 0;
|
||||
bpmNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
smallNumbers[i].digit = Math.floor(newBPM / 10) % 10;
|
||||
bpmNumbers[i].digit = Math.floor(newBPM / 10) % 10;
|
||||
|
||||
if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4;
|
||||
}
|
||||
case 2:
|
||||
smallNumbers[i].digit = newBPM % 10;
|
||||
bpmNumbers[i].digit = newBPM % 10;
|
||||
default:
|
||||
trace('why the fuck is this being called');
|
||||
}
|
||||
smallNumbers[i].x += tempShift;
|
||||
bpmNumbers[i].x += tempShift;
|
||||
}
|
||||
// diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
|
||||
// diffRatingSprite.visible = false;
|
||||
|
@ -439,21 +434,21 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
{
|
||||
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
|
||||
|
||||
for (i in 0...bigNumbers.length)
|
||||
for (i in 0...difficultyNumbers.length)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (newRating < 10)
|
||||
{
|
||||
bigNumbers[i].digit = 0;
|
||||
difficultyNumbers[i].digit = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bigNumbers[i].digit = Math.floor(newRating / 10);
|
||||
difficultyNumbers[i].digit = Math.floor(newRating / 10);
|
||||
}
|
||||
case 1:
|
||||
bigNumbers[i].digit = newRating % 10;
|
||||
difficultyNumbers[i].digit = newRating % 10;
|
||||
default:
|
||||
trace('why the fuck is this being called');
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import funkin.modding.IScriptedClass.IStateChangingScriptedClass;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.util.BitmapUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* A class for the backing cards so they dont have to be part of freeplayState......
|
||||
|
|
|
@ -10,7 +10,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
|||
import funkin.modding.events.ScriptEvent;
|
||||
import openfl.display.BlendMode;
|
||||
import funkin.util.BitmapUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class NewCharacterCard extends BackingCard
|
||||
{
|
||||
|
|
|
@ -268,10 +268,8 @@ class MainMenuState extends MusicBeatState
|
|||
// reset camera when debug menu is closed
|
||||
subStateClosed.add(_ -> resetCamStuff(false));
|
||||
|
||||
// TODO: Why does this specific function break with null safety?
|
||||
@:nullSafety(Off)
|
||||
subStateOpened.add((sub:FlxSubState) -> {
|
||||
if (Type.getClass(sub) == FreeplayState)
|
||||
if (Std.isOfType(sub, FreeplayState))
|
||||
{
|
||||
new FlxTimer().start(0.5, _ -> {
|
||||
magenta.visible = false;
|
||||
|
|
|
@ -545,6 +545,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
if (FlxG.sound.music.time < _lastTime)
|
||||
{
|
||||
localConductor.update(FlxG.sound.music.time, !calibrating);
|
||||
b = localConductor.currentBeatTime;
|
||||
|
||||
// Update arrows to be the correct distance away from the receptor.
|
||||
var lastArrowBeat:Float = 0;
|
||||
|
@ -558,7 +559,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
}
|
||||
if (calibrating)
|
||||
{
|
||||
arrowBeat = lastArrowBeat + 2;
|
||||
arrowBeat = lastArrowBeat;
|
||||
}
|
||||
else
|
||||
arrowBeat = 4;
|
||||
|
@ -566,6 +567,10 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
testStrumline.clean();
|
||||
testStrumline.noteData = [];
|
||||
testStrumline.nextNoteIndex = 0;
|
||||
trace('Restarting conductor');
|
||||
|
||||
_lastTime = FlxG.sound.music.time;
|
||||
return;
|
||||
}
|
||||
|
||||
_lastBeat = b;
|
||||
|
@ -608,7 +613,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms';
|
||||
|
||||
var toRemove:Array<ArrowData> = [];
|
||||
|
||||
var _lastArrowBeat:Float = 0;
|
||||
// Update arrows
|
||||
for (i in 0...arrows.length)
|
||||
{
|
||||
|
@ -629,12 +634,13 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
arrow.sprite.alpha -= elapsed * 5;
|
||||
}
|
||||
|
||||
if (arrow.sprite.alpha <= 0)
|
||||
if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0)
|
||||
{
|
||||
toRemove.push(arrow);
|
||||
arrow.sprite.kill();
|
||||
// arrow.debugText.kill();
|
||||
continue;
|
||||
}
|
||||
_lastArrowBeat = arrow.beat;
|
||||
}
|
||||
|
||||
// Remove arrows that are marked for removal.
|
||||
|
|
|
@ -69,10 +69,11 @@ class OptionsState extends MusicBeatState
|
|||
optionsCodex = new Codex<OptionsMenuPageName>(Options);
|
||||
add(optionsCodex);
|
||||
|
||||
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu());
|
||||
var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu());
|
||||
var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData));
|
||||
var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu());
|
||||
var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu());
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu());
|
||||
#end
|
||||
|
||||
|
@ -81,9 +82,10 @@ class OptionsState extends MusicBeatState
|
|||
options.onExit.add(exitToMainMenu);
|
||||
controls.onExit.add(exitControls);
|
||||
preferences.onExit.add(optionsCodex.switchPage.bind(Options));
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
offsets.onExit.add(exitOffsets);
|
||||
#end
|
||||
saveData.onExit.add(optionsCodex.switchPage.bind(Options));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -159,7 +161,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
|
||||
final CAMERA_MARGIN:Int = 150;
|
||||
|
||||
public function new()
|
||||
public function new(saveDataMenu:SaveDataMenu)
|
||||
{
|
||||
super();
|
||||
add(items = new TextMenuList());
|
||||
|
@ -172,8 +174,8 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
// createItem("CONTROL SCHEMES", function() {
|
||||
// FlxG.state.openSubState(new ControlsSchemeMenu());
|
||||
// });
|
||||
#if FEATURE_INPUT_OFFSETS
|
||||
createItem("INPUT OFFSETS", function() {
|
||||
#if FEATURE_LAG_ADJUSTMENT
|
||||
createItem("LAG ADJUSTMENT", function() {
|
||||
FlxG.sound.music.fadeOut(0.5, 0, function(tw) {
|
||||
FunkinSound.playMusic('offsetsLoop',
|
||||
{
|
||||
|
@ -196,7 +198,7 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
#end
|
||||
#if android
|
||||
createItem("OPEN DATA FOLDER", function() {
|
||||
funkin.mobile.external.android.DataFolderUtil.openDataFolder();
|
||||
funkin.external.android.DataFolderUtil.openDataFolder();
|
||||
});
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
@ -227,9 +229,19 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
});
|
||||
}
|
||||
#end
|
||||
createItem("CLEAR SAVE DATA", function() {
|
||||
promptClearSaveData();
|
||||
});
|
||||
|
||||
// no need to show an entire new menu for just one option
|
||||
if (saveDataMenu.hasMultipleOptions())
|
||||
{
|
||||
createItem("SAVE DATA OPTIONS", function() {
|
||||
codex.switchPage(SaveData);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt);
|
||||
}
|
||||
|
||||
#if NO_FEATURE_TOUCH_CONTROLS
|
||||
createItem("EXIT", exit);
|
||||
#else
|
||||
|
@ -277,7 +289,6 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
enabled = (prompt == null);
|
||||
#if FEATURE_TOUCH_CONTROLS
|
||||
backButton.active = (!goingBack) ? !items.busy : true;
|
||||
#end
|
||||
|
@ -298,31 +309,6 @@ class OptionsMenu extends Page<OptionsMenuPageName>
|
|||
{
|
||||
return items.length > 2;
|
||||
}
|
||||
|
||||
var prompt:Prompt;
|
||||
|
||||
function promptClearSaveData():Void
|
||||
{
|
||||
if (prompt != null) return;
|
||||
prompt = new Prompt("This will delete
|
||||
\nALL your save data.
|
||||
\nAre you sure?
|
||||
", Custom("Delete", "Cancel"));
|
||||
prompt.create();
|
||||
prompt.createBgFromMargin(100, 0xFFFAFD6D);
|
||||
prompt.back.scrollFactor.set(0, 0);
|
||||
add(prompt);
|
||||
prompt.onYes = function() {
|
||||
// Clear the save data.
|
||||
funkin.save.Save.clearData();
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
};
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract OptionsMenuPageName(String) to PageName
|
||||
|
@ -333,4 +319,5 @@ enum abstract OptionsMenuPageName(String) to PageName
|
|||
var Mods = "mods";
|
||||
var Preferences = "preferences";
|
||||
var Offsets = "offsets";
|
||||
var SaveData = "saveData";
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
createPrefDescription();
|
||||
|
||||
camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70);
|
||||
if (items != null) camFollow.y = items.selectedItem.y;
|
||||
|
||||
menuCamera.follow(camFollow, null, 0.085);
|
||||
var margin = 160;
|
||||
|
@ -73,7 +72,6 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
menuCamera.minScrollY = 0;
|
||||
|
||||
items.onChange.add(function(selected) {
|
||||
camFollow.y = selected.y;
|
||||
itemDesc.text = preferenceDesc[items.selectedIndex];
|
||||
});
|
||||
|
||||
|
@ -204,6 +202,9 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// Positions the camera to the selected item.
|
||||
if (items != null) camFollow.y = items.selectedItem.y;
|
||||
|
||||
// Indent the selected item.
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
var thyOffset:Int = 0;
|
||||
|
|
125
source/funkin/ui/options/SaveDataMenu.hx
Normal file
|
@ -0,0 +1,125 @@
|
|||
package funkin.ui.options;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
#end
|
||||
import funkin.save.Save;
|
||||
|
||||
class SaveDataMenu extends Page<OptionsState.OptionsMenuPageName>
|
||||
{
|
||||
var items:TextMenuList;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
add(items = new TextMenuList());
|
||||
|
||||
createItem("CLEAR SAVE DATA", openSaveDataPrompt);
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
createItem("LOAD FROM NG", function() {
|
||||
openConfirmPrompt("This will overwrite
|
||||
\nALL your save data.
|
||||
\nAre you sure?
|
||||
", "Overwrite", function() {
|
||||
Save.loadFromNewgrounds(function() {
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
createItem("SAVE TO NG", function() {
|
||||
openConfirmPrompt("This will overwrite
|
||||
\nALL save data saved
|
||||
\non NG. Are you sure?", "Overwrite", function() {
|
||||
Save.saveToNewgrounds();
|
||||
});
|
||||
});
|
||||
|
||||
createItem("CLEAR NG SAVE DATA", function() {
|
||||
openConfirmPrompt("This will delete
|
||||
\nALL save data saved
|
||||
\non NG. Are you sure?", "Delete", function() {
|
||||
funkin.api.newgrounds.NGSaveSlot.instance.clear();
|
||||
});
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
createItem("EXIT", exit);
|
||||
}
|
||||
|
||||
function createItem(name:String, callback:Void->Void, fireInstantly = false)
|
||||
{
|
||||
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.screenCenter(X);
|
||||
return item;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
enabled = (prompt == null);
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
override function set_enabled(value:Bool)
|
||||
{
|
||||
items.enabled = value;
|
||||
return super.set_enabled(value);
|
||||
}
|
||||
|
||||
var prompt:Prompt;
|
||||
|
||||
function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void, ?groupToOpenOn:Null<flixel.group.FlxGroup>):Void
|
||||
{
|
||||
if (prompt != null) return;
|
||||
|
||||
prompt = new Prompt(text, Custom(yesText, "Cancel"));
|
||||
prompt.create();
|
||||
prompt.createBgFromMargin(100, 0xFFFAFD6D);
|
||||
prompt.back.scrollFactor.set(0, 0);
|
||||
FlxG.state.add(prompt);
|
||||
|
||||
prompt.onYes = function() {
|
||||
onYes();
|
||||
|
||||
if (prompt != null)
|
||||
{
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
}
|
||||
};
|
||||
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
}
|
||||
}
|
||||
public function openSaveDataPrompt()
|
||||
{
|
||||
openConfirmPrompt("This will delete
|
||||
\nALL your save data.
|
||||
\nAre you sure?
|
||||
", "Delete", function() {
|
||||
// Clear the save data.
|
||||
Save.clearData();
|
||||
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this page has multiple options, excluding the exit option.
|
||||
* If false, there's no reason to ever show this page.
|
||||
*/
|
||||
public function hasMultipleOptions():Bool
|
||||
{
|
||||
return items.length > 2;
|
||||
}
|
||||
}
|
|
@ -148,6 +148,6 @@ class NumberPreferenceItem extends TextMenuItem
|
|||
function toFixed(value:Float):Float
|
||||
{
|
||||
var multiplier:Float = Math.pow(10, precision);
|
||||
return Math.floor(value * multiplier) / multiplier;
|
||||
return Math.round(value * multiplier) / multiplier;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import funkin.ui.transition.stickers.StickerSubState;
|
|||
import funkin.util.MathUtil;
|
||||
import funkin.util.SwipeUtil;
|
||||
import funkin.util.TouchUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
|
@ -605,12 +604,14 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty);
|
||||
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetVariation: targetVariation
|
||||
}, true);
|
||||
FlxG.camera.fade(FlxColor.BLACK, 0.2, false, function() {
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||
targetVariation: targetVariation
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -228,11 +228,11 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
FlxG.bitmapLog.add(FlxG.camera.buffer);
|
||||
|
||||
#if desktop
|
||||
#if (desktop || android)
|
||||
// Pressing BACK on the title screen should close the game.
|
||||
// This lets you exit without leaving fullscreen mode.
|
||||
// Only applicable on desktop.
|
||||
if (controls.BACK)
|
||||
// Only applicable on desktop and Android.
|
||||
if (#if android FlxG.android.justReleased.BACK || #end controls.BACK)
|
||||
{
|
||||
openfl.Lib.application.window.close();
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ class TitleState extends MusicBeatState
|
|||
|
||||
if (gamepad != null)
|
||||
{
|
||||
if (gamepad.justPressed.START) pressedEnter = true;
|
||||
if (gamepad.justPressed.START || gamepad.justPressed.ACCEPT) pressedEnter = true;
|
||||
}
|
||||
|
||||
// If you spam Enter, we should skip the transition.
|
||||
|
|
|
@ -102,12 +102,19 @@ class WindowUtil
|
|||
*/
|
||||
public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>();
|
||||
|
||||
/**
|
||||
* Has `initWindowEvents()` been called already?
|
||||
* This is to prevent multiple instances of the same function.
|
||||
*/
|
||||
private static var _initializedWindowEvents:Bool = false;
|
||||
|
||||
/**
|
||||
* Wires up FlxSignals that happen based on window activity.
|
||||
* For example, we can run a callback when the window is closed.
|
||||
*/
|
||||
public static function initWindowEvents():Void
|
||||
{
|
||||
if (_initializedWindowEvents) return; // Fix that annoying
|
||||
// onUpdate is called every frame just before rendering.
|
||||
|
||||
// onExit is called when the game window is closed.
|
||||
|
@ -137,6 +144,7 @@ class WindowUtil
|
|||
}
|
||||
});
|
||||
#end
|
||||
_initializedWindowEvents = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -59,13 +59,21 @@ class EnvironmentConfigMacro
|
|||
|
||||
for (line in envFile.split('\n'))
|
||||
{
|
||||
if (line == "" || line.startsWith("#")) continue;
|
||||
if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue;
|
||||
|
||||
var parts = line.split('=');
|
||||
if (parts.length != 2) continue;
|
||||
var index:Int = line.indexOf('=');
|
||||
|
||||
envFields.push(parts[0]);
|
||||
envValues.push(parts[1]);
|
||||
if (index == -1) continue;
|
||||
|
||||
var field:String = line.substr(0, index);
|
||||
var value:String = line.substr(index + 1);
|
||||
|
||||
if (value == "") continue;
|
||||
|
||||
Sys.println('[INFO] Found a key for environment value $field!');
|
||||
|
||||
envFields.push(field);
|
||||
envValues.push(value);
|
||||
}
|
||||
|
||||
var newFields = fields.copy();
|
||||
|
@ -100,6 +108,27 @@ class EnvironmentConfigMacro
|
|||
|
||||
return newFields;
|
||||
}
|
||||
|
||||
private static function shouldExcludeKey(key:String):Bool
|
||||
{
|
||||
final android:Bool = key.startsWith('ANDROID_');
|
||||
final ios:Bool = key.startsWith('IOS_');
|
||||
final mobile:Bool = key.startsWith('MOBILE_') || ios || android;
|
||||
final web:Bool = key.startsWith('WEB_');
|
||||
final desktop:Bool = key.startsWith('DESKTOP_');
|
||||
|
||||
#if html5
|
||||
if (mobile || desktop) return true;
|
||||
#elseif desktop
|
||||
if (mobile || web) return true;
|
||||
#elseif android
|
||||
if (ios || web || desktop) return true;
|
||||
#elseif ios
|
||||
if (android || web || desktop) return true;
|
||||
#end
|
||||
|
||||
return false;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class GitCommit
|
|||
|
||||
process.close();
|
||||
|
||||
trace('Git Commit ID: ${commitHashSplice}');
|
||||
Sys.println('[INFO] Git Commit ID: ${commitHashSplice}');
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{commitHashSplice};
|
||||
|
@ -56,7 +56,7 @@ class GitCommit
|
|||
|
||||
var branchName:String = branchProcess.stdout.readLine();
|
||||
branchProcess.close();
|
||||
trace('Git Branch Name: ${branchName}');
|
||||
Sys.println('[INFO] Git Branch Name: ${branchName}');
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{branchName};
|
||||
|
@ -103,7 +103,7 @@ class GitCommit
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
trace('Git Status Output: ${output}');
|
||||
Sys.println('[INFO] Git Status Output: ${output}');
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{output.length > 0};
|
||||
|
|
44
source/funkin/util/macro/LinkerMacro.hx
Normal file
|
@ -0,0 +1,44 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import flixel.FlxBasic;
|
|||
import funkin.ui.MusicBeatState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
#if android
|
||||
import funkin.mobile.external.android.CallbackUtil;
|
||||
import funkin.external.android.CallbackUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package funkin.util.plugins;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.math.FlxAngle;
|
||||
|
@ -33,6 +33,11 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup<TouchPointer>
|
|||
*/
|
||||
private static var instance:TouchPointerPlugin = null;
|
||||
|
||||
/**
|
||||
* A camera dedicated to displaying the pointers.
|
||||
*/
|
||||
private static var pointerCamera:FlxCamera;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
@ -43,7 +48,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup<TouchPointer>
|
|||
*/
|
||||
public static function initialize():Void
|
||||
{
|
||||
var pointerCamera:FlxCamera = new FlxCamera();
|
||||
pointerCamera = new FlxCamera();
|
||||
pointerCamera.bgColor.alpha = 0;
|
||||
instance = new TouchPointerPlugin();
|
||||
instance.cameras = [pointerCamera];
|
||||
|
@ -125,7 +130,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup<TouchPointer>
|
|||
add(pointer);
|
||||
}
|
||||
|
||||
pointer.updateFromTouch(touch);
|
||||
pointer.updateFromTouch(touch, pointerCamera);
|
||||
}
|
||||
|
||||
for (pointer in members)
|
||||
|
@ -226,6 +231,12 @@ class TouchPointer extends FlxSprite
|
|||
*/
|
||||
public var touchId:Int = -1;
|
||||
|
||||
/**
|
||||
* An internal point for grabbing the view position of the camera.
|
||||
* Useful for reducing point allocation.
|
||||
*/
|
||||
private var viewPoint:FlxPoint;
|
||||
|
||||
/**
|
||||
* Stores the last position of the touch pointer.
|
||||
*/
|
||||
|
@ -240,6 +251,7 @@ class TouchPointer extends FlxSprite
|
|||
super();
|
||||
makeGraphic(16, 16, FlxColor.RED);
|
||||
scrollFactor.set(0, 0);
|
||||
viewPoint = FlxPoint.get();
|
||||
lastPosition = FlxPoint.get();
|
||||
}
|
||||
|
||||
|
@ -260,12 +272,16 @@ class TouchPointer extends FlxSprite
|
|||
* Used in TouchPointerPlugin's update method.
|
||||
*
|
||||
* @param touch The FlxTouch object containing the current touch input data.
|
||||
* @param camera The FlxCamera to grab the touch's view position from.
|
||||
*/
|
||||
public function updateFromTouch(touch:FlxTouch):Void
|
||||
public function updateFromTouch(touch:FlxTouch, camera:FlxCamera):Void
|
||||
{
|
||||
// Grab the view coordinates
|
||||
touch.getViewPosition(camera, viewPoint);
|
||||
|
||||
// Update position
|
||||
x = touch.viewX - width / 2;
|
||||
y = touch.viewY - height / 2;
|
||||
x = viewPoint.x - width / 2;
|
||||
y = viewPoint.y - height / 2;
|
||||
|
||||
if (camera.target != null)
|
||||
{
|
||||
|
@ -274,7 +290,7 @@ class TouchPointer extends FlxSprite
|
|||
}
|
||||
|
||||
// Calculate angle if moving
|
||||
if (lastPosition.distanceTo(FlxPoint.weak(touch.viewX, touch.viewY)) > 3)
|
||||
if (lastPosition.distanceTo(FlxPoint.weak(viewPoint.x, viewPoint.y)) > 3)
|
||||
{
|
||||
var angle = FlxAngle.angleBetweenPoint(this, lastPosition, true);
|
||||
this.angle = angle;
|
||||
|
@ -286,11 +302,12 @@ class TouchPointer extends FlxSprite
|
|||
loadGraphic("assets/images/cursor/michael.png");
|
||||
}
|
||||
|
||||
lastPosition.set(touch.viewX, touch.viewY);
|
||||
lastPosition.copyFrom(viewPoint);
|
||||
}
|
||||
|
||||
override public function destroy():Void
|
||||
{
|
||||
viewPoint.put();
|
||||
lastPosition.put();
|
||||
super.destroy();
|
||||
}
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -1,30 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 28 KiB |
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FDF150</color>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
package;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import openfl.errors.Error;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxState;
|
||||
|
|
|
@ -11,7 +11,6 @@ import massive.munit.async.AsyncFactory;
|
|||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.animation.FlxAnimationController;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
@:access(funkin.play.notes.notestyle.NoteStyle)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
|
|