diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml
index 5a1f5609a..49bab1ac1 100644
--- a/.github/workflows/build-shit.yml
+++ b/.github/workflows/build-shit.yml
@@ -77,7 +77,7 @@ jobs:
key: ${{ runner.os }}-build-win-${{ github.ref_name }}
- name: Build game
run: |
- haxelib run lime build windows -v -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD
+ haxelib run lime build windows -v -release -DGITHUB_BUILD
env:
HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
- name: Upload build artifacts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 87ed06aed..fa036f0e9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -180,6 +180,16 @@
"target": "windows",
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
},
+ {
+ "label": "Windows / Release",
+ "target": "windows",
+ "args": ["-release"]
+ },
+ {
+ "label": "Windows / Release (GitHub Actions)",
+ "target": "windows",
+ "args": ["-release", "-DGITHUB_BUILD"]
+ },
{
"label": "HashLink / Debug (Waveform Test)",
"target": "hl",
diff --git a/Project.xml b/Project.xml
index 99c46ef9f..ffc8382a4 100644
--- a/Project.xml
+++ b/Project.xml
@@ -91,8 +91,11 @@
NOT USING A DIRECT THING TO THE ASSET!!!
-->
-
-
+
+
+
+
+
@@ -194,7 +197,7 @@
-
+
-
+
diff --git a/assets b/assets
index fe8c987eb..0e2c5bf21 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit fe8c987eb846ceb73b8518879b506111aaccdf80
+Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50
diff --git a/hmm.json b/hmm.json
index cbd5fea30..42d17743f 100644
--- a/hmm.json
+++ b/hmm.json
@@ -11,7 +11,7 @@
"name": "flixel",
"type": "git",
"dir": null,
- "ref": "4d054bd10b05bb1309a0ba3427ffa5378e0b4b99",
+ "ref": "5823c46b4e7410372d58b99f8d5c52fc18b3cb3d",
"url": "https://github.com/FunkinCrew/flixel"
},
{
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 9ecf66ec7..a9e8dbffa 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -30,7 +30,7 @@ import funkin.modding.module.ModuleHandler;
import funkin.ui.title.TitleState;
import funkin.util.CLIUtil;
import funkin.util.CLIUtil.CLIParams;
-import funkin.util.tools.TimerTools;
+import funkin.util.TimerUtil;
import funkin.ui.transition.LoadingState;
import funkin.util.TrackerUtil;
#if discord_rpc
@@ -90,69 +90,7 @@ class InitState extends FlxState
// Set the game to a lower frame rate while it is in the background.
FlxG.game.focusLostFramerate = 30;
- //
- // FLIXEL DEBUG SETUP
- //
- #if (debug || FORCE_DEBUG_VERSION)
- // Disable using ~ to open the console (we use that for the Editor menu)
- FlxG.debugger.toggleKeys = [F2];
- TrackerUtil.initTrackers();
- // Adds an additional Close Debugger button.
- // This big obnoxious white button is for MOBILE, so that you can press it
- // easily with your finger when debug bullshit pops up during testing lol!
- FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
- FlxG.debugger.visible = false;
- });
-
- // Adds a red button to the debugger.
- // This pauses the game AND the music! This ensures the Conductor stops.
- FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
- if (FlxG.vcr.paused)
- {
- FlxG.vcr.resume();
-
- for (snd in FlxG.sound.list)
- {
- snd.resume();
- }
-
- FlxG.sound.music.resume();
- }
- else
- {
- FlxG.vcr.pause();
-
- for (snd in FlxG.sound.list)
- {
- snd.pause();
- }
-
- FlxG.sound.music.pause();
- }
- });
-
- // Adds a blue button to the debugger.
- // This skips forward in the song.
- FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
- FlxG.game.debugger.vcr.onStep();
-
- for (snd in FlxG.sound.list)
- {
- snd.pause();
- snd.time += FlxG.elapsed * 1000;
- }
-
- FlxG.sound.music.pause();
- FlxG.sound.music.time += FlxG.elapsed * 1000;
- });
- #end
-
- // Make errors and warnings less annoying.
- // Forcing this always since I have never been happy to have the debugger to pop up
- LogStyle.ERROR.openConsole = false;
- LogStyle.ERROR.errorSound = null;
- LogStyle.WARNING.openConsole = false;
- LogStyle.WARNING.errorSound = null;
+ setupFlixelDebug();
//
// FLIXEL TRANSITIONS
@@ -221,7 +159,7 @@ class InitState extends FlxState
// NOTE: Registries must be imported and not referenced with fully qualified names,
// to ensure build macros work properly.
trace('Parsing game data...');
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries();
@@ -238,7 +176,7 @@ class InitState extends FlxState
ModuleHandler.loadModuleCache();
ModuleHandler.callOnCreate();
- trace('Parsing game data took: ${TimerTools.ms(perfStart)}');
+ trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
}
/**
@@ -349,6 +287,80 @@ class InitState extends FlxState
});
}
+ function setupFlixelDebug():Void
+ {
+ //
+ // FLIXEL DEBUG SETUP
+ //
+ #if (debug || FORCE_DEBUG_VERSION)
+ // Make errors and warnings less annoying.
+ // Forcing this always since I have never been happy to have the debugger to pop up
+ LogStyle.ERROR.openConsole = false;
+ LogStyle.ERROR.errorSound = null;
+ LogStyle.WARNING.openConsole = false;
+ LogStyle.WARNING.errorSound = null;
+
+ // Disable using ~ to open the console (we use that for the Editor menu)
+ FlxG.debugger.toggleKeys = [F2];
+ TrackerUtil.initTrackers();
+ // Adds an additional Close Debugger button.
+ // This big obnoxious white button is for MOBILE, so that you can press it
+ // easily with your finger when debug bullshit pops up during testing lol!
+ FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
+ FlxG.debugger.visible = false;
+
+ // Make errors and warnings less annoying.
+ // Forcing this always since I have never been happy to have the debugger to pop up
+ LogStyle.ERROR.openConsole = false;
+ LogStyle.ERROR.errorSound = null;
+ LogStyle.WARNING.openConsole = false;
+ LogStyle.WARNING.errorSound = null;
+ });
+
+ // Adds a red button to the debugger.
+ // This pauses the game AND the music! This ensures the Conductor stops.
+ FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
+ if (FlxG.vcr.paused)
+ {
+ FlxG.vcr.resume();
+
+ for (snd in FlxG.sound.list)
+ {
+ snd.resume();
+ }
+
+ FlxG.sound.music.resume();
+ }
+ else
+ {
+ FlxG.vcr.pause();
+
+ for (snd in FlxG.sound.list)
+ {
+ snd.pause();
+ }
+
+ FlxG.sound.music.pause();
+ }
+ });
+
+ // Adds a blue button to the debugger.
+ // This skips forward in the song.
+ FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
+ FlxG.game.debugger.vcr.onStep();
+
+ for (snd in FlxG.sound.list)
+ {
+ snd.pause();
+ snd.time += FlxG.elapsed * 1000;
+ }
+
+ FlxG.sound.music.pause();
+ FlxG.sound.music.time += FlxG.elapsed * 1000;
+ });
+ #end
+ }
+
function defineSong():String
{
return MacroUtil.getDefine('SONG');
diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx
index a1e14d705..9efa6ed50 100644
--- a/source/funkin/audio/FunkinSound.hx
+++ b/source/funkin/audio/FunkinSound.hx
@@ -8,6 +8,8 @@ import flixel.sound.FlxSound;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.system.FlxAssets.FlxSoundAsset;
import funkin.util.tools.ICloneable;
+import funkin.data.song.SongData.SongMusicData;
+import funkin.data.song.SongRegistry;
import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
import flixel.math.FlxMath;
@@ -28,7 +30,7 @@ class FunkinSound extends FlxSound implements ICloneable
/**
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
*/
- static var cache(default, null):FlxTypedGroup = new FlxTypedGroup();
+ static var pool(default, null):FlxTypedGroup = new FlxTypedGroup();
public var muted(default, set):Bool = false;
@@ -265,23 +267,55 @@ class FunkinSound extends FlxSound implements ICloneable
}
/**
- * Creates a new `FunkinSound` object.
+ * Creates a new `FunkinSound` object and loads it as the current music track.
*
- * @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
- * @param volume How loud to play it (0 to 1).
- * @param looped Whether to loop this sound.
- * @param group The group to add this sound to.
- * @param autoDestroy Whether to destroy this sound when it finishes playing.
+ * @param key The key of the music you want to play. Music should be at `music//.ogg`.
+ * @param overrideExisting Whether to override music if it is already playing.
+ * @param mapTimeChanges Whether to check for `SongMusicData` to update the Conductor with.
+ * Data should be at `music//-metadata.json`.
+ */
+ public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void
+ {
+ if (!overrideExisting && FlxG.sound.music?.playing) return;
+
+ if (mapTimeChanges)
+ {
+ var songMusicData:Null = SongRegistry.instance.parseMusicData(key);
+ // Will fall back and return null if the metadata doesn't exist or can't be parsed.
+ if (songMusicData != null)
+ {
+ Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
+ }
+ else
+ {
+ FlxG.log.warn('Tried and failed to find music metadata for $key');
+ }
+ }
+
+ FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key'));
+
+ // Prevent repeat update() and onFocus() calls.
+ FlxG.sound.list.remove(FlxG.sound.music);
+ }
+
+ /**
+ * Creates a new `FunkinSound` object synchronously.
+ *
+ * @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
+ * @param volume How loud to play it (0 to 1).
+ * @param looped Whether to loop this sound.
+ * @param group The group to add this sound to.
+ * @param autoDestroy Whether to destroy this sound when it finishes playing.
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
- * @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
- * @param onComplete Called when the sound finished playing.
- * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
- * @return A `FunkinSound` object.
+ * @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
+ * @param onComplete Called when the sound finished playing.
+ * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
+ * @return A `FunkinSound` object.
*/
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound
{
- var sound:FunkinSound = cache.recycle(construct);
+ var sound:FunkinSound = pool.recycle(construct);
// Load the sound.
// Sets `exists = true` as a side effect.
@@ -297,9 +331,11 @@ class FunkinSound extends FlxSound implements ICloneable
sound.persist = true;
if (autoPlay) sound.play();
- // Call OnlLoad() because the sound already loaded
+ // Call onLoad() because the sound already loaded
if (onLoad != null && sound._sound != null) onLoad();
+ FlxG.sound.list.remove(FlxG.sound.music);
+
return sound;
}
@@ -307,7 +343,7 @@ class FunkinSound extends FlxSound implements ICloneable
{
var sound:FunkinSound = new FunkinSound();
- cache.add(sound);
+ pool.add(sound);
FlxG.sound.list.add(sound);
return sound;
diff --git a/source/funkin/audio/visualize/SpectogramSprite.hx b/source/funkin/audio/visualize/SpectogramSprite.hx
index 470dbf7fe..636c0726a 100644
--- a/source/funkin/audio/visualize/SpectogramSprite.hx
+++ b/source/funkin/audio/visualize/SpectogramSprite.hx
@@ -4,7 +4,6 @@ import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
-import flixel.math.FlxVector;
import flixel.sound.FlxSound;
import flixel.util.FlxColor;
import funkin.audio.visualize.PolygonSpectogram.VISTYPE;
diff --git a/source/funkin/audio/waveform/WaveformDataParser.hx b/source/funkin/audio/waveform/WaveformDataParser.hx
index c667f2002..5aa54d744 100644
--- a/source/funkin/audio/waveform/WaveformDataParser.hx
+++ b/source/funkin/audio/waveform/WaveformDataParser.hx
@@ -1,6 +1,6 @@
package funkin.audio.waveform;
-import funkin.util.tools.TimerTools;
+import funkin.util.TimerUtil;
class WaveformDataParser
{
@@ -73,7 +73,7 @@ class WaveformDataParser
var outputData:Array = [];
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
for (pointIndex in 0...outputPointCount)
{
@@ -110,7 +110,7 @@ class WaveformDataParser
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
- trace('[WAVEFORM] Interpreted audio buffer in ${TimerTools.seconds(perfStart)}.');
+ trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.');
return result;
}
diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx
index 2df0c18f0..ad028fa94 100644
--- a/source/funkin/data/BaseRegistry.hx
+++ b/source/funkin/data/BaseRegistry.hx
@@ -1,6 +1,5 @@
package funkin.data;
-import openfl.Assets;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
import haxe.Constraints.Constructible;
@@ -19,12 +18,23 @@ typedef EntryConstructorFunction = String->Void;
@:generic
abstract class BaseRegistry & Constructible), J>
{
+ /**
+ * The ID of the registry. Used when logging.
+ */
public final registryId:String;
final dataFilePath:String;
+ /**
+ * A map of entry IDs to entries.
+ */
final entries:Map;
+ /**
+ * A map of entry IDs to scripted class names.
+ */
+ final scriptedEntryIds:Map;
+
/**
* The version rule to use when loading entries.
* If the entry's version does not match this rule, migration is needed.
@@ -37,17 +47,18 @@ abstract class BaseRegistry & Constructible();
+ this.scriptedEntryIds = [];
}
/**
- * TODO: Create a `loadEntriesAsync()` function.
+ * TODO: Create a `loadEntriesAsync(onProgress, onComplete)` function.
*/
public function loadEntries():Void
{
@@ -66,7 +77,7 @@ abstract class BaseRegistry & Constructible & Constructible & Constructible & Constructible & Constructible
{
var entryStr:String = loadEntryFile(id).contents;
@@ -185,6 +232,8 @@ abstract class BaseRegistry & Constructible;
@@ -194,6 +243,7 @@ abstract class BaseRegistry & Constructible;
@@ -202,6 +252,9 @@ abstract class BaseRegistry & Constructible
{
@@ -220,12 +273,17 @@ abstract class BaseRegistry & Constructible & Constructible
{
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
entries.set(entry.id, entry);
+ scriptedEntryIds.set(entry.id, entryCls);
}
else
{
@@ -441,6 +442,13 @@ class SongRegistry extends BaseRegistry
return {fileName: entryFilePath, contents: rawJson};
}
+ function hasMusicDataFile(id:String, ?variation:String):Bool
+ {
+ variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
+ var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
+ return openfl.Assets.exists(entryFilePath);
+ }
+
function loadEntryChartFile(id:String, ?variation:String):Null
{
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx
index f47b4138a..03382f757 100644
--- a/source/funkin/graphics/FunkinSprite.hx
+++ b/source/funkin/graphics/FunkinSprite.hx
@@ -81,9 +81,10 @@ class FunkinSprite extends FlxSprite
*/
public function loadTexture(key:String):FunkinSprite
{
- if (!isTextureCached(key)) FlxG.log.warn('Texture not cached, may experience stuttering! $key');
+ var graphicKey:String = Paths.image(key);
+ if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
- loadGraphic(key);
+ loadGraphic(graphicKey);
return this;
}
@@ -95,7 +96,7 @@ class FunkinSprite extends FlxSprite
*/
public function loadSparrow(key:String):FunkinSprite
{
- var graphicKey = Paths.image(key);
+ var graphicKey:String = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getSparrowAtlas(key);
@@ -110,7 +111,7 @@ class FunkinSprite extends FlxSprite
*/
public function loadPacker(key:String):FunkinSprite
{
- var graphicKey = Paths.image(key);
+ var graphicKey:String = Paths.image(key);
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
this.frames = Paths.getPackerAtlas(key);
diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx
index 38e8986ef..747565100 100644
--- a/source/funkin/play/Countdown.hx
+++ b/source/funkin/play/Countdown.hx
@@ -215,7 +215,7 @@ class Countdown
if (spritePath == null) return;
- var countdownSprite:FunkinSprite = FunkinSprite.create(Paths.image(spritePath));
+ var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
countdownSprite.scrollFactor.set(0, 0);
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx
index 1ed9dcf3b..eae56a9c3 100644
--- a/source/funkin/play/GitarooPause.hx
+++ b/source/funkin/play/GitarooPause.hx
@@ -28,7 +28,7 @@ class GitarooPause extends MusicBeatState
{
if (FlxG.sound.music != null) FlxG.sound.music.stop();
- var bg:FunkinSprite = FunkinSprite.create(Paths.image('pauseAlt/pauseBG'));
+ var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG');
add(bg);
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 0bd731bc6..7c0ed64b3 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -1,5 +1,6 @@
package funkin.play;
+import funkin.audio.FunkinSound;
import flixel.addons.display.FlxPieDial;
import flixel.addons.display.FlxPieDial;
import flixel.addons.transition.FlxTransitionableState;
@@ -1448,7 +1449,7 @@ class PlayState extends MusicBeatSubState
function initHealthBar():Void
{
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
- healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar'));
+ healthBarBG = FunkinSprite.create(0, healthBarYPos, 'healthBar');
healthBarBG.screenCenter(X);
healthBarBG.scrollFactor.set(0, 0);
healthBarBG.zIndex = 800;
@@ -1485,7 +1486,7 @@ class PlayState extends MusicBeatSubState
function initMinimalMode():Void
{
// Create the green background.
- var menuBG = FunkinSprite.create(Paths.image('menuDesat'));
+ var menuBG = FunkinSprite.create('menuDesat');
menuBG.color = 0xFF4CAF50;
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
menuBG.updateHitbox();
@@ -2743,7 +2744,7 @@ class PlayState extends MusicBeatSubState
if (targetSongId == null)
{
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
+ FunkinSound.playMusic('freakyMenu');
// transIn = FlxTransitionableState.defaultTransIn;
// transOut = FlxTransitionableState.defaultTransOut;
diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx
index 0fe50f513..39fc192a0 100644
--- a/source/funkin/play/components/PopUpStuff.hx
+++ b/source/funkin/play/components/PopUpStuff.hx
@@ -6,7 +6,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxDirection;
import funkin.graphics.FunkinSprite;
import funkin.play.PlayState;
-import funkin.util.tools.TimerTools;
+import funkin.util.TimerUtil;
class PopUpStuff extends FlxTypedGroup
{
@@ -17,7 +17,7 @@ class PopUpStuff extends FlxTypedGroup
public function displayRating(daRating:String)
{
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
if (daRating == null) daRating = "good";
@@ -25,7 +25,7 @@ class PopUpStuff extends FlxTypedGroup
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
- var rating:FunkinSprite = FunkinSprite.create(0, 0, Paths.image(ratingPath));
+ var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
rating.scrollFactor.set(0.2, 0.2);
rating.zIndex = 1000;
@@ -59,12 +59,12 @@ class PopUpStuff extends FlxTypedGroup
startDelay: Conductor.instance.beatLengthMs * 0.001
});
- trace('displayRating took: ${TimerTools.seconds(perfStart)}');
+ trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
}
public function displayCombo(?combo:Int = 0):Int
{
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
if (combo == null) combo = 0;
@@ -76,7 +76,7 @@ class PopUpStuff extends FlxTypedGroup
pixelShitPart1 = 'weeb/pixelUI/';
pixelShitPart2 = '-pixel';
}
- var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
+ var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
comboSpr.y = FlxG.camera.height * 0.4 + 80;
comboSpr.x = FlxG.width * 0.50;
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
@@ -124,7 +124,7 @@ class PopUpStuff extends FlxTypedGroup
var daLoop:Int = 1;
for (i in seperatedScore)
{
- var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
+ var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
if (PlayState.instance.currentStageId.startsWith('school'))
{
@@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup
daLoop++;
}
- trace('displayCombo took: ${TimerTools.seconds(perfStart)}');
+ trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
return combo;
}
diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx
index cd4366dd2..d4ab4f24f 100644
--- a/source/funkin/play/event/FocusCameraSongEvent.hx
+++ b/source/funkin/play/event/FocusCameraSongEvent.hx
@@ -58,6 +58,9 @@ class FocusCameraSongEvent extends SongEvent
// Does nothing if there is no PlayState camera or stage.
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
+ // Does nothing if we are minimal mode.
+ if (PlayState.instance.isMinimalMode) return;
+
var posX:Null = data.getFloat('x');
if (posX == null) posX = 0.0;
var posY:Null = data.getFloat('y');
diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx
index 187664e97..b9b634ffe 100644
--- a/source/funkin/play/event/ZoomCameraSongEvent.hx
+++ b/source/funkin/play/event/ZoomCameraSongEvent.hx
@@ -55,7 +55,10 @@ class ZoomCameraSongEvent extends SongEvent
public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState camera or stage.
- if (PlayState.instance == null) return;
+ if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
+
+ // Does nothing if we are minimal mode.
+ if (PlayState.instance.isMinimalMode) return;
var zoom:Null = data.getFloat('zoom');
if (zoom == null) zoom = 1.0;
diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx
index 3997692c2..1b7740408 100644
--- a/source/funkin/play/song/Song.hx
+++ b/source/funkin/play/song/Song.hx
@@ -1,6 +1,5 @@
package funkin.play.song;
-import flixel.sound.FlxSound;
import funkin.audio.VoicesGroup;
import funkin.audio.FunkinSound;
import funkin.data.IRegistryEntry;
@@ -13,9 +12,8 @@ import funkin.data.song.SongData.SongOffsets;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.SongData.SongTimeFormat;
import funkin.data.song.SongRegistry;
-import funkin.data.song.SongRegistry;
+import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.events.ScriptEvent;
-import funkin.modding.IScriptedClass;
import funkin.util.SortUtil;
import openfl.utils.Assets;
@@ -31,14 +29,44 @@ import openfl.utils.Assets;
@:nullSafety
class Song implements IPlayStateScriptedClass implements IRegistryEntry
{
- public static final DEFAULT_SONGNAME:String = "Unknown";
- public static final DEFAULT_ARTIST:String = "Unknown";
+ /**
+ * The default value for the song's name
+ */
+ public static final DEFAULT_SONGNAME:String = 'Unknown';
+
+ /**
+ * The default value for the song's artist
+ */
+ public static final DEFAULT_ARTIST:String = 'Unknown';
+
+ /**
+ * The default value for the song's time format
+ */
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
+
+ /**
+ * The default value for the song's divisions
+ */
public static final DEFAULT_DIVISIONS:Null = null;
+
+ /**
+ * The default value for whether the song loops.
+ */
public static final DEFAULT_LOOPED:Bool = false;
- public static final DEFAULT_STAGE:String = "mainStage";
+
+ /**
+ * The default value for the song's playable stage.
+ */
+ public static final DEFAULT_STAGE:String = 'mainStage';
+
+ /**
+ * The default value for the song's scroll speed.
+ */
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
+ /**
+ * The internal ID of the song.
+ */
public final id:String;
/**
@@ -53,6 +81,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry;
final difficulties:Map;
+ /**
+ * The list of variations a song has.
+ */
public var variations(get, never):Array;
function get_variations():Array
@@ -65,6 +96,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = fetchVariationMetadata(id, vari);
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
}
}
@@ -115,27 +152,62 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry, variations:Array, charts:Map,
- validScore:Bool = false):Song
+ includeScript:Bool = true, validScore:Bool = false):Song
{
- var result:Song = new Song(songId);
+ @:privateAccess
+ var result:Null;
+
+ if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
+ {
+ var songClassName:String = SongRegistry.instance.getScriptedEntryClassName(songId);
+
+ @:privateAccess
+ result = SongRegistry.instance.createScriptedEntry(songClassName);
+ }
+ else
+ {
+ @:privateAccess
+ result = SongRegistry.instance.createEntry(songId);
+ }
+
+ if (result == null) throw 'ERROR: Could not build Song instance ($songId), is the attached script bad?';
result._metadata.clear();
for (meta in metadata)
+ {
result._metadata.set(meta.variation, meta);
+ }
result.difficulties.clear();
result.populateDifficulties();
for (variation => chartData in charts)
+ {
result.applyChartData(chartData, variation);
+ }
result.validScore = validScore;
return result;
}
+ /**
+ * Retrieve a list of the raw metadata for the song.
+ * @return The metadata JSON objects for the song's variations.
+ */
public function getRawMetadata():Array
{
return _metadata.values();
@@ -192,6 +264,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = state.audioInstTrackData.get(instId);
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData);
- trace('Built instrumental track in ${TimerTools.seconds(perfStart)} seconds.');
+ trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.');
if (instTrack == null) return false;
stopExistingInstrumental(state);
@@ -205,9 +205,9 @@ class ChartEditorAudioHandler
{
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
var vocalTrackData:Null = state.audioVocalTrackData.get(trackId);
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData);
- trace('Built vocal track in ${TimerTools.seconds(perfStart)}.');
+ trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.');
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
@@ -218,9 +218,9 @@ class ChartEditorAudioHandler
case BF:
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
var waveformData:Null = vocalTrack.waveformData;
- trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.');
+ trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
if (waveformData != null)
{
@@ -244,9 +244,9 @@ class ChartEditorAudioHandler
case DAD:
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
var waveformData:Null = vocalTrack.waveformData;
- trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.');
+ trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
if (waveformData != null)
{
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx
index 970f021ac..b84c68f8d 100644
--- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx
@@ -808,7 +808,8 @@ class ChartEditorDialogHandler
}
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
#if FILE_DROP_SUPPORTED
- addDropHandler(songVariationMetadataEntry, onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel));
+ state.addDropHandler({component: songVariationMetadataEntry, handler: onDropFileMetadataVariation.bind(variation)
+ .bind(songVariationMetadataEntryLabel)});
#end
chartContainerB.addComponent(songVariationMetadataEntry);
@@ -832,7 +833,11 @@ class ChartEditorDialogHandler
}
songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel);
#if FILE_DROP_SUPPORTED
- addDropHandler(songVariationChartDataEntry, onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel));
+ state.addDropHandler(
+ {
+ component: songVariationChartDataEntry,
+ handler: onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel)
+ });
#end
chartContainerB.addComponent(songVariationChartDataEntry);
}
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx
index 3b32edf5d..8c7b1a8c1 100644
--- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx
@@ -318,6 +318,17 @@ class ChartEditorToolboxHandler
state.enabledDebuggerPopup = checkboxDebugger.selected;
};
+ var checkboxSongScripts:Null = toolbox.findComponent('playtestSongScriptsCheckbox', CheckBox);
+
+ if (checkboxSongScripts == null)
+ throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestSongScriptsCheckbox component.';
+
+ state.playtestSongScripts = checkboxSongScripts.selected;
+
+ checkboxSongScripts.onClick = _ -> {
+ state.playtestSongScripts = checkboxSongScripts.selected;
+ };
+
return toolbox;
}
diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx
index 28d435c54..c65781259 100644
--- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx
+++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx
@@ -7,7 +7,7 @@ import funkin.audio.waveform.WaveformDataParser;
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
import funkin.ui.haxeui.components.WaveformPlayer;
import funkin.ui.freeplay.FreeplayState;
-import funkin.util.tools.TimerTools;
+import funkin.util.TimerUtil;
import haxe.ui.backend.flixel.components.SpriteWrapper;
import haxe.ui.components.Button;
import haxe.ui.components.HorizontalSlider;
@@ -289,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
// Build player waveform.
// waveformMusic.waveform.forceUpdate = true;
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
var waveformData1 = playerVoice?.waveformData;
var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file!
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
var waveformData = waveformData3.merge(waveformData1).merge(waveformData2);
- trace('Waveform data merging took: ${TimerTools.seconds(perfStart)}');
+ trace('Waveform data merging took: ${TimerUtil.seconds(perfStart)}');
waveformMusic.waveform.waveformData = waveformData;
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx
index 9d37fe2c1..55f43d2ef 100644
--- a/source/funkin/ui/freeplay/DJBoyfriend.hx
+++ b/source/funkin/ui/freeplay/DJBoyfriend.hx
@@ -4,7 +4,7 @@ import flixel.FlxSprite;
import flixel.util.FlxSignal;
import funkin.util.assets.FlxAnimationUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
-import flixel.system.FlxSound;
+import flixel.sound.FlxSound;
import flixel.util.FlxTimer;
import funkin.audio.FlxStreamSound;
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 45f9a4d27..7ade5a2a6 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -1,21 +1,19 @@
package funkin.ui.freeplay;
-import flash.text.TextField;
+import openfl.text.TextField;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera;
import flixel.FlxGame;
import flixel.FlxSprite;
-import funkin.graphics.FunkinSprite;
import flixel.FlxState;
import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup;
-import flixel.group.FlxSpriteGroup;
+import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle;
import flixel.math.FlxMath;
-import funkin.graphics.FunkinCamera;
import flixel.math.FlxPoint;
import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.text.FlxText;
@@ -24,9 +22,12 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
+import funkin.audio.FunkinSound;
import funkin.data.level.LevelRegistry;
import funkin.data.song.SongRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
+import funkin.graphics.FunkinCamera;
+import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.AngleMask;
import funkin.graphics.shaders.HSVShader;
import funkin.graphics.shaders.PureColor;
@@ -187,10 +188,7 @@ class FreeplayState extends MusicBeatSubState
isDebug = true;
#end
- if (FlxG.sound.music == null || (FlxG.sound.music != null && !FlxG.sound.music.playing))
- {
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
- }
+ FunkinSound.playMusic('freakyMenu');
// Add a null entry that represents the RANDOM option
songs.push(null);
@@ -227,7 +225,7 @@ class FreeplayState extends MusicBeatSubState
trace(FlxG.camera.initialZoom);
trace(FlxCamera.defaultZoom);
- var pinkBack:FunkinSprite = FunkinSprite.create(Paths.image('freeplay/pinkBack'));
+ var pinkBack:FunkinSprite = FunkinSprite.create('freeplay/pinkBack');
pinkBack.color = 0xFFffd4e9; // sets it to pink!
pinkBack.x -= pinkBack.width;
@@ -590,7 +588,7 @@ class FreeplayState extends MusicBeatSubState
});
}
- public function generateSongList(?filterStuff:SongFilter, force:Bool = false)
+ public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void
{
curSelected = 1;
@@ -693,7 +691,7 @@ class FreeplayState extends MusicBeatSubState
var busy:Bool = false; // Set to true once the user has pressed enter to select a song.
- override function update(elapsed:Float)
+ override function update(elapsed:Float):Void
{
super.update(elapsed);
@@ -983,7 +981,7 @@ class FreeplayState extends MusicBeatSubState
}
}
- function changeDiff(change:Int = 0)
+ function changeDiff(change:Int = 0):Void
{
touchTimer = 0;
@@ -1173,7 +1171,7 @@ class FreeplayState extends MusicBeatSubState
difficultyStars.difficulty = daSong?.songRating ?? 0;
}
- function changeSelection(change:Int = 0)
+ function changeSelection(change:Int = 0):Void
{
// NGio.logEvent('Fresh');
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
@@ -1228,7 +1226,7 @@ class FreeplayState extends MusicBeatSubState
// TODO: Stream the instrumental of the selected song?
if (prevSelected == 0)
{
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
+ FunkinSound.playMusic('freakyMenu');
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
}
@@ -1259,7 +1257,7 @@ class DifficultySelector extends FlxSprite
flipX = flipped;
}
- override function update(elapsed:Float)
+ override function update(elapsed:Float):Void
{
if (flipX && controls.UI_RIGHT_P) moveShitDown();
if (!flipX && controls.UI_LEFT_P) moveShitDown();
@@ -1267,7 +1265,7 @@ class DifficultySelector extends FlxSprite
super.update(elapsed);
}
- function moveShitDown()
+ function moveShitDown():Void
{
offset.y -= 5;
diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx
index 8842c37de..1892bdec1 100644
--- a/source/funkin/ui/mainmenu/MainMenuState.hx
+++ b/source/funkin/ui/mainmenu/MainMenuState.hx
@@ -12,8 +12,10 @@ import flixel.util.typeLimit.NextState;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.input.touch.FlxTouch;
import flixel.text.FlxText;
+import funkin.data.song.SongData.SongMusicData;
import flixel.tweens.FlxEase;
import funkin.graphics.FunkinCamera;
+import funkin.audio.FunkinSound;
import flixel.tweens.FlxTween;
import funkin.ui.MusicBeatState;
import flixel.util.FlxTimer;
@@ -51,7 +53,7 @@ class MainMenuState extends MusicBeatState
if (!(FlxG?.sound?.music?.playing ?? false))
{
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
+ playMenuMusic();
}
persistentUpdate = persistentDraw = true;
@@ -151,6 +153,11 @@ class MainMenuState extends MusicBeatState
// NG.core.calls.event.logEvent('swag').send();
}
+ function playMenuMusic():Void
+ {
+ FunkinSound.playMusic('freakyMenu');
+ }
+
function resetCamStuff()
{
FlxG.cameras.reset(new FunkinCamera());
diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx
index 404dfb67e..1f78eb375 100644
--- a/source/funkin/ui/story/StoryMenuState.hx
+++ b/source/funkin/ui/story/StoryMenuState.hx
@@ -16,6 +16,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.data.level.LevelRegistry;
+import funkin.audio.FunkinSound;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState;
@@ -234,17 +235,7 @@ class StoryMenuState extends MusicBeatState
function playMenuMusic():Void
{
- if (FlxG.sound.music == null || !FlxG.sound.music.playing)
- {
- var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu');
- if (freakyMenuMetadata != null)
- {
- Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
- }
-
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
- FlxG.sound.music.fadeIn(4, 0, 0.7);
- }
+ FunkinSound.playMusic('freakyMenu');
}
function updateData():Void
diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx
index 5424e2255..1c194d80d 100644
--- a/source/funkin/ui/title/TitleState.hx
+++ b/source/funkin/ui/title/TitleState.hx
@@ -18,6 +18,7 @@ import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatState;
import funkin.data.song.SongData.SongMusicData;
import funkin.graphics.shaders.TitleOutline;
+import funkin.audio.FunkinSound;
import funkin.ui.freeplay.FreeplayState;
import funkin.ui.AtlasText;
import openfl.Assets;
@@ -219,16 +220,11 @@ class TitleState extends MusicBeatState
function playMenuMusic():Void
{
- if (FlxG.sound.music == null || !FlxG.sound.music.playing)
- {
- var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu');
- if (freakyMenuMetadata != null)
- {
- Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
- }
- FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
- FlxG.sound.music.fadeIn(4, 0, 0.7);
- }
+ var shouldFadeIn = (FlxG.sound.music == null);
+ // Load music. Includes logic to handle BPM changes.
+ FunkinSound.playMusic('freakyMenu', false, true);
+ // Fade from 0.0 to 0.7 over 4 seconds
+ if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7);
}
function getIntroTextShit():Array>
diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx
index 5f755872f..23b3db6a9 100644
--- a/source/funkin/ui/transition/LoadingState.hx
+++ b/source/funkin/ui/transition/LoadingState.hx
@@ -48,7 +48,7 @@ class LoadingState extends MusicBeatState
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
add(bg);
- funkay = FunkinSprite.create(Paths.image('funkay'));
+ funkay = FunkinSprite.create('funkay');
funkay.setGraphicSize(0, FlxG.height);
funkay.updateHitbox();
add(funkay);
@@ -238,11 +238,38 @@ class LoadingState extends MusicBeatState
FunkinSprite.cacheTexture(Paths.image('shit', 'shared'));
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this
+ // List all image assets in the level's library.
+ // This is crude and I want to remove it when we have a proper asset caching system.
+ // TODO: Get rid of this junk!
+ var library = openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId);
+ var assets = library.list(lime.utils.AssetType.IMAGE);
+ trace('Got ${assets.length} assets: ${assets}');
+
+ // TODO: assets includes non-images! This is a bug with Polymod
+ for (asset in assets)
+ {
+ // Exclude items of the wrong type.
+ var path = '${PlayStatePlaylist.campaignId}:${asset}';
+ // TODO DUMB HACK DUMB HACK why doesn't filtering by AssetType.IMAGE above work
+ // I will fix this properly later I swear -eric
+ if (!path.endsWith('.png')) continue;
+
+ FunkinSprite.cacheTexture(path);
+
+ // Another dumb hack: FlxAnimate fetches from OpenFL's BitmapData cache directly and skips the FlxGraphic cache.
+ // Since FlxGraphic tells OpenFL to not cache it, we have to do it manually.
+ if (path.endsWith('spritemap1.png'))
+ {
+ openfl.Assets.getBitmapData(path, true);
+ }
+ }
+
// FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above!
// FunkinSprite.cacheAllCharacterTextures(player)
// FunkinSprite.cacheAllCharacterTextures(girlfriend)
// FunkinSprite.cacheAllCharacterTextures(opponent)
// FunkinSprite.cacheAllStageTextures(stage)
+ // FunkinSprite.cacheAllSongTextures(stage)
FunkinSprite.purgeCache();
@@ -389,9 +416,15 @@ class MultiCallback
public function getUnfired():ArrayVoid>
return unfired.array();
+ /**
+ * Perform an FlxG.switchState with a nice transition
+ * @param state
+ * @param transitionTex
+ * @param time
+ */
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
{
- var screenShit:FunkinSprite = FunkinSprite.create(Paths.image("shaderTransitionStuff/coolDots"));
+ var screenShit:FunkinSprite = FunkinSprite.create('shaderTransitionStuff/coolDots');
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
screenWipeShit.funnyShit.input = screenShit.pixels;
diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx
index 40fce6f7d..981a30e09 100644
--- a/source/funkin/ui/transition/StickerSubState.hx
+++ b/source/funkin/ui/transition/StickerSubState.hx
@@ -313,7 +313,7 @@ class StickerSprite extends FunkinSprite
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
{
super(x, y);
- loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
+ loadTexture('transitionSwag/' + stickerSet + '/' + stickerName);
updateHitbox();
scrollFactor.set();
}
diff --git a/source/funkin/util/tools/TimerTools.hx b/source/funkin/util/TimerUtil.hx
similarity index 93%
rename from source/funkin/util/tools/TimerTools.hx
rename to source/funkin/util/TimerUtil.hx
index 5322ada92..caf49341b 100644
--- a/source/funkin/util/tools/TimerTools.hx
+++ b/source/funkin/util/TimerUtil.hx
@@ -1,9 +1,9 @@
-package funkin.util.tools;
+package funkin.util;
import funkin.util.tools.FloatTools;
import haxe.Timer;
-class TimerTools
+class TimerUtil
{
public static function start():Float
{
diff --git a/source/funkin/util/logging/Perf.hx b/source/funkin/util/logging/Perf.hx
new file mode 100644
index 000000000..83da7a32f
--- /dev/null
+++ b/source/funkin/util/logging/Perf.hx
@@ -0,0 +1,76 @@
+package funkin.util.logging;
+
+/**
+ * A small utility class for timing how long functions take.
+ * Specify a string as a label (or don't, by default it uses the name of the function it was called from.)
+ *
+ * Example:
+ * ```haxe
+ *
+ * var perf = new Perf();
+ * ...
+ * perf.print();
+ * ```
+ */
+class Perf
+{
+ final startTime:Float;
+ final label:Null;
+ final posInfos:Null;
+
+ /**
+ * Create a new performance marker.
+ * @param label Optionally specify a label to use for the performance marker. Defaults to the function name.
+ * @param posInfos The position of the calling function. Used to build the default label.
+ * Note: `haxe.PosInfos` is magic and automatically populated by the compiler!
+ */
+ public function new(?label:String, ?posInfos:haxe.PosInfos)
+ {
+ this.label = label;
+ this.posInfos = posInfos;
+ startTime = current();
+ }
+
+ /**
+ * The current timestamp, in fractional seconds.
+ * @return The current timestamp.
+ */
+ static function current():Float
+ {
+ #if sys
+ // This one is more accurate if it's available.
+ return Sys.time();
+ #else
+ return haxe.Timer.stamp();
+ #end
+ }
+
+ /**
+ * The duration in seconds since this `Perf` was created.
+ * @return The duration in seconds
+ */
+ public function duration():Float
+ {
+ return current() - startTime;
+ }
+
+ /**
+ * A rounded millisecond duration
+ * @return The duration in milliseconds
+ */
+ public function durationClean():Float
+ {
+ var round:Float = 100;
+ return Math.floor(duration() * Constants.MS_PER_SEC * round) / round;
+ }
+
+ /**
+ * Cleanly prints the duration since this `Perf` was created.
+ */
+ public function print():Void
+ {
+ var label:String = label ?? (posInfos == null ? 'unknown' : '${posInfos.className}#${posInfos.methodName}()');
+
+ trace('[PERF] [$label] Took ${durationClean()}ms.');
+ }
+}
diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx
index 67a4fe18e..4b89fa637 100644
--- a/source/funkin/util/plugins/MemoryGCPlugin.hx
+++ b/source/funkin/util/plugins/MemoryGCPlugin.hx
@@ -1,7 +1,7 @@
package funkin.util.plugins;
import flixel.FlxBasic;
-import funkin.util.tools.TimerTools;
+import funkin.util.TimerUtil;
/**
* A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection.
@@ -24,9 +24,9 @@ class MemoryGCPlugin extends FlxBasic
if (FlxG.keys.justPressed.INSERT)
{
- var perfStart:Float = TimerTools.start();
+ var perfStart:Float = TimerUtil.start();
funkin.util.MemoryUtil.collect(true);
- trace('Memory GC took: ${TimerTools.seconds(perfStart)}');
+ trace('Memory GC took: ${TimerUtil.seconds(perfStart)}');
}
}
diff --git a/source/funkin/util/tools/StringTools.hx b/source/funkin/util/tools/StringTools.hx
index 0585ffeae..cb660db05 100644
--- a/source/funkin/util/tools/StringTools.hx
+++ b/source/funkin/util/tools/StringTools.hx
@@ -13,20 +13,50 @@ class StringTools
*/
public static function toTitleCase(value:String):String
{
- var words:Array = value.split(" ");
- var result:String = "";
+ var words:Array = value.split(' ');
+ var result:String = '';
for (i in 0...words.length)
{
var word:String = words[i];
result += word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
if (i < words.length - 1)
{
- result += " ";
+ result += ' ';
}
}
return result;
}
+ /**
+ * Strip a given prefix from a string.
+ * @param value The string to strip.
+ * @param prefix The prefix to strip. If the prefix isn't found, the original string is returned.
+ * @return The stripped string.
+ */
+ public static function stripPrefix(value:String, prefix:String):String
+ {
+ if (value.startsWith(prefix))
+ {
+ return value.substr(prefix.length);
+ }
+ return value;
+ }
+
+ /**
+ * Strip a given suffix from a string.
+ * @param value The string to strip.
+ * @param suffix The suffix to strip. If the suffix isn't found, the original string is returned.
+ * @return The stripped string.
+ */
+ public static function stripSuffix(value:String, suffix:String):String
+ {
+ if (value.endsWith(suffix))
+ {
+ return value.substr(0, value.length - suffix.length);
+ }
+ return value;
+ }
+
/**
* Converts a string to lower kebab case. For example, "Hello World" becomes "hello-world".
*
@@ -35,7 +65,7 @@ class StringTools
*/
public static function toLowerKebabCase(value:String):String
{
- return value.toLowerCase().replace(' ', "-");
+ return value.toLowerCase().replace(' ', '-');
}
/**
@@ -46,13 +76,30 @@ class StringTools
*/
public static function toUpperKebabCase(value:String):String
{
- return value.toUpperCase().replace(' ', "-");
+ return value.toUpperCase().replace(' ', '-');
+ }
+
+ /**
+ * The regular expression to sanitize strings.
+ */
+ static final SANTIZE_REGEX:EReg = ~/[^-a-zA-Z0-9]/g;
+
+ /**
+ * Remove all instances of symbols other than alpha-numeric characters (and dashes)from a string.
+ * @param value The string to sanitize.
+ * @return The sanitized string.
+ */
+ public static function sanitize(value:String):String
+ {
+ return SANTIZE_REGEX.replace(value, '');
}
/**
* Parses the string data as JSON and returns the resulting object.
* This is here so you can use `string.parseJSON()` when `using StringTools`.
*
+ * TODO: Remove this and replace with `json2object`
+ * @param value The
* @return The parsed object.
*/
public static function parseJSON(value:String):Dynamic