diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml
index 49bab1ac1..e217d1f18 100644
--- a/.github/workflows/build-shit.yml
+++ b/.github/workflows/build-shit.yml
@@ -13,8 +13,9 @@ jobs:
apt update
apt install -y sudo git curl unzip
- name: Fix git config on posix runner
+ # this can't be {{ github.workspace }} because that's not docker-aware
run: |
- git config --global --add safe.directory ${{ github.workspace }}
+ git config --global --add safe.directory $GITHUB_WORKSPACE
- name: Get checkout token
uses: actions/create-github-app-token@v1
id: app_token
@@ -90,8 +91,9 @@ jobs:
runs-on: [self-hosted, macos]
steps:
- name: Fix git config on posix runner
+ # this can't be {{ github.workspace }} because that's not docker-aware
run: |
- git config --global --add safe.directory ${{ github.workspace }}
+ git config --global --add safe.directory $GITHUB_WORKSPACE
- name: Get checkout token
uses: actions/create-github-app-token@v1
id: app_token
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 84af3a3fd..13a1862d2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -204,6 +204,21 @@
"label": "HTML5 / Debug (Watch)",
"target": "html5",
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
+ },
+ {
+ "label": "macOS / Debug",
+ "target": "mac",
+ "args": ["-debug", "-DFORCE_DEBUG_VERSION"]
+ },
+ {
+ "label": "macOS / Release",
+ "target": "mac",
+ "args": ["-release"]
+ },
+ {
+ "label": "macOS / Release (GitHub Actions)",
+ "target": "mac",
+ "args": ["-release", "-DGITHUB_BUILD"]
}
],
"cmake.configureOnOpen": false,
diff --git a/Project.xml b/Project.xml
index ffc8382a4..c0da3c89a 100644
--- a/Project.xml
+++ b/Project.xml
@@ -4,12 +4,19 @@
-
+
+
+
+
-
+
@@ -95,6 +102,7 @@
+
diff --git a/art b/art
index 03e7c2a23..00463685f 160000
--- a/art
+++ b/art
@@ -1 +1 @@
-Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34
+Subproject commit 00463685fa570f0c853d08e250b46ef80f30bc48
diff --git a/assets b/assets
index 526743915..485243fdd 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 52674391511577300cdb8c08df293ea72099aa82
+Subproject commit 485243fdd44acbc4db6a97ec7bf10a8b18350be9
diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx
index 5b207c045..d8ee7fdc0 100644
--- a/source/funkin/Conductor.hx
+++ b/source/funkin/Conductor.hx
@@ -38,7 +38,15 @@ class Conductor
* You can also do stuff like store a reference to the Conductor and pass it around or temporarily replace it,
* or have a second Conductor running at the same time, or other weird stuff like that if you need to.
*/
- public static var instance:Conductor = new Conductor();
+ public static var instance(get, never):Conductor;
+
+ static var _instance:Null = null;
+
+ static function get_instance():Conductor
+ {
+ if (_instance == null) _instance = new Conductor();
+ return _instance;
+ }
/**
* Signal fired when the current Conductor instance advances to a new measure.
@@ -597,6 +605,6 @@ class Conductor
*/
public static function reset():Void
{
- Conductor.instance = new Conductor();
+ _instance = new Conductor();
}
}
diff --git a/source/funkin/Preloader.hx b/source/funkin/Preloader.hx
deleted file mode 100644
index 157015ba6..000000000
--- a/source/funkin/Preloader.hx
+++ /dev/null
@@ -1,65 +0,0 @@
-package funkin;
-
-import flash.Lib;
-import flash.display.Bitmap;
-import flash.display.BitmapData;
-import flash.display.Sprite;
-import flixel.system.FlxBasePreloader;
-import openfl.display.Sprite;
-import funkin.util.CLIUtil;
-import openfl.text.TextField;
-import openfl.text.TextFormat;
-import flixel.system.FlxAssets;
-
-@:bitmap('art/preloaderArt.png')
-class LogoImage extends BitmapData {}
-
-class Preloader extends FlxBasePreloader
-{
- public function new(MinDisplayTime:Float = 0, ?AllowedURLs:Array)
- {
- super(MinDisplayTime, AllowedURLs);
-
- CLIUtil.resetWorkingDir(); // Bug fix for drag-and-drop.
- }
-
- var logo:Sprite;
- var _text:TextField;
-
- override function create():Void
- {
- this._width = Lib.current.stage.stageWidth;
- this._height = Lib.current.stage.stageHeight;
-
- _text = new TextField();
- _text.width = 500;
- _text.text = "Loading FNF";
- _text.defaultTextFormat = new TextFormat(FlxAssets.FONT_DEFAULT, 16, 0xFFFFFFFF);
- _text.embedFonts = true;
- _text.selectable = false;
- _text.multiline = false;
- _text.wordWrap = false;
- _text.autoSize = LEFT;
- _text.x = 2;
- _text.y = 2;
- addChild(_text);
-
- var ratio:Float = this._width / 2560; // This allows us to scale assets depending on the size of the screen.
-
- logo = new Sprite();
- logo.addChild(new Bitmap(new LogoImage(0, 0))); // Sets the graphic of the sprite to a Bitmap object, which uses our embedded BitmapData class.
- logo.scaleX = logo.scaleY = ratio;
- logo.x = ((this._width) / 2) - ((logo.width) / 2);
- logo.y = (this._height / 2) - ((logo.height) / 2);
- // addChild(logo); // Adds the graphic to the NMEPreloader's buffer.
-
- super.create();
- }
-
- override function update(Percent:Float):Void
- {
- _text.text = "FNF: " + Math.round(Percent * 100) + "%";
-
- super.update(Percent);
- }
-}
diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx
index c64240909..0df290feb 100644
--- a/source/funkin/audio/FunkinSound.hx
+++ b/source/funkin/audio/FunkinSound.hx
@@ -1,16 +1,18 @@
package funkin.audio;
-import flixel.sound.FlxSound;
import flixel.group.FlxGroup.FlxTypedGroup;
-import flixel.util.FlxSignal.FlxTypedSignal;
+import flixel.math.FlxMath;
+import flixel.sound.FlxSound;
import flixel.system.FlxAssets.FlxSoundAsset;
-import funkin.util.tools.ICloneable;
-import funkin.data.song.SongData.SongMusicData;
-import funkin.data.song.SongRegistry;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxSignal.FlxTypedSignal;
import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
-import flixel.math.FlxMath;
+import funkin.data.song.SongData.SongMusicData;
+import funkin.data.song.SongRegistry;
+import funkin.util.tools.ICloneable;
import openfl.Assets;
+import openfl.media.SoundMixer;
#if (openfl >= "8.0.0")
import openfl.utils.AssetType;
#end
@@ -18,6 +20,7 @@ import openfl.utils.AssetType;
/**
* A FlxSound which adds additional functionality:
* - Delayed playback via negative song position.
+ * - Easy functions for immediate playback and recycling.
*/
@:nullSafety
class FunkinSound extends FlxSound implements ICloneable
@@ -48,6 +51,11 @@ class FunkinSound extends FlxSound implements ICloneable
*/
static var pool(default, null):FlxTypedGroup = new FlxTypedGroup();
+ /**
+ * Calculate the current time of the sound.
+ * NOTE: You need to `add()` the sound to the scene for `update()` to increment the time.
+ */
+ //
public var muted(default, set):Bool = false;
function set_muted(value:Bool):Bool
@@ -286,15 +294,28 @@ class FunkinSound extends FlxSound implements ICloneable
* Creates a new `FunkinSound` object and loads it as the current music track.
*
* @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.
+ * @param params A set of additional optional parameters.
* Data should be at `music//-metadata.json`.
+ * @return Whether the music was started. `false` if music was already playing or could not be started
*/
- public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void
+ public static function playMusic(key:String, params:FunkinSoundPlayMusicParams):Bool
{
- if (!overrideExisting && FlxG.sound.music?.playing) return;
+ if (!(params.overrideExisting ?? false) && (FlxG.sound.music?.exists ?? false) && FlxG.sound.music.playing) return false;
- if (mapTimeChanges)
+ if (!(params.restartTrack ?? false) && FlxG.sound.music?.playing)
+ {
+ if (FlxG.sound.music != null && Std.isOfType(FlxG.sound.music, FunkinSound))
+ {
+ var existingSound:FunkinSound = cast FlxG.sound.music;
+ // Stop here if we would play a matching music track.
+ if (existingSound._label == Paths.music('$key/$key'))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (params?.mapTimeChanges ?? true)
{
var songMusicData:Null = SongRegistry.instance.parseMusicData(key);
// Will fall back and return null if the metadata doesn't exist or can't be parsed.
@@ -308,10 +329,27 @@ class FunkinSound extends FlxSound implements ICloneable
}
}
- FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key'));
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.fadeTween?.cancel();
+ FlxG.sound.music.stop();
+ FlxG.sound.music.kill();
+ }
- // Prevent repeat update() and onFocus() calls.
- FlxG.sound.list.remove(FlxG.sound.music);
+ var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
+ if (music != null)
+ {
+ FlxG.sound.music = music;
+
+ // Prevent repeat update() and onFocus() calls.
+ FlxG.sound.list.remove(FlxG.sound.music);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
/**
@@ -326,11 +364,18 @@ class FunkinSound extends FlxSound implements ICloneable
* @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.
+ * @return A `FunkinSound` object, or `null` if the sound could not be loaded.
*/
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
+ ?onComplete:Void->Void, ?onLoad:Void->Void):Null
{
+ @:privateAccess
+ if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
+ {
+ FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.');
+ return null;
+ }
+
var sound:FunkinSound = pool.recycle(construct);
// Load the sound.
@@ -341,6 +386,10 @@ class FunkinSound extends FlxSound implements ICloneable
{
sound._label = embeddedSound;
}
+ else
+ {
+ sound._label = 'unknown';
+ }
sound.volume = volume;
sound.group = FlxG.sound.defaultSoundGroup;
@@ -350,11 +399,41 @@ class FunkinSound extends FlxSound implements ICloneable
// Call onLoad() because the sound already loaded
if (onLoad != null && sound._sound != null) onLoad();
- FlxG.sound.list.remove(FlxG.sound.music);
-
return sound;
}
+ public override function destroy():Void
+ {
+ // trace('[FunkinSound] Destroying sound "${this._label}"');
+ super.destroy();
+ FlxTween.cancelTweensOf(this);
+ this._label = 'unknown';
+ }
+
+ /**
+ * Play a sound effect once, then destroy it.
+ * @param key
+ * @param volume
+ * @return static function construct():FunkinSound
+ */
+ public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Void
+ {
+ var result = FunkinSound.load(key, volume, false, true, true, onComplete, onLoad);
+ }
+
+ /**
+ * Stop all sounds in the pool and allow them to be recycled.
+ */
+ public static function stopAllAudio(musicToo:Bool = false):Void
+ {
+ for (sound in pool)
+ {
+ if (sound == null) continue;
+ if (!musicToo && sound == FlxG.sound.music) continue;
+ sound.destroy();
+ }
+ }
+
static function construct():FunkinSound
{
var sound:FunkinSound = new FunkinSound();
@@ -365,3 +444,39 @@ class FunkinSound extends FlxSound implements ICloneable
return sound;
}
}
+
+/**
+ * Additional parameters for `FunkinSound.playMusic()`
+ */
+typedef FunkinSoundPlayMusicParams =
+{
+ /**
+ * The volume you want the music to start at.
+ * @default `1.0`
+ */
+ var ?startingVolume:Float;
+
+ /**
+ * Whether to override music if a different track is already playing.
+ * @default `false`
+ */
+ var ?overrideExisting:Bool;
+
+ /**
+ * Whether to override music if the same track is already playing.
+ * @default `false`
+ */
+ var ?restartTrack:Bool;
+
+ /**
+ * Whether the music should loop or play once.
+ * @default `true`
+ */
+ var ?loop:Bool;
+
+ /**
+ * Whether to check for `SongMusicData` to update the Conductor with.
+ * @default `true`
+ */
+ var ?mapTimeChanges:Bool;
+}
diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx
index a26537c2a..020d5f5bb 100644
--- a/source/funkin/audio/SoundGroup.hx
+++ b/source/funkin/audio/SoundGroup.hx
@@ -1,7 +1,6 @@
package funkin.audio;
import flixel.group.FlxGroup.FlxTypedGroup;
-import flixel.sound.FlxSound;
import funkin.audio.FunkinSound;
import flixel.tweens.FlxTween;
@@ -153,9 +152,12 @@ class SoundGroup extends FlxTypedGroup
*/
public function stop()
{
- forEachAlive(function(sound:FunkinSound) {
- sound.stop();
- });
+ if (members != null)
+ {
+ forEachAlive(function(sound:FunkinSound) {
+ sound.stop();
+ });
+ }
}
public override function destroy()
diff --git a/source/funkin/audio/VoicesGroup.hx b/source/funkin/audio/VoicesGroup.hx
index 5daebc89d..91054cfb0 100644
--- a/source/funkin/audio/VoicesGroup.hx
+++ b/source/funkin/audio/VoicesGroup.hx
@@ -160,7 +160,9 @@ class VoicesGroup extends SoundGroup
public override function destroy():Void
{
playerVoices.destroy();
+ playerVoices = null;
opponentVoices.destroy();
+ opponentVoices = null;
super.destroy();
}
}
diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx
index ad028fa94..7419d9425 100644
--- a/source/funkin/data/BaseRegistry.hx
+++ b/source/funkin/data/BaseRegistry.hx
@@ -55,6 +55,13 @@ abstract class BaseRegistry & Constructible();
this.scriptedEntryIds = [];
+
+ // Lazy initialization of singletons should let this get called,
+ // but we have this check just in case.
+ if (FlxG.game != null)
+ {
+ FlxG.console.registerObject('registry$registryId', this);
+ }
}
/**
diff --git a/source/funkin/data/dialogue/conversation/ConversationRegistry.hx b/source/funkin/data/dialogue/conversation/ConversationRegistry.hx
index e64d62831..ca072897f 100644
--- a/source/funkin/data/dialogue/conversation/ConversationRegistry.hx
+++ b/source/funkin/data/dialogue/conversation/ConversationRegistry.hx
@@ -15,7 +15,14 @@ class ConversationRegistry extends BaseRegistry
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
- public static final instance:ConversationRegistry = new ConversationRegistry();
+ public static var instance(get, never):ConversationRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():ConversationRegistry
+ {
+ if (_instance == null) _instance = new ConversationRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/data/dialogue/dialoguebox/DialogueBoxRegistry.hx b/source/funkin/data/dialogue/dialoguebox/DialogueBoxRegistry.hx
index ec0feeaae..18f1e640a 100644
--- a/source/funkin/data/dialogue/dialoguebox/DialogueBoxRegistry.hx
+++ b/source/funkin/data/dialogue/dialoguebox/DialogueBoxRegistry.hx
@@ -15,7 +15,14 @@ class DialogueBoxRegistry extends BaseRegistry
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
- public static final instance:DialogueBoxRegistry = new DialogueBoxRegistry();
+ public static var instance(get, never):DialogueBoxRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():DialogueBoxRegistry
+ {
+ if (_instance == null) _instance = new DialogueBoxRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/data/dialogue/speaker/SpeakerRegistry.hx b/source/funkin/data/dialogue/speaker/SpeakerRegistry.hx
index 7db501d41..89144f843 100644
--- a/source/funkin/data/dialogue/speaker/SpeakerRegistry.hx
+++ b/source/funkin/data/dialogue/speaker/SpeakerRegistry.hx
@@ -15,7 +15,14 @@ class SpeakerRegistry extends BaseRegistry
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
- public static final instance:SpeakerRegistry = new SpeakerRegistry();
+ public static var instance(get, never):SpeakerRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():SpeakerRegistry
+ {
+ if (_instance == null) _instance = new SpeakerRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/data/notestyle/NoteStyleRegistry.hx b/source/funkin/data/notestyle/NoteStyleRegistry.hx
index ffb9bf490..5e9fa9a3d 100644
--- a/source/funkin/data/notestyle/NoteStyleRegistry.hx
+++ b/source/funkin/data/notestyle/NoteStyleRegistry.hx
@@ -15,7 +15,14 @@ class NoteStyleRegistry extends BaseRegistry
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
- public static final instance:NoteStyleRegistry = new NoteStyleRegistry();
+ public static var instance(get, never):NoteStyleRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():NoteStyleRegistry
+ {
+ if (_instance == null) _instance = new NoteStyleRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx
index e7f74569c..4fdf5d0df 100644
--- a/source/funkin/data/song/SongRegistry.hx
+++ b/source/funkin/data/song/SongRegistry.hx
@@ -40,10 +40,17 @@ class SongRegistry extends BaseRegistry
}
/**
- * TODO: What if there was a Singleton macro which created static functions
- * that redirected to the instance?
+ * TODO: What if there was a Singleton macro which automatically created the property for us?
*/
- public static final instance:SongRegistry = new SongRegistry();
+ public static var instance(get, never):SongRegistry;
+
+ static var _instance:Null = null;
+
+ static function get_instance():SongRegistry
+ {
+ if (_instance == null) _instance = new SongRegistry();
+ return _instance;
+ }
public function new()
{
@@ -424,7 +431,11 @@ class SongRegistry extends BaseRegistry
{
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
- if (!openfl.Assets.exists(entryFilePath)) return null;
+ if (!openfl.Assets.exists(entryFilePath))
+ {
+ trace(' [WARN] Could not locate file $entryFilePath');
+ return null;
+ }
var rawJson:Null = openfl.Assets.getText(entryFilePath);
if (rawJson == null) return null;
rawJson = rawJson.trim();
diff --git a/source/funkin/data/stage/StageRegistry.hx b/source/funkin/data/stage/StageRegistry.hx
index f18a643d2..a03371296 100644
--- a/source/funkin/data/stage/StageRegistry.hx
+++ b/source/funkin/data/stage/StageRegistry.hx
@@ -15,7 +15,14 @@ class StageRegistry extends BaseRegistry
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
- public static final instance:StageRegistry = new StageRegistry();
+ public static var instance(get, never):StageRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():StageRegistry
+ {
+ if (_instance == null) _instance = new StageRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/data/story/level/LevelRegistry.hx b/source/funkin/data/story/level/LevelRegistry.hx
index 4f33207c8..e94b5870b 100644
--- a/source/funkin/data/story/level/LevelRegistry.hx
+++ b/source/funkin/data/story/level/LevelRegistry.hx
@@ -15,7 +15,14 @@ class LevelRegistry extends BaseRegistry
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
- public static final instance:LevelRegistry = new LevelRegistry();
+ public static var instance(get, never):LevelRegistry;
+ static var _instance:Null = null;
+
+ static function get_instance():LevelRegistry
+ {
+ if (_instance == null) _instance = new LevelRegistry();
+ return _instance;
+ }
public function new()
{
diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
index c5a3a3771..5394bce1a 100644
--- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
+++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
@@ -79,6 +79,23 @@ class FlxAtlasSprite extends FlxAnimate
return this.currentAnimation;
}
+ /**
+ * `anim.finished` always returns false on looping animations,
+ * but this function will return true if we are on the last frame of the looping animation.
+ */
+ public function isLoopFinished():Bool
+ {
+ if (this.anim == null) return false;
+ if (!this.anim.isPlaying) return false;
+
+ // Reverse animation finished.
+ if (this.anim.reversed && this.anim.curFrame == 0) return true;
+ // Forward animation finished.
+ if (!this.anim.reversed && this.anim.curFrame >= (this.anim.length - 1)) return true;
+
+ return false;
+ }
+
/**
* Plays an animation.
* @param id A string ID of the animation to play.
diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx
index c53374454..9a54e4053 100644
--- a/source/funkin/modding/PolymodHandler.hx
+++ b/source/funkin/modding/PolymodHandler.hx
@@ -209,7 +209,6 @@ class PolymodHandler
// Add import aliases for certain classes.
// NOTE: Scripted classes are automatically aliased to their parent class.
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
- Polymod.addImportAlias('flixel.system.FlxSound', flixel.sound.FlxSound);
// Add blacklisting for prohibited classes and packages.
// `polymod.*`
diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx
index 747565100..10636afdf 100644
--- a/source/funkin/play/Countdown.hx
+++ b/source/funkin/play/Countdown.hx
@@ -9,6 +9,7 @@ import funkin.modding.module.ModuleHandler;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
import flixel.util.FlxTimer;
+import funkin.audio.FunkinSound;
class Countdown
{
@@ -282,7 +283,7 @@ class Countdown
if (soundPath == null) return;
- FlxG.sound.play(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
+ FunkinSound.playOnce(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
}
public static function decrement(step:CountdownStep):CountdownStep
diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx
index ec3282164..a1796e912 100644
--- a/source/funkin/play/GameOverSubState.hx
+++ b/source/funkin/play/GameOverSubState.hx
@@ -3,7 +3,6 @@ package funkin.play;
import flixel.FlxG;
import flixel.FlxObject;
import flixel.FlxSprite;
-import flixel.sound.FlxSound;
import funkin.audio.FunkinSound;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
@@ -418,7 +417,7 @@ class GameOverSubState extends MusicBeatSubState
blueballed = true;
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
{
- FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
+ FunkinSound.playOnce(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
}
else
{
@@ -438,7 +437,7 @@ class GameOverSubState extends MusicBeatSubState
if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21];
- FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() {
+ FunkinSound.playOnce(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), function() {
// Once the quote ends, fade in the game over music.
if (!isEnding && gameOverMusic != null)
{
diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx
index eae56a9c3..6abe78cd8 100644
--- a/source/funkin/play/GitarooPause.hx
+++ b/source/funkin/play/GitarooPause.hx
@@ -26,7 +26,11 @@ class GitarooPause extends MusicBeatState
override function create():Void
{
- if (FlxG.sound.music != null) FlxG.sound.music.stop();
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.destroy();
+ FlxG.sound.music = null;
+ }
var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG');
add(bg);
diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx
index 5acfcfa63..8d0aca6a8 100644
--- a/source/funkin/play/PauseSubState.hx
+++ b/source/funkin/play/PauseSubState.hx
@@ -369,7 +369,7 @@ class PauseSubState extends MusicBeatSubState
*/
function changeSelection(change:Int = 0):Void
{
- FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
+ FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
currentEntry += change;
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index a4f665329..cd5ea6342 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -1,20 +1,15 @@
package funkin.play;
-import funkin.audio.FunkinSound;
import flixel.addons.display.FlxPieDial;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.Transition;
-import flixel.addons.transition.Transition;
import flixel.FlxCamera;
import flixel.FlxObject;
import flixel.FlxState;
-import funkin.graphics.FunkinSprite;
import flixel.FlxSubState;
-import funkin.graphics.FunkinSprite;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
-import funkin.graphics.FunkinSprite;
import flixel.text.FlxText;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
@@ -22,18 +17,19 @@ import flixel.ui.FlxBar;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.api.newgrounds.NGio;
-import funkin.audio.VoicesGroup;
+import funkin.audio.FunkinSound;
import funkin.audio.VoicesGroup;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.notestyle.NoteStyleData;
import funkin.data.notestyle.NoteStyleRegistry;
-import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongData.SongCharacterData;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongRegistry;
import funkin.data.stage.StageRegistry;
+import funkin.graphics.FunkinCamera;
+import funkin.graphics.FunkinSprite;
import funkin.Highscore.Tallies;
import funkin.input.PreciseInputManager;
import funkin.modding.events.ScriptEvent;
@@ -44,14 +40,11 @@ import funkin.play.components.ComboMilestone;
import funkin.play.components.HealthIcon;
import funkin.play.components.PopUpStuff;
import funkin.play.cutscene.dialogue.Conversation;
-import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
import funkin.play.notes.NoteDirection;
import funkin.play.notes.NoteSplash;
import funkin.play.notes.NoteSprite;
-import funkin.play.notes.NoteSprite;
-import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.Strumline;
import funkin.play.notes.SustainTrail;
@@ -65,7 +58,6 @@ import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.MusicBeatSubState;
import funkin.ui.options.PreferencesMenu;
import funkin.ui.story.StoryMenuState;
-import funkin.graphics.FunkinCamera;
import funkin.ui.transition.LoadingState;
import funkin.util.SerializerUtil;
import haxe.Int64;
@@ -1293,16 +1285,35 @@ class PlayState extends MusicBeatSubState
currentStage = null;
}
- // Stop the instrumental.
- if (FlxG.sound.music != null)
+ if (!overrideMusic)
{
- FlxG.sound.music.stop();
- }
+ // Stop the instrumental.
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.destroy();
+ FlxG.sound.music = null;
+ }
- // Stop the vocals.
- if (vocals != null && vocals.exists)
+ // Stop the vocals.
+ if (vocals != null && vocals.exists)
+ {
+ vocals.destroy();
+ vocals = null;
+ }
+ }
+ else
{
- vocals.stop();
+ // Stop the instrumental.
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.stop();
+ }
+
+ // Stop the vocals.
+ if (vocals != null && vocals.exists)
+ {
+ vocals.stop();
+ }
}
super.debug_refreshModules();
@@ -1353,7 +1364,10 @@ class PlayState extends MusicBeatSubState
}
// Only zoom camera if we are zoomed by less than 35%.
- if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0)
+ if (Preferences.zoomCamera
+ && FlxG.camera.zoom < (1.35 * defaultCameraZoom)
+ && cameraZoomRate > 0
+ && Conductor.instance.currentBeat % cameraZoomRate == 0)
{
// Zoom camera in (1.5%)
currentCameraZoom += cameraZoomIntensity * defaultCameraZoom;
@@ -1899,20 +1913,26 @@ class PlayState extends MusicBeatSubState
currentChart.playInst(1.0, false);
}
+ if (FlxG.sound.music == null)
+ {
+ FlxG.log.error('PlayState failed to initialize instrumental!');
+ return;
+ }
+
FlxG.sound.music.onComplete = endSong.bind(false);
// A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
FlxG.sound.music.pitch = playbackRate;
- // I am going insane.
+ // Prevent the volume from being wrong.
FlxG.sound.music.volume = 1.0;
-
FlxG.sound.music.fadeTween?.cancel();
trace('Playing vocals...');
add(vocals);
vocals.play();
+ vocals.volume = 1.0;
vocals.pitch = playbackRate;
resyncVocals();
@@ -2089,8 +2109,7 @@ class PlayState extends MusicBeatSubState
holdNote.handledMiss = true;
// We dropped a hold note.
- // Mute vocals and play miss animation, but don't penalize.
- vocals.opponentVolume = 0;
+ // Play miss animation, but don't penalize.
currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true);
}
}
@@ -2428,7 +2447,7 @@ class PlayState extends MusicBeatSubState
if (playSound)
{
vocals.playerVolume = 0;
- FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
+ FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6));
}
}
@@ -2483,7 +2502,7 @@ class PlayState extends MusicBeatSubState
if (event.playSound)
{
vocals.playerVolume = 0;
- FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
+ FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2));
}
}
@@ -2766,7 +2785,11 @@ class PlayState extends MusicBeatSubState
if (targetSongId == null)
{
- FunkinSound.playMusic('freakyMenu');
+ FunkinSound.playMusic('freakyMenu',
+ {
+ overrideExisting: true,
+ restartTrack: false
+ });
// transIn = FlxTransitionableState.defaultTransIn;
// transOut = FlxTransitionableState.defaultTransOut;
@@ -2844,7 +2867,7 @@ class PlayState extends MusicBeatSubState
camHUD.visible = false;
isInCutscene = true;
- FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() {
+ FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() {
// no camFollow so it centers on horror tree
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
LoadingState.loadPlayState(
@@ -2904,6 +2927,9 @@ class PlayState extends MusicBeatSubState
// If the camera is being tweened, stop it.
cancelAllCameraTweens();
+ // Dispatch the destroy event.
+ dispatchEvent(new ScriptEvent(DESTROY, false));
+
if (currentConversation != null)
{
remove(currentConversation);
@@ -2918,7 +2944,7 @@ class PlayState extends MusicBeatSubState
if (overrideMusic)
{
// Stop the music. Do NOT destroy it, something still references it!
- FlxG.sound.music.pause();
+ if (FlxG.sound.music != null) FlxG.sound.music.pause();
if (vocals != null)
{
vocals.pause();
@@ -2928,7 +2954,7 @@ class PlayState extends MusicBeatSubState
else
{
// Stop and destroy the music.
- FlxG.sound.music.pause();
+ if (FlxG.sound.music != null) FlxG.sound.music.pause();
if (vocals != null)
{
vocals.destroy();
@@ -2941,7 +2967,6 @@ class PlayState extends MusicBeatSubState
{
remove(currentStage);
currentStage.kill();
- dispatchEvent(new ScriptEvent(DESTROY, false));
currentStage = null;
}
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index a78d61583..821f4ba3c 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -13,6 +13,7 @@ import flixel.text.FlxBitmapText;
import flixel.tweens.FlxEase;
import funkin.ui.freeplay.FreeplayState;
import flixel.tweens.FlxTween;
+import funkin.audio.FunkinSound;
import flixel.util.FlxGradient;
import flixel.util.FlxTimer;
import funkin.graphics.shaders.LeftMaskShader;
@@ -48,9 +49,13 @@ class ResultState extends MusicBeatSubState
else
resultsVariation = NORMAL;
- var loops:Bool = resultsVariation != SHIT;
-
- FlxG.sound.playMusic(Paths.music("results" + resultsVariation), 1, loops);
+ FunkinSound.playMusic('results$resultsVariation',
+ {
+ startingVolume: 1.0,
+ overrideExisting: true,
+ restartTrack: true,
+ loop: resultsVariation != SHIT
+ });
// TEMP-ish, just used to sorta "cache" the 3000x3000 image!
var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
@@ -104,7 +109,7 @@ class ResultState extends MusicBeatSubState
add(gf);
var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
- boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false);
+ boyfriend.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
boyfriend.visible = false;
boyfriend.animation.finishCallback = function(_) {
boyfriend.animation.play('fall', true, false, 14);
@@ -159,7 +164,7 @@ class ResultState extends MusicBeatSubState
add(blackTopBar);
var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
- resultsAnim.animation.addByPrefix("result", "results", 24, false);
+ resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false);
resultsAnim.animation.play("result");
add(resultsAnim);
@@ -348,9 +353,12 @@ class ResultState extends MusicBeatSubState
if (controls.PAUSE)
{
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
- FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, {onComplete: _ -> {
- FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
- }});
+ FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
+ {
+ onComplete: _ -> {
+ FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
+ }
+ });
if (params.storyMode)
{
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx
index 9e7aa98bf..f1dadf3e2 100644
--- a/source/funkin/play/character/AnimateAtlasCharacter.hx
+++ b/source/funkin/play/character/AnimateAtlasCharacter.hx
@@ -76,10 +76,17 @@ class AnimateAtlasCharacter extends BaseCharacter
{
trace('Creating Animate Atlas character: ' + this.characterId);
- var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
- setSprite(atlasSprite);
+ try
+ {
+ var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
+ setSprite(atlasSprite);
- loadAnimations();
+ loadAnimations();
+ }
+ catch (e)
+ {
+ throw "Exception thrown while building FlxAtlasSprite: " + e;
+ }
super.onCreate(event);
}
diff --git a/source/funkin/play/components/ComboMilestone.hx b/source/funkin/play/components/ComboMilestone.hx
index 4119e45c2..22ce2d671 100644
--- a/source/funkin/play/components/ComboMilestone.hx
+++ b/source/funkin/play/components/ComboMilestone.hx
@@ -4,6 +4,7 @@ import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.util.FlxTimer;
+import funkin.audio.FunkinSound;
class ComboMilestone extends FlxTypedSpriteGroup
{
@@ -78,7 +79,7 @@ class ComboMilestone extends FlxTypedSpriteGroup
function setupCombo(daCombo:Int)
{
- FlxG.sound.play(Paths.sound('comboSound'));
+ FunkinSound.playOnce(Paths.sound('comboSound'));
wasComboSetup = true;
var loopNum:Int = 0;
diff --git a/source/funkin/play/cutscene/VanillaCutscenes.hx b/source/funkin/play/cutscene/VanillaCutscenes.hx
index a332d0795..7a4349e6a 100644
--- a/source/funkin/play/cutscene/VanillaCutscenes.hx
+++ b/source/funkin/play/cutscene/VanillaCutscenes.hx
@@ -4,6 +4,7 @@ import flixel.FlxSprite;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
+import funkin.audio.FunkinSound;
import flixel.util.FlxTimer;
/**
@@ -40,7 +41,7 @@ class VanillaCutscenes
FlxG.camera.zoom = 2.5;
// Play the Sound effect.
- FlxG.sound.play(Paths.sound('Lights_Turn_On'), function() {
+ FunkinSound.playOnce(Paths.sound('Lights_Turn_On'), function() {
// Fade in the HUD.
trace('SFX done...');
PlayState.instance.camHUD.visible = true;
diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx
index ff56e0919..3da51185f 100644
--- a/source/funkin/play/cutscene/VideoCutscene.hx
+++ b/source/funkin/play/cutscene/VideoCutscene.hx
@@ -5,6 +5,7 @@ import flixel.FlxSprite;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxColor;
+import flixel.util.FlxSignal;
import flixel.util.FlxTimer;
#if html5
import funkin.graphics.video.FlxVideo;
@@ -28,6 +29,31 @@ class VideoCutscene
static var vid:FlxVideoSprite;
#end
+ /**
+ * Called when the video is started.
+ */
+ public static final onVideoStarted:FlxSignal = new FlxSignal();
+
+ /**
+ * Called if the video is paused.
+ */
+ public static final onVideoPaused:FlxSignal = new FlxSignal();
+
+ /**
+ * Called if the video is resumed.
+ */
+ public static final onVideoResumed:FlxSignal = new FlxSignal();
+
+ /**
+ * Called if the video is restarted. onVideoStarted is not called.
+ */
+ public static final onVideoRestarted:FlxSignal = new FlxSignal();
+
+ /**
+ * Called when the video is ended or skipped.
+ */
+ public static final onVideoEnded:FlxSignal = new FlxSignal();
+
/**
* Play a video cutscene.
* TODO: Currently this is hardcoded to start the countdown after the video is done.
@@ -94,6 +120,8 @@ class VideoCutscene
PlayState.instance.add(vid);
PlayState.instance.refresh();
+
+ onVideoStarted.dispatch();
}
else
{
@@ -129,6 +157,8 @@ class VideoCutscene
vid.y = 0;
// vid.scale.set(0.5, 0.5);
});
+
+ onVideoStarted.dispatch();
}
else
{
@@ -143,6 +173,7 @@ class VideoCutscene
if (vid != null)
{
vid.restartVideo();
+ onVideoRestarted.dispatch();
}
#end
@@ -156,6 +187,8 @@ class VideoCutscene
// Resume the video if it was paused.
vid.resume();
}
+
+ onVideoRestarted.dispatch();
}
#end
}
@@ -166,6 +199,7 @@ class VideoCutscene
if (vid != null)
{
vid.pauseVideo();
+ onVideoPaused.dispatch();
}
#end
@@ -173,6 +207,45 @@ class VideoCutscene
if (vid != null)
{
vid.pause();
+ onVideoPaused.dispatch();
+ }
+ #end
+ }
+
+ public static function hideVideo():Void
+ {
+ #if html5
+ if (vid != null)
+ {
+ vid.visible = false;
+ blackScreen.visible = false;
+ }
+ #end
+
+ #if hxCodec
+ if (vid != null)
+ {
+ vid.visible = false;
+ blackScreen.visible = false;
+ }
+ #end
+ }
+
+ public static function showVideo():Void
+ {
+ #if html5
+ if (vid != null)
+ {
+ vid.visible = true;
+ blackScreen.visible = false;
+ }
+ #end
+
+ #if hxCodec
+ if (vid != null)
+ {
+ vid.visible = true;
+ blackScreen.visible = false;
}
#end
}
@@ -183,6 +256,7 @@ class VideoCutscene
if (vid != null)
{
vid.resumeVideo();
+ onVideoResumed.dispatch();
}
#end
@@ -190,6 +264,7 @@ class VideoCutscene
if (vid != null)
{
vid.resume();
+ onVideoResumed.dispatch();
}
#end
}
@@ -240,6 +315,7 @@ class VideoCutscene
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
+ onVideoEnded.dispatch();
onCutsceneFinish(cutsceneType);
}
});
diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx
index 1b899acf5..1e399e2c8 100644
--- a/source/funkin/play/cutscene/dialogue/Conversation.hx
+++ b/source/funkin/play/cutscene/dialogue/Conversation.hx
@@ -1,28 +1,28 @@
package funkin.play.cutscene.dialogue;
-import funkin.data.IRegistryEntry;
+import flixel.addons.display.FlxPieDial;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
-import flixel.util.FlxColor;
-import funkin.graphics.FunkinSprite;
-import flixel.tweens.FlxTween;
import flixel.tweens.FlxEase;
-import flixel.sound.FlxSound;
-import funkin.util.SortUtil;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
import flixel.util.FlxSort;
-import funkin.modding.events.ScriptEvent;
-import funkin.modding.IScriptedClass.IEventHandler;
-import funkin.play.cutscene.dialogue.DialogueBox;
-import funkin.modding.IScriptedClass.IDialogueScriptedClass;
-import funkin.modding.events.ScriptEventDispatcher;
-import flixel.addons.display.FlxPieDial;
+import funkin.audio.FunkinSound;
import funkin.data.dialogue.conversation.ConversationData;
import funkin.data.dialogue.conversation.ConversationData.DialogueEntryData;
import funkin.data.dialogue.conversation.ConversationRegistry;
-import funkin.data.dialogue.speaker.SpeakerData;
-import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
+import funkin.data.dialogue.speaker.SpeakerData;
+import funkin.data.dialogue.speaker.SpeakerRegistry;
+import funkin.data.IRegistryEntry;
+import funkin.graphics.FunkinSprite;
+import funkin.modding.events.ScriptEvent;
+import funkin.modding.events.ScriptEventDispatcher;
+import funkin.modding.IScriptedClass.IDialogueScriptedClass;
+import funkin.modding.IScriptedClass.IEventHandler;
+import funkin.play.cutscene.dialogue.DialogueBox;
+import funkin.util.SortUtil;
/**
* A high-level handler for dialogue.
@@ -90,7 +90,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
/**
* AUDIO
*/
- var music:FlxSound;
+ var music:FunkinSound;
/**
* GRAPHICS
@@ -129,8 +129,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
{
if (_data.music == null) return;
- music = new FlxSound().loadEmbedded(Paths.music(_data.music.asset), true, true);
- music.volume = 0;
+ music = FunkinSound.load(Paths.music(_data.music.asset), 0.0, true, true, true);
if (_data.music.fadeTime > 0.0)
{
@@ -140,9 +139,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
{
music.volume = 1.0;
}
-
- FlxG.sound.list.add(music);
- music.play();
}
public function pauseMusic():Void
diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx
index edfb2cae7..744091b44 100644
--- a/source/funkin/play/scoring/Scoring.hx
+++ b/source/funkin/play/scoring/Scoring.hx
@@ -43,7 +43,7 @@ class Scoring
case WEEK7: scoreNoteWEEK7(msTiming);
case PBOT1: scoreNotePBOT1(msTiming);
default:
- trace('ERROR: Unknown scoring system: ' + scoringSystem);
+ FlxG.log.error('Unknown scoring system: ${scoringSystem}');
0;
}
}
@@ -62,7 +62,7 @@ class Scoring
case WEEK7: judgeNoteWEEK7(msTiming);
case PBOT1: judgeNotePBOT1(msTiming);
default:
- trace('ERROR: Unknown scoring system: ' + scoringSystem);
+ FlxG.log.error('Unknown scoring system: ${scoringSystem}');
'miss';
}
}
@@ -145,7 +145,9 @@ class Scoring
case(_ < PBOT1_PERFECT_THRESHOLD) => true:
PBOT1_MAX_SCORE;
default:
+ // Fancy equation.
var factor:Float = 1.0 - (1.0 / (1.0 + Math.exp(-PBOT1_SCORING_SLOPE * (absTiming - PBOT1_SCORING_OFFSET))));
+
var score:Int = Std.int(PBOT1_MAX_SCORE * factor + PBOT1_MIN_SCORE);
score;
@@ -169,6 +171,7 @@ class Scoring
case(_ < PBOT1_SHIT_THRESHOLD) => true:
'shit';
default:
+ FlxG.log.warn('Missed note: Bad timing ($absTiming < $PBOT1_SHIT_THRESHOLD)');
'miss';
}
}
@@ -257,6 +260,7 @@ class Scoring
case(_ < LEGACY_HIT_WINDOW * LEGACY_SHIT_THRESHOLD) => true:
'shit';
default:
+ FlxG.log.warn('Missed note: Bad timing ($absTiming < $LEGACY_SHIT_THRESHOLD)');
'miss';
}
}
@@ -336,6 +340,7 @@ class Scoring
}
else
{
+ FlxG.log.warn('Missed note: Bad timing ($absTiming < $WEEK7_HIT_WINDOW)');
return 'miss';
}
}
diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx
index 567c388c7..0248e09ee 100644
--- a/source/funkin/play/song/Song.hx
+++ b/source/funkin/play/song/Song.hx
@@ -139,7 +139,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = fetchVariationMetadata(id, vari);
- if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
+ if (variMeta != null)
+ {
+ _metadata.set(variMeta.variation, variMeta);
+ trace(' Loaded variation: $vari');
+ }
+ else
+ {
+ FlxG.log.warn('[SONG] Failed to load variation metadata (${id}:${vari}), is the path correct?');
+ trace(' FAILED to load variation: $vari');
+ }
}
}
@@ -374,12 +383,17 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry):Null
{
- if (variations == null) possibleVariations = variations;
+ if (possibleVariations == null)
+ {
+ possibleVariations = variations;
+ possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
+ }
if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0];
- for (variation in variations)
+ for (variationId in possibleVariations)
{
- if (difficulties.exists('$diffId-$variation')) return variation;
+ var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : '';
+ if (difficulties.exists('$diffId$variationSuffix')) return variationId;
}
return null;
diff --git a/source/funkin/ui/Alphabet.hx b/source/funkin/ui/Alphabet.hx
index 66b95f5b8..e0492bee5 100644
--- a/source/funkin/ui/Alphabet.hx
+++ b/source/funkin/ui/Alphabet.hx
@@ -5,6 +5,7 @@ import flixel.group.FlxSpriteGroup;
import flixel.math.FlxMath;
import flixel.util.FlxTimer;
import funkin.util.MathUtil;
+import funkin.audio.FunkinSound;
/**
* Loosley based on FlxTypeText lolol
@@ -200,7 +201,7 @@ class Alphabet extends FlxSpriteGroup
if (FlxG.random.bool(40))
{
var daSound:String = "GF_";
- FlxG.sound.play(Paths.soundRandom(daSound, 1, 4));
+ FunkinSound.playOnce(Paths.soundRandom(daSound, 1, 4));
}
add(letter);
diff --git a/source/funkin/ui/MenuList.hx b/source/funkin/ui/MenuList.hx
index 3ffe3c330..63a688778 100644
--- a/source/funkin/ui/MenuList.hx
+++ b/source/funkin/ui/MenuList.hx
@@ -5,6 +5,7 @@ import flixel.effects.FlxFlicker;
import flixel.group.FlxGroup;
import flixel.math.FlxPoint;
import flixel.util.FlxSignal;
+import funkin.audio.FunkinSound;
class MenuTypedList extends FlxTypedGroup
{
@@ -93,7 +94,7 @@ class MenuTypedList extends FlxTypedGroup
if (newIndex != selectedIndex)
{
- FlxG.sound.play(Paths.sound('scrollMenu'));
+ FunkinSound.playOnce(Paths.sound('scrollMenu'));
selectItem(newIndex);
}
@@ -163,7 +164,7 @@ class MenuTypedList extends FlxTypedGroup
else
{
busy = true;
- FlxG.sound.play(Paths.sound('confirmMenu'));
+ FunkinSound.playOnce(Paths.sound('confirmMenu'));
FlxFlicker.flicker(selected, 1, 0.06, true, false, function(_) {
busy = false;
selected.callback();
diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx
index e76bf31a7..92169df75 100644
--- a/source/funkin/ui/MusicBeatState.hx
+++ b/source/funkin/ui/MusicBeatState.hx
@@ -7,6 +7,7 @@ import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.text.FlxText;
import flixel.util.FlxColor;
+import funkin.audio.FunkinSound;
import flixel.util.FlxSort;
import funkin.modding.PolymodHandler;
import funkin.modding.events.ScriptEvent;
@@ -166,6 +167,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
}
else
{
+ FunkinSound.stopAllAudio();
+
onComplete();
}
}
diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx
index 8caf105d3..0804a83fd 100644
--- a/source/funkin/ui/debug/DebugMenuSubState.hx
+++ b/source/funkin/ui/debug/DebugMenuSubState.hx
@@ -4,6 +4,7 @@ import flixel.math.FlxPoint;
import flixel.FlxObject;
import flixel.FlxSprite;
import funkin.ui.MusicBeatSubState;
+import funkin.audio.FunkinSound;
import funkin.ui.TextMenuList;
import funkin.ui.debug.charting.ChartEditorState;
import funkin.ui.MusicBeatSubState;
@@ -76,7 +77,7 @@ class DebugMenuSubState extends MusicBeatSubState
if (controls.BACK)
{
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
exitDebugMenu();
}
}
diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx
index 46a095a65..1aa5d6e12 100644
--- a/source/funkin/ui/debug/anim/DebugBoundingState.hx
+++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx
@@ -1,32 +1,35 @@
package funkin.ui.debug.anim;
-import funkin.util.SerializerUtil;
-import funkin.play.character.CharacterData;
-import flixel.FlxCamera;
-import flixel.FlxSprite;
-import flixel.FlxState;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.ui.FlxInputText;
import flixel.addons.ui.FlxUIDropDownMenu;
+import flixel.FlxCamera;
+import flixel.FlxSprite;
+import flixel.FlxState;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFrame;
import flixel.group.FlxGroup;
import flixel.math.FlxPoint;
-import flixel.sound.FlxSound;
import flixel.text.FlxText;
import flixel.util.FlxColor;
-import funkin.util.MouseUtil;
import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer;
+import funkin.audio.FunkinSound;
+import funkin.input.Cursor;
import funkin.play.character.BaseCharacter;
+import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.SparrowCharacter;
-import haxe.ui.RuntimeComponentBuilder;
+import funkin.ui.mainmenu.MainMenuState;
+import funkin.util.MouseUtil;
+import funkin.util.SerializerUtil;
+import funkin.util.SortUtil;
import haxe.ui.components.DropDown;
import haxe.ui.core.Component;
+import haxe.ui.core.Screen;
import haxe.ui.events.ItemEvent;
import haxe.ui.events.UIEvent;
-import funkin.ui.mainmenu.MainMenuState;
+import haxe.ui.RuntimeComponentBuilder;
import lime.utils.Assets as LimeAssets;
import openfl.Assets;
import openfl.events.Event;
@@ -34,13 +37,8 @@ import openfl.events.IOErrorEvent;
import openfl.geom.Rectangle;
import openfl.net.FileReference;
import openfl.net.URLLoader;
-import funkin.ui.mainmenu.MainMenuState;
import openfl.net.URLRequest;
import openfl.utils.ByteArray;
-import funkin.input.Cursor;
-import funkin.play.character.CharacterData.CharacterDataParser;
-import funkin.util.SortUtil;
-import haxe.ui.core.Screen;
using flixel.util.FlxSpriteUtil;
@@ -179,7 +177,7 @@ class DebugBoundingState extends FlxState
var objShit = js.html.URL.createObjectURL(swagList.item(0));
trace(objShit);
- var funnysound = new FlxSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false,
+ var funnysound = new FunkinSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false,
null, function() {
trace('LOADED SHIT??');
});
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index bdc0d311e..888398f34 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -15,7 +15,6 @@ import flixel.input.mouse.FlxMouseEvent;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
-import flixel.sound.FlxSound;
import flixel.system.debug.log.LogStyle;
import flixel.system.FlxAssets.FlxSoundAsset;
import flixel.text.FlxText;
@@ -1091,7 +1090,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
* The chill audio track that plays in the chart editor.
* Plays when the main music is NOT being played.
*/
- var welcomeMusic:FlxSound = new FlxSound();
+ var welcomeMusic:FunkinSound = new FunkinSound();
/**
* The audio track for the instrumental.
@@ -3888,8 +3887,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleCursor():Void
{
// Mouse sounds
- if (FlxG.mouse.justPressed) FlxG.sound.play(Paths.sound("chartingSounds/ClickDown"));
- if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp"));
+ if (FlxG.mouse.justPressed) FunkinSound.playOnce(Paths.sound("chartingSounds/ClickDown"));
+ if (FlxG.mouse.justReleased) FunkinSound.playOnce(Paths.sound("chartingSounds/ClickUp"));
// Note: If a menu is open in HaxeUI, don't handle cursor behavior.
var shouldHandleCursor:Bool = !(isHaxeUIFocused || playbarHeadDragging || isHaxeUIDialogOpen)
@@ -4949,7 +4948,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
playbarNoteSnap.text = '1/${noteSnapQuant}';
playbarDifficulty.text = '${selectedDifficulty.toTitleCase()}';
- // playbarBPM.text = 'BPM: ${(Conductor.currentTimeChange?.bpm ?? 0.0)}';
+ playbarBPM.text = 'BPM: ${(Conductor.instance.bpm ?? 0.0)}';
}
function handlePlayhead():Void
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
index 1e1d165f3..26e246371 100644
--- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx
@@ -1,7 +1,6 @@
package funkin.ui.debug.charting.handlers;
import flixel.system.FlxAssets.FlxSoundAsset;
-import flixel.sound.FlxSound;
import funkin.audio.VoicesGroup;
import funkin.audio.FunkinSound;
import funkin.play.character.BaseCharacter.CharacterType;
@@ -302,7 +301,8 @@ class ChartEditorAudioHandler
trace('WARN: Failed to play sound $path, asset not found.');
return;
}
- var snd:FunkinSound = FunkinSound.load(asset);
+ var snd:Null = FunkinSound.load(asset);
+ if (snd == null) return;
snd.autoDestroy = true;
snd.play(true);
snd.volume = volume;
diff --git a/source/funkin/ui/debug/latency/LatencyState.hx b/source/funkin/ui/debug/latency/LatencyState.hx
index 480338e46..91cf2013b 100644
--- a/source/funkin/ui/debug/latency/LatencyState.hx
+++ b/source/funkin/ui/debug/latency/LatencyState.hx
@@ -96,7 +96,28 @@ class LatencyState extends MusicBeatSubState
localConductor.forceBPM(60);
- noteGrp = [];
+ FlxG.stage.addEventListener(KeyboardEvent.KEY_DOWN, key -> {
+ trace(key.charCode);
+
+ if (key.charCode == 120) generateBeatStuff();
+
+ trace("\tEVENT PRESS: \t" + FlxG.sound.music.time + " " + Timer.stamp());
+ // trace(FlxG.sound.music.prevTimestamp);
+ trace(FlxG.sound.music.time);
+ trace("\tFR FR PRESS: \t" + swagSong.getTimeWithDiff());
+
+ // trace("\tREDDIT: \t" + swagSong.frfrTime + " " + Timer.stamp());
+ @:privateAccess
+ trace("\tREDDIT: \t" + FlxG.sound.music._channel.position + " " + Timer.stamp());
+ // trace("EVENT LISTENER: " + key);
+ });
+
+ // funnyStatsGraph.hi
+
+ Conductor.instance.forceBPM(60);
+
+ noteGrp = new FlxTypedGroup();
+ add(noteGrp);
diffGrp = new FlxTypedGroup();
add(diffGrp);
@@ -313,6 +334,18 @@ class LatencyState extends MusicBeatSubState
{
close();
}
+ noteGrp.forEach(function(daNote:NoteSprite) {
+ daNote.y = (strumLine.y - ((Conductor.instance.songPosition - Conductor.instance.instrumentalOffset) - daNote.noteData.time) * 0.45);
+ daNote.x = strumLine.x + 30;
+
+ if (daNote.y < strumLine.y) daNote.alpha = 0.5;
+
+ if (daNote.y < 0 - daNote.height)
+ {
+ daNote.alpha = 1;
+ // daNote.data.strumTime += Conductor.instance.beatLengthMs * 8;
+ }
+ });
super.update(elapsed);
}
diff --git a/source/funkin/ui/debug/stage/StageBuilderState.hx b/source/funkin/ui/debug/stage/StageBuilderState.hx
index 074914f58..b556b9fde 100644
--- a/source/funkin/ui/debug/stage/StageBuilderState.hx
+++ b/source/funkin/ui/debug/stage/StageBuilderState.hx
@@ -37,8 +37,6 @@ class StageBuilderState extends MusicBeatState
FlxG.mouse.visible = true;
- // var alsoSnd:FlxSound = new FlxSound();
-
// snd = new Sound();
// var swagBytes:ByteArray = new ByteArray(8192);
diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx
index 55f43d2ef..33f264301 100644
--- a/source/funkin/ui/freeplay/DJBoyfriend.hx
+++ b/source/funkin/ui/freeplay/DJBoyfriend.hx
@@ -4,8 +4,9 @@ import flixel.FlxSprite;
import flixel.util.FlxSignal;
import funkin.util.assets.FlxAnimationUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
-import flixel.sound.FlxSound;
+import funkin.audio.FunkinSound;
import flixel.util.FlxTimer;
+import funkin.audio.FunkinSound;
import funkin.audio.FlxStreamSound;
class DJBoyfriend extends FlxAtlasSprite
@@ -26,8 +27,8 @@ class DJBoyfriend extends FlxAtlasSprite
var gotSpooked:Bool = false;
- static final SPOOK_PERIOD:Float = 120.0;
- static final TV_PERIOD:Float = 180.0;
+ static final SPOOK_PERIOD:Float = 10.0;
+ static final TV_PERIOD:Float = 10.0;
// Time since dad last SPOOKED you.
var timeSinceSpook:Float = 0;
@@ -48,7 +49,6 @@ class DJBoyfriend extends FlxAtlasSprite
};
setupAnimations();
- trace(listAnimations());
FlxG.debugger.track(this);
FlxG.console.registerObject("dj", this);
@@ -87,20 +87,21 @@ class DJBoyfriend extends FlxAtlasSprite
timeSinceSpook = 0;
case Idle:
// We are in this state the majority of the time.
- if (getCurrentAnimation() != 'Boyfriend DJ' || anim.finished)
+ if (getCurrentAnimation() != 'Boyfriend DJ')
{
- if (timeSinceSpook > SPOOK_PERIOD && !gotSpooked)
+ playFlashAnimation('Boyfriend DJ', true);
+ }
+
+ if (getCurrentAnimation() == 'Boyfriend DJ' && this.isLoopFinished())
+ {
+ if (timeSinceSpook >= SPOOK_PERIOD && !gotSpooked)
{
currentState = Spook;
}
- else if (timeSinceSpook > TV_PERIOD)
+ else if (timeSinceSpook >= TV_PERIOD)
{
currentState = TV;
}
- else
- {
- playFlashAnimation('Boyfriend DJ', false);
- }
}
timeSinceSpook += elapsed;
case Confirm:
@@ -111,6 +112,7 @@ class DJBoyfriend extends FlxAtlasSprite
{
onSpook.dispatch();
playFlashAnimation('bf dj afk', false);
+ gotSpooked = true;
}
timeSinceSpook = 0;
case TV:
@@ -119,6 +121,34 @@ class DJBoyfriend extends FlxAtlasSprite
default:
// I shit myself.
}
+
+ if (FlxG.keys.pressed.CONTROL)
+ {
+ if (FlxG.keys.justPressed.LEFT)
+ {
+ this.offsetX -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
+ }
+
+ if (FlxG.keys.justPressed.RIGHT)
+ {
+ this.offsetX += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
+ }
+
+ if (FlxG.keys.justPressed.UP)
+ {
+ this.offsetY -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
+ }
+
+ if (FlxG.keys.justPressed.DOWN)
+ {
+ this.offsetY += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
+ }
+
+ if (FlxG.keys.justPressed.SPACE)
+ {
+ currentState = (currentState == Idle ? TV : Idle);
+ }
+ }
}
function onFinishAnim():Void
@@ -139,11 +169,15 @@ class DJBoyfriend extends FlxAtlasSprite
case "Boyfriend DJ watchin tv OG":
var frame:Int = FlxG.random.bool(33) ? 112 : 166;
- if (FlxG.random.bool(10))
+
+ // BF switches channels when the video ends, or at a 10% chance each time his idle loops.
+ if (FlxG.random.bool(5))
{
frame = 60;
// boyfriend switches channel code?
+ // runTvLogic();
}
+ trace('Replay idle: ${frame}');
anim.play("Boyfriend DJ watchin tv OG", true, false, frame);
// trace('Finished confirm');
}
@@ -152,24 +186,31 @@ class DJBoyfriend extends FlxAtlasSprite
public function resetAFKTimer():Void
{
timeSinceSpook = 0;
+ gotSpooked = false;
}
+ var offsetX:Float = 0.0;
+ var offsetY:Float = 0.0;
+
function setupAnimations():Void
{
- // animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
- addOffset('boyfriend dj intro', 8, 3);
+ // Intro
+ addOffset('boyfriend dj intro', 8.0 - 1.3, 3.0 - 0.4);
- // animation.addByPrefix('idle', "Boyfriend DJ0", 24, false);
+ // Idle
addOffset('Boyfriend DJ', 0, 0);
- // animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24, false);
+ // Confirm
addOffset('Boyfriend DJ confirm', 0, 0);
- // animation.addByPrefix('spook', "bf dj afk0", 24, false);
- addOffset('bf dj afk', 0, 0);
+ // AFK: Spook
+ addOffset('bf dj afk', 649.5, 58.5);
+
+ // AFK: TV
+ addOffset('Boyfriend DJ watchin tv OG', 0, 0);
}
- var cartoonSnd:FlxStreamSound;
+ var cartoonSnd:Null = null;
public var playingCartoon:Bool = false;
@@ -178,39 +219,47 @@ class DJBoyfriend extends FlxAtlasSprite
if (cartoonSnd == null)
{
// tv is OFF, but getting turned on
- FlxG.sound.play(Paths.sound('tv_on'));
-
- cartoonSnd = new FlxStreamSound();
- FlxG.sound.defaultSoundGroup.add(cartoonSnd);
+ // Eric got FUCKING TROLLED there is no `tv_on` or `channel_switch` sound!
+ // FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() {
+ // });
+ loadCartoon();
}
else
{
// plays it smidge after the click
- new FlxTimer().start(0.1, function(_) {
- FlxG.sound.play(Paths.sound('channel_switch'));
- });
+ // new FlxTimer().start(0.1, function(_) {
+ // // FunkinSound.playOnce(Paths.sound('channel_switch'));
+ // });
+ cartoonSnd.destroy();
+ loadCartoon();
}
- // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck"));
- // cartoonSnd.play();
- loadCartoon();
+ // loadCartoon();
}
function loadCartoon()
{
- cartoonSnd.loadEmbedded(Paths.sound(getRandomFlashToon()), false, false, function() {
+ cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() {
anim.play("Boyfriend DJ watchin tv OG", true, false, 60);
});
- cartoonSnd.play(true, FlxG.random.float(0, cartoonSnd.length));
+
+ // Fade out music to 40% volume over 1 second.
+ // This helps make the TV a bit more audible.
+ FlxG.sound.music.fadeOut(1.0, 0.4);
+
+ // Play the cartoon at a random time between the start and 5 seconds from the end.
+ cartoonSnd.time = FlxG.random.float(0, Math.max(cartoonSnd.length - (5 * Constants.MS_PER_SEC), 0.0));
}
- var cartoonList:Array = openfl.utils.Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/"));
+ final cartoonList:Array = openfl.utils.Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/"));
function getRandomFlashToon():String
{
var randomFile = FlxG.random.getObject(cartoonList);
+ // Strip folder prefix
randomFile = randomFile.replace("assets/sounds/", "");
+ // Strip file extension
randomFile = randomFile.substring(0, randomFile.length - 4);
return randomFile;
@@ -244,10 +293,31 @@ class DJBoyfriend extends FlxAtlasSprite
var daOffset = animOffsets.get(AnimName);
if (animOffsets.exists(AnimName))
{
- offset.set(daOffset[0], daOffset[1]);
+ var xValue = daOffset[0];
+ var yValue = daOffset[1];
+ if (AnimName == "Boyfriend DJ watchin tv OG")
+ {
+ xValue += offsetX;
+ yValue += offsetY;
+ }
+
+ offset.set(xValue, yValue);
}
else
+ {
offset.set(0, 0);
+ }
+ }
+
+ public override function destroy():Void
+ {
+ super.destroy();
+
+ if (cartoonSnd != null)
+ {
+ cartoonSnd.destroy();
+ cartoonSnd = null;
+ }
}
}
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 858ff922f..9536d10dc 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -174,7 +174,11 @@ class FreeplayState extends MusicBeatSubState
isDebug = true;
#end
- FunkinSound.playMusic('freakyMenu');
+ FunkinSound.playMusic('freakyMenu',
+ {
+ overrideExisting: true,
+ restartTrack: false
+ });
// Add a null entry that represents the RANDOM option
songs.push(null);
@@ -684,14 +688,6 @@ class FreeplayState extends MusicBeatSubState
if (FlxG.keys.justPressed.T) typing.hasFocus = true;
- if (FlxG.sound.music != null)
- {
- if (FlxG.sound.music.volume < 0.7)
- {
- FlxG.sound.music.volume += 0.5 * elapsed;
- }
- }
-
lerpScore = MathUtil.coolLerp(lerpScore, intendedScore, 0.2);
lerpCompletion = MathUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9);
@@ -729,9 +725,9 @@ class FreeplayState extends MusicBeatSubState
{
if (busy) return;
- var upP:Bool = controls.UI_UP_P;
- var downP:Bool = controls.UI_DOWN_P;
- var accepted:Bool = controls.ACCEPT;
+ var upP:Bool = controls.UI_UP_P && !FlxG.keys.pressed.CONTROL;
+ var downP:Bool = controls.UI_DOWN_P && !FlxG.keys.pressed.CONTROL;
+ var accepted:Bool = controls.ACCEPT && !FlxG.keys.pressed.CONTROL;
if (FlxG.onMobile)
{
@@ -805,10 +801,8 @@ class FreeplayState extends MusicBeatSubState
}
#end
- if (controls.UI_UP || controls.UI_DOWN)
+ if (!FlxG.keys.pressed.CONTROL && (controls.UI_UP || controls.UI_DOWN))
{
- spamTimer += elapsed;
-
if (spamming)
{
if (spamTimer >= 0.07)
@@ -825,7 +819,24 @@ class FreeplayState extends MusicBeatSubState
}
}
}
- else if (spamTimer >= 0.9) spamming = true;
+ else if (spamTimer >= 0.9)
+ {
+ spamming = true;
+ }
+ else if (spamTimer <= 0)
+ {
+ if (controls.UI_UP)
+ {
+ changeSelection(-1);
+ }
+ else
+ {
+ changeSelection(1);
+ }
+ }
+
+ spamTimer += elapsed;
+ dj.resetAFKTimer();
}
else
{
@@ -833,29 +844,18 @@ class FreeplayState extends MusicBeatSubState
spamTimer = 0;
}
- if (upP)
- {
- dj.resetAFKTimer();
- changeSelection(-1);
- }
- if (downP)
- {
- dj.resetAFKTimer();
- changeSelection(1);
- }
-
if (FlxG.mouse.wheel != 0)
{
dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 4));
}
- if (controls.UI_LEFT_P)
+ if (controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL)
{
dj.resetAFKTimer();
changeDiff(-1);
}
- if (controls.UI_RIGHT_P)
+ if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
{
dj.resetAFKTimer();
changeDiff(1);
@@ -867,7 +867,7 @@ class FreeplayState extends MusicBeatSubState
FlxTimer.globalManager.clear();
dj.onIntroDone.removeAll();
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
var longestTimer:Float = 0;
@@ -1013,7 +1013,14 @@ class FreeplayState extends MusicBeatSubState
// Set the difficulty star count on the right.
albumRoll.setDifficultyStars(daSong?.songRating);
- albumRoll.albumId = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID;
+
+ // Set the album graphic and play the animation if relevant.
+ var newAlbumId:String = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID;
+ if (albumRoll.albumId != newAlbumId)
+ {
+ albumRoll.albumId = newAlbumId;
+ albumRoll.playIntro();
+ }
}
// Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)
@@ -1051,7 +1058,7 @@ class FreeplayState extends MusicBeatSubState
trace('No songs available!');
busy = false;
letterSort.inputEnabled = true;
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
return;
}
@@ -1084,7 +1091,7 @@ class FreeplayState extends MusicBeatSubState
PlayStatePlaylist.campaignId = cap.songData.levelId;
// Visual and audio effects.
- FlxG.sound.play(Paths.sound('confirmMenu'));
+ FunkinSound.playOnce(Paths.sound('confirmMenu'));
dj.confirm();
new FlxTimer().start(1, function(tmr:FlxTimer) {
@@ -1126,8 +1133,7 @@ class FreeplayState extends MusicBeatSubState
function changeSelection(change:Int = 0):Void
{
- FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
- // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName));
+ FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var prevSelected:Int = curSelected;
@@ -1170,15 +1176,25 @@ class FreeplayState extends MusicBeatSubState
{
if (curSelected == 0)
{
- FlxG.sound.playMusic(Paths.music('freeplay/freeplayRandom'), 0);
+ FunkinSound.playMusic('freeplayRandom',
+ {
+ startingVolume: 0.0,
+ overrideExisting: true,
+ restartTrack: true
+ });
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
else
{
// TODO: Stream the instrumental of the selected song?
- if (prevSelected == 0)
+ var didReplace:Bool = FunkinSound.playMusic('freakyMenu',
+ {
+ startingVolume: 0.0,
+ overrideExisting: true,
+ restartTrack: false
+ });
+ if (didReplace)
{
- FunkinSound.playMusic('freakyMenu');
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
}
@@ -1214,8 +1230,8 @@ class DifficultySelector extends FlxSprite
override function update(elapsed:Float):Void
{
- if (flipX && controls.UI_RIGHT_P) moveShitDown();
- if (!flipX && controls.UI_LEFT_P) moveShitDown();
+ if (flipX && controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL) moveShitDown();
+ if (!flipX && controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL) moveShitDown();
super.update(elapsed);
}
diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx
index c20d81328..bffa821b3 100644
--- a/source/funkin/ui/freeplay/SongMenuItem.hx
+++ b/source/funkin/ui/freeplay/SongMenuItem.hx
@@ -182,8 +182,6 @@ class SongMenuItem extends FlxSpriteGroup
{
var charPath:String = "freeplay/icons/";
- trace(char);
-
// TODO: Put this in the character metadata where it belongs.
// TODO: Also, can use CharacterDataParser.getCharPixelIconAsset()
switch (char)
diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx
index f4d48dba3..a8c2039ab 100644
--- a/source/funkin/ui/mainmenu/MainMenuState.hx
+++ b/source/funkin/ui/mainmenu/MainMenuState.hx
@@ -155,7 +155,11 @@ class MainMenuState extends MusicBeatState
function playMenuMusic():Void
{
- FunkinSound.playMusic('freakyMenu');
+ FunkinSound.playMusic('freakyMenu',
+ {
+ overrideExisting: true,
+ restartTrack: false
+ });
}
function resetCamStuff()
@@ -321,7 +325,7 @@ class MainMenuState extends MusicBeatState
if (controls.BACK && menuItems.enabled && !menuItems.busy)
{
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
FlxG.switchState(() -> new TitleState());
}
}
diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx
index 152d9b817..f3d6d6fe4 100644
--- a/source/funkin/ui/options/OptionsState.hx
+++ b/source/funkin/ui/options/OptionsState.hx
@@ -6,9 +6,11 @@ import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup;
import flixel.util.FlxSignal;
+import funkin.audio.FunkinSound;
import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.MusicBeatState;
import funkin.util.WindowUtil;
+import funkin.audio.FunkinSound;
import funkin.input.Controls;
class OptionsState extends MusicBeatState
@@ -144,7 +146,7 @@ class Page extends FlxGroup
{
if (canExit && controls.BACK)
{
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
exit();
}
}
diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx
index 3719704c8..5be31fa71 100644
--- a/source/funkin/ui/story/StoryMenuState.hx
+++ b/source/funkin/ui/story/StoryMenuState.hx
@@ -231,7 +231,11 @@ class StoryMenuState extends MusicBeatState
function playMenuMusic():Void
{
- FunkinSound.playMusic('freakyMenu');
+ FunkinSound.playMusic('freakyMenu',
+ {
+ overrideExisting: true,
+ restartTrack: false
+ });
}
function updateData():Void
@@ -382,7 +386,7 @@ class StoryMenuState extends MusicBeatState
if (controls.BACK && !exitingMenu && !selectedLevel)
{
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
exitingMenu = true;
FlxG.switchState(() -> new MainMenuState());
}
@@ -434,6 +438,8 @@ class StoryMenuState extends MusicBeatState
}
}
+ FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
+
updateText();
updateBackground(previousLevelId);
updateProps();
@@ -446,7 +452,11 @@ class StoryMenuState extends MusicBeatState
*/
function changeDifficulty(change:Int = 0):Void
{
- var difficultyList:Array = currentLevel.getDifficulties();
+ // "For now, NO erect in story mode" -Dave
+
+ var difficultyList:Array = Constants.DEFAULT_DIFFICULTY_LIST;
+ // Use this line to displays all difficulties
+ // var difficultyList:Array = currentLevel.getDifficulties();
var currentIndex:Int = difficultyList.indexOf(currentDifficultyId);
currentIndex += change;
@@ -473,6 +483,7 @@ class StoryMenuState extends MusicBeatState
if (hasChanged)
{
buildDifficultySprite();
+ FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
// Disable the funny music thing for now.
// funnyMusicThing();
}
@@ -511,7 +522,7 @@ class StoryMenuState extends MusicBeatState
{
if (!currentLevel.isUnlocked())
{
- FlxG.sound.play(Paths.sound('cancelMenu'));
+ FunkinSound.playOnce(Paths.sound('cancelMenu'));
return;
}
@@ -519,7 +530,7 @@ class StoryMenuState extends MusicBeatState
selectedLevel = true;
- FlxG.sound.play(Paths.sound('confirmMenu'));
+ FunkinSound.playOnce(Paths.sound('confirmMenu'));
currentLevelTitle.isFlashing = true;
diff --git a/source/funkin/ui/title/AttractState.hx b/source/funkin/ui/title/AttractState.hx
index 63a9e63ad..0af97afd9 100644
--- a/source/funkin/ui/title/AttractState.hx
+++ b/source/funkin/ui/title/AttractState.hx
@@ -22,7 +22,11 @@ class AttractState extends MusicBeatState
public override function create():Void
{
// Pause existing music.
- FlxG.sound.music.stop();
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.destroy();
+ FlxG.sound.music = null;
+ }
#if html5
playVideoHTML5(ATTRACT_VIDEO_PATH);
diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx
index 5cc7b8cc1..1a4e13ab1 100644
--- a/source/funkin/ui/title/TitleState.hx
+++ b/source/funkin/ui/title/TitleState.hx
@@ -222,10 +222,14 @@ class TitleState extends MusicBeatState
{
var shouldFadeIn = (FlxG.sound.music == null);
// Load music. Includes logic to handle BPM changes.
- FunkinSound.playMusic('freakyMenu', false, true);
- FlxG.sound.music.volume = 0;
+ FunkinSound.playMusic('freakyMenu',
+ {
+ startingVolume: 0.0,
+ overrideExisting: true,
+ restartTrack: true
+ });
// Fade from 0.0 to 0.7 over 4 seconds
- if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7);
+ if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 0.7);
}
function getIntroTextShit():Array>
@@ -323,7 +327,7 @@ class TitleState extends MusicBeatState
if (Date.now().getDay() == 5) NGio.unlockMedal(61034);
titleText.animation.play('press');
FlxG.camera.flash(FlxColor.WHITE, 1);
- FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
+ FunkinSound.playOnce(Paths.sound('confirmMenu'), 0.7);
transitioning = true;
var targetState:NextState = () -> new MainMenuState();
@@ -338,7 +342,7 @@ class TitleState extends MusicBeatState
// ngSpr??
FlxG.switchState(targetState);
});
- // FlxG.sound.play(Paths.music('titleShoot'), 0.7);
+ // FunkinSound.playOnce(Paths.music('titleShoot'), 0.7);
}
if (pressedEnter && !skippedIntro && initialized) skipIntro();
@@ -385,14 +389,12 @@ class TitleState extends MusicBeatState
{
cheatActive = true;
- FlxG.sound.playMusic(Paths.music('tutorialTitle'), 1);
-
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
add(spec);
Conductor.instance.forceBPM(190);
FlxG.camera.flash(FlxColor.WHITE, 1);
- FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
+ FunkinSound.playOnce(Paths.sound('confirmMenu'), 0.7);
}
function createCoolText(textArray:Array)
diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx
index 23b3db6a9..980c264e3 100644
--- a/source/funkin/ui/transition/LoadingState.hx
+++ b/source/funkin/ui/transition/LoadingState.hx
@@ -171,7 +171,12 @@ class LoadingState extends MusicBeatState
function onLoad():Void
{
- if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
+ // Stop the instrumental.
+ if (stopMusic && FlxG.sound.music != null)
+ {
+ FlxG.sound.music.destroy();
+ FlxG.sound.music = null;
+ }
FlxG.switchState(target);
}
@@ -200,7 +205,8 @@ class LoadingState extends MusicBeatState
// All assets preloaded, switch directly to play state (defualt on other targets).
if (shouldStopMusic && FlxG.sound.music != null)
{
- FlxG.sound.music.stop();
+ FlxG.sound.music.destroy();
+ FlxG.sound.music = null;
}
// Load and cache the song's charts.
diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx
index 981a30e09..0b5e16f97 100644
--- a/source/funkin/ui/transition/StickerSubState.hx
+++ b/source/funkin/ui/transition/StickerSubState.hx
@@ -18,6 +18,7 @@ import flixel.addons.transition.FlxTransitionableState;
import openfl.display.BitmapData;
import funkin.ui.freeplay.FreeplayState;
import openfl.geom.Matrix;
+import funkin.audio.FunkinSound;
import openfl.display.Sprite;
import openfl.display.Bitmap;
import flixel.FlxState;
@@ -137,7 +138,7 @@ class StickerSubState extends MusicBeatSubState
new FlxTimer().start(sticker.timing, _ -> {
sticker.visible = false;
var daSound:String = FlxG.random.getObject(sounds);
- FlxG.sound.play(Paths.sound(daSound));
+ FunkinSound.playOnce(Paths.sound(daSound));
if (grpStickers == null || ind == grpStickers.members.length - 1)
{
@@ -227,7 +228,7 @@ class StickerSubState extends MusicBeatSubState
sticker.visible = true;
var daSound:String = FlxG.random.getObject(sounds);
- FlxG.sound.play(Paths.sound(daSound));
+ FunkinSound.playOnce(Paths.sound(daSound));
var frameTimer:Int = FlxG.random.int(0, 2);
diff --git a/source/funkin/ui/transition/preload/FunkinPreloader.hx b/source/funkin/ui/transition/preload/FunkinPreloader.hx
new file mode 100644
index 000000000..89a8c1140
--- /dev/null
+++ b/source/funkin/ui/transition/preload/FunkinPreloader.hx
@@ -0,0 +1,994 @@
+package funkin.ui.transition.preload;
+
+import openfl.events.MouseEvent;
+import flash.display.Bitmap;
+import flash.display.BitmapData;
+import flash.display.BlendMode;
+import flash.display.Sprite;
+import flash.Lib;
+import flixel.system.FlxBasePreloader;
+import funkin.modding.PolymodHandler;
+import funkin.play.character.CharacterData.CharacterDataParser;
+import funkin.util.MathUtil;
+import lime.app.Future;
+import lime.math.Rectangle;
+import openfl.display.Sprite;
+import openfl.text.TextField;
+import openfl.text.TextFormat;
+import openfl.text.TextFormatAlign;
+
+using StringTools;
+
+// Annotation embeds the asset in the executable for faster loading.
+// Polymod can't override this, so we can't use this technique elsewhere.
+
+@:bitmap("art/preloaderArt.png")
+class LogoImage extends BitmapData {}
+
+#if TOUCH_HERE_TO_PLAY
+@:bitmap('art/touchHereToPlay.png')
+class TouchHereToPlayImage extends BitmapData {}
+#end
+
+/**
+ * This preloader displays a logo while the game downloads assets.
+ */
+class FunkinPreloader extends FlxBasePreloader
+{
+ /**
+ * The logo image width at the base resolution.
+ * Scaled up/down appropriately as needed.
+ */
+ static final BASE_WIDTH:Float = 1280;
+
+ /**
+ * Margin at the sides and bottom, around the loading bar.
+ */
+ static final BAR_PADDING:Float = 20;
+
+ static final BAR_HEIGHT:Int = 20;
+
+ /**
+ * Logo takes this long (in seconds) to fade in.
+ */
+ static final LOGO_FADE_TIME:Float = 2.5;
+
+ // Ratio between window size and BASE_WIDTH
+ var ratio:Float = 0;
+
+ var currentState:FunkinPreloaderState = FunkinPreloaderState.NotStarted;
+
+ // private var downloadingAssetsStartTime:Float = -1;
+ private var downloadingAssetsPercent:Float = -1;
+ private var downloadingAssetsComplete:Bool = false;
+
+ private var preloadingPlayAssetsPercent:Float = -1;
+ private var preloadingPlayAssetsStartTime:Float = -1;
+ private var preloadingPlayAssetsComplete:Bool = false;
+
+ private var cachingGraphicsPercent:Float = -1;
+ private var cachingGraphicsStartTime:Float = -1;
+ private var cachingGraphicsComplete:Bool = false;
+
+ private var cachingAudioPercent:Float = -1;
+ private var cachingAudioStartTime:Float = -1;
+ private var cachingAudioComplete:Bool = false;
+
+ private var cachingDataPercent:Float = -1;
+ private var cachingDataStartTime:Float = -1;
+ private var cachingDataComplete:Bool = false;
+
+ private var parsingSpritesheetsPercent:Float = -1;
+ private var parsingSpritesheetsStartTime:Float = -1;
+ private var parsingSpritesheetsComplete:Bool = false;
+
+ private var parsingStagesPercent:Float = -1;
+ private var parsingStagesStartTime:Float = -1;
+ private var parsingStagesComplete:Bool = false;
+
+ private var parsingCharactersPercent:Float = -1;
+ private var parsingCharactersStartTime:Float = -1;
+ private var parsingCharactersComplete:Bool = false;
+
+ private var parsingSongsPercent:Float = -1;
+ private var parsingSongsStartTime:Float = -1;
+ private var parsingSongsComplete:Bool = false;
+
+ private var initializingScriptsPercent:Float = -1;
+
+ private var cachingCoreAssetsPercent:Float = -1;
+
+ /**
+ * The timestamp when the other steps completed and the `Finishing up` step started.
+ */
+ private var completeTime:Float = -1;
+
+ // Graphics
+ var logo:Bitmap;
+ #if TOUCH_HERE_TO_PLAY
+ var touchHereToPlay:Bitmap;
+ #end
+ var progressBar:Bitmap;
+ var progressLeftText:TextField;
+ var progressRightText:TextField;
+
+ public function new()
+ {
+ super(Constants.PRELOADER_MIN_STAGE_TIME, Constants.SITE_LOCK);
+
+ // We can't even call trace() yet, until Flixel loads.
+ trace('Initializing custom preloader...');
+
+ this.siteLockTitleText = Constants.SITE_LOCK_TITLE;
+ this.siteLockBodyText = Constants.SITE_LOCK_DESC;
+ }
+
+ override function create():Void
+ {
+ // Nothing happens in the base preloader.
+ super.create();
+
+ // Background color.
+ Lib.current.stage.color = Constants.COLOR_PRELOADER_BG;
+
+ // Width and height of the preloader.
+ this._width = Lib.current.stage.stageWidth;
+ this._height = Lib.current.stage.stageHeight;
+
+ // Scale assets to the screen size.
+ ratio = this._width / BASE_WIDTH / 2.0;
+
+ // Create the logo.
+ logo = createBitmap(LogoImage, function(bmp:Bitmap) {
+ // Scale and center the logo.
+ // We have to do this inside the async call, after the image size is known.
+ bmp.scaleX = bmp.scaleY = ratio;
+ bmp.x = (this._width - bmp.width) / 2;
+ bmp.y = (this._height - bmp.height) / 2;
+ });
+ addChild(logo);
+
+ #if TOUCH_HERE_TO_PLAY
+ touchHereToPlay = createBitmap(TouchHereToPlayImage, function(bmp:Bitmap) {
+ // Scale and center the touch to start image.
+ // We have to do this inside the async call, after the image size is known.
+ bmp.scaleX = bmp.scaleY = ratio;
+ bmp.x = (this._width - bmp.width) / 2;
+ bmp.y = (this._height - bmp.height) / 2;
+ });
+ touchHereToPlay.alpha = 0.0;
+ addChild(touchHereToPlay);
+ #end
+
+ // Create the progress bar.
+ progressBar = new Bitmap(new BitmapData(1, BAR_HEIGHT, true, Constants.COLOR_PRELOADER_BAR));
+ progressBar.x = BAR_PADDING;
+ progressBar.y = this._height - BAR_PADDING - BAR_HEIGHT;
+ addChild(progressBar);
+
+ // Create the progress message.
+ progressLeftText = new TextField();
+
+ var progressLeftTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
+ progressLeftTextFormat.align = TextFormatAlign.LEFT;
+ progressLeftText.defaultTextFormat = progressLeftTextFormat;
+
+ progressLeftText.selectable = false;
+ progressLeftText.width = this._width - BAR_PADDING * 2;
+ progressLeftText.text = 'Downloading assets...';
+ progressLeftText.x = BAR_PADDING;
+ progressLeftText.y = this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4;
+ addChild(progressLeftText);
+
+ // Create the progress %.
+ progressRightText = new TextField();
+
+ var progressRightTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
+ progressRightTextFormat.align = TextFormatAlign.RIGHT;
+ progressRightText.defaultTextFormat = progressRightTextFormat;
+
+ progressRightText.selectable = false;
+ progressRightText.width = this._width - BAR_PADDING * 2;
+ progressRightText.text = '0%';
+ progressRightText.x = BAR_PADDING;
+ progressRightText.y = this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4;
+ addChild(progressRightText);
+ }
+
+ var lastElapsed:Float = 0.0;
+
+ override function update(percent:Float):Void
+ {
+ var elapsed:Float = (Date.now().getTime() - this._startTime) / 1000.0;
+ // trace('Time since last frame: ' + (lastElapsed - elapsed));
+
+ downloadingAssetsPercent = percent;
+ var loadPercent:Float = updateState(percent, elapsed);
+ updateGraphics(loadPercent, elapsed);
+
+ lastElapsed = elapsed;
+ }
+
+ function updateState(percent:Float, elapsed:Float):Float
+ {
+ switch (currentState)
+ {
+ case FunkinPreloaderState.NotStarted:
+ if (downloadingAssetsPercent > 0.0) currentState = FunkinPreloaderState.DownloadingAssets;
+
+ return percent;
+
+ case FunkinPreloaderState.DownloadingAssets:
+ // Sometimes percent doesn't go to 100%, it's a floating point error.
+ if (downloadingAssetsPercent >= 1.0
+ || (elapsed > Constants.PRELOADER_MIN_STAGE_TIME
+ && downloadingAssetsComplete)) currentState = FunkinPreloaderState.PreloadingPlayAssets;
+
+ return percent;
+
+ case FunkinPreloaderState.PreloadingPlayAssets:
+ if (preloadingPlayAssetsPercent < 0.0)
+ {
+ preloadingPlayAssetsStartTime = elapsed;
+ preloadingPlayAssetsPercent = 0.0;
+
+ // This is quick enough to do synchronously.
+ // Assets.initialize();
+
+ /*
+ // Make a future to retrieve the manifest
+ var future:Future = Assets.preloadLibrary('gameplay');
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ preloadingPlayAssetsPercent = loaded / total;
+ });
+ future.onComplete((library:lime.utils.AssetLibrary) -> {
+ });
+ */
+
+ // TODO: Reimplement this.
+ preloadingPlayAssetsPercent = 1.0;
+ preloadingPlayAssetsComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedPreloadingPlayAssets:Float = elapsed - preloadingPlayAssetsStartTime;
+ if (preloadingPlayAssetsComplete && elapsedPreloadingPlayAssets >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.InitializingScripts;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (preloadingPlayAssetsPercent < (elapsedPreloadingPlayAssets / Constants.PRELOADER_MIN_STAGE_TIME)) return preloadingPlayAssetsPercent;
+ else
+ return elapsedPreloadingPlayAssets / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ else
+ {
+ if (preloadingPlayAssetsComplete) currentState = FunkinPreloaderState.InitializingScripts;
+ }
+
+ return preloadingPlayAssetsPercent;
+
+ case FunkinPreloaderState.InitializingScripts:
+ if (initializingScriptsPercent < 0.0)
+ {
+ initializingScriptsPercent = 0.0;
+
+ /*
+ var future:Future> = []; // PolymodHandler.loadNoModsAsync();
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ trace('PolymodHandler.loadNoModsAsync() progress: ' + loaded + '/' + total);
+ initializingScriptsPercent = loaded / total;
+ });
+ future.onComplete((result:Array) -> {
+ trace('Completed initializing scripts: ' + result);
+ });
+ */
+
+ initializingScriptsPercent = 1.0;
+ currentState = FunkinPreloaderState.CachingGraphics;
+ return 0.0;
+ }
+
+ return initializingScriptsPercent;
+
+ case CachingGraphics:
+ if (cachingGraphicsPercent < 0)
+ {
+ cachingGraphicsPercent = 0.0;
+ cachingGraphicsStartTime = elapsed;
+
+ /*
+ var assetsToCache:Array = []; // Assets.listGraphics('core');
+
+ var future:Future> = []; // Assets.cacheAssets(assetsToCache);
+ future.onProgress((loaded:Int, total:Int) -> {
+ cachingGraphicsPercent = loaded / total;
+ });
+ future.onComplete((_result) -> {
+ trace('Completed caching graphics.');
+ });
+ */
+
+ // TODO: Reimplement this.
+ cachingGraphicsPercent = 1.0;
+ cachingGraphicsComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedCachingGraphics:Float = elapsed - cachingGraphicsStartTime;
+ if (cachingGraphicsComplete && elapsedCachingGraphics >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.CachingAudio;
+ return 0.0;
+ }
+ else
+ {
+ if (cachingGraphicsPercent < (elapsedCachingGraphics / Constants.PRELOADER_MIN_STAGE_TIME))
+ {
+ // Return real progress if it's lower.
+ return cachingGraphicsPercent;
+ }
+ else
+ {
+ // Return simulated progress if it's higher.
+ return elapsedCachingGraphics / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ }
+ else
+ {
+ if (cachingGraphicsComplete)
+ {
+ currentState = FunkinPreloaderState.CachingAudio;
+ return 0.0;
+ }
+ else
+ {
+ return cachingGraphicsPercent;
+ }
+ }
+
+ case CachingAudio:
+ if (cachingAudioPercent < 0)
+ {
+ cachingAudioPercent = 0.0;
+ cachingAudioStartTime = elapsed;
+
+ var assetsToCache:Array = []; // Assets.listSound('core');
+
+ /*
+ var future:Future> = []; // Assets.cacheAssets(assetsToCache);
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ cachingAudioPercent = loaded / total;
+ });
+ future.onComplete((_result) -> {
+ trace('Completed caching audio.');
+ });
+ */
+
+ // TODO: Reimplement this.
+ cachingAudioPercent = 1.0;
+ cachingAudioComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedCachingAudio:Float = elapsed - cachingAudioStartTime;
+ if (cachingAudioComplete && elapsedCachingAudio >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.CachingData;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (cachingAudioPercent < (elapsedCachingAudio / Constants.PRELOADER_MIN_STAGE_TIME))
+ {
+ return cachingAudioPercent;
+ }
+ else
+ {
+ return elapsedCachingAudio / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ }
+ else
+ {
+ if (cachingAudioComplete)
+ {
+ currentState = FunkinPreloaderState.CachingData;
+ return 0.0;
+ }
+ else
+ {
+ return cachingAudioPercent;
+ }
+ }
+
+ case CachingData:
+ if (cachingDataPercent < 0)
+ {
+ cachingDataPercent = 0.0;
+ cachingDataStartTime = elapsed;
+
+ var assetsToCache:Array = [];
+ var sparrowFramesToCache:Array = [];
+
+ // Core files
+ // assetsToCache = assetsToCache.concat(Assets.listText('core'));
+ // assetsToCache = assetsToCache.concat(Assets.listJSON('core'));
+ // Core spritesheets
+ // assetsToCache = assetsToCache.concat(Assets.listXML('core'));
+
+ // Gameplay files
+ // assetsToCache = assetsToCache.concat(Assets.listText('gameplay'));
+ // assetsToCache = assetsToCache.concat(Assets.listJSON('gameplay'));
+ // We're not caching gameplay spritesheets here because they're fetched on demand.
+
+ /*
+ var future:Future> = [];
+ // Assets.cacheAssets(assetsToCache, true);
+ future.onProgress((loaded:Int, total:Int) -> {
+ cachingDataPercent = loaded / total;
+ });
+ future.onComplete((_result) -> {
+ trace('Completed caching data.');
+ });
+ */
+ cachingDataPercent = 1.0;
+ cachingDataComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedCachingData:Float = elapsed - cachingDataStartTime;
+ if (cachingDataComplete && elapsedCachingData >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.ParsingSpritesheets;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (cachingDataPercent < (elapsedCachingData / Constants.PRELOADER_MIN_STAGE_TIME)) return cachingDataPercent;
+ else
+ return elapsedCachingData / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ else
+ {
+ if (cachingDataComplete)
+ {
+ currentState = FunkinPreloaderState.ParsingSpritesheets;
+ return 0.0;
+ }
+ }
+
+ return cachingDataPercent;
+
+ case ParsingSpritesheets:
+ if (parsingSpritesheetsPercent < 0)
+ {
+ parsingSpritesheetsPercent = 0.0;
+ parsingSpritesheetsStartTime = elapsed;
+
+ // Core spritesheets
+ var sparrowFramesToCache = []; // Assets.listXML('core').map((xml:String) -> xml.replace('.xml', '').replace('core:assets/core/', ''));
+ // We're not caching gameplay spritesheets here because they're fetched on demand.
+
+ /*
+ var future:Future> = []; // Assets.cacheSparrowFrames(sparrowFramesToCache, true);
+ future.onProgress((loaded:Int, total:Int) -> {
+ parsingSpritesheetsPercent = loaded / total;
+ });
+ future.onComplete((_result) -> {
+ trace('Completed parsing spritesheets.');
+ });
+ */
+ parsingSpritesheetsPercent = 1.0;
+ parsingSpritesheetsComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedParsingSpritesheets:Float = elapsed - parsingSpritesheetsStartTime;
+ if (parsingSpritesheetsComplete && elapsedParsingSpritesheets >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.ParsingStages;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (parsingSpritesheetsPercent < (elapsedParsingSpritesheets / Constants.PRELOADER_MIN_STAGE_TIME)) return parsingSpritesheetsPercent;
+ else
+ return elapsedParsingSpritesheets / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ else
+ {
+ if (parsingSpritesheetsComplete)
+ {
+ currentState = FunkinPreloaderState.ParsingStages;
+ return 0.0;
+ }
+ }
+
+ return parsingSpritesheetsPercent;
+
+ case ParsingStages:
+ if (parsingStagesPercent < 0)
+ {
+ parsingStagesPercent = 0.0;
+ parsingStagesStartTime = elapsed;
+
+ /*
+ // TODO: Reimplement this.
+ var future:Future> = []; // StageDataParser.loadStageCacheAsync();
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ parsingStagesPercent = loaded / total;
+ });
+
+ future.onComplete((_result) -> {
+ trace('Completed parsing stages.');
+ });
+ */
+
+ parsingStagesPercent = 1.0;
+ parsingStagesComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedParsingStages:Float = elapsed - parsingStagesStartTime;
+ if (parsingStagesComplete && elapsedParsingStages >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.ParsingCharacters;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (parsingStagesPercent < (elapsedParsingStages / Constants.PRELOADER_MIN_STAGE_TIME)) return parsingStagesPercent;
+ else
+ return elapsedParsingStages / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ else
+ {
+ if (parsingStagesComplete)
+ {
+ currentState = FunkinPreloaderState.ParsingCharacters;
+ return 0.0;
+ }
+ }
+
+ return parsingStagesPercent;
+
+ case ParsingCharacters:
+ if (parsingCharactersPercent < 0)
+ {
+ parsingCharactersPercent = 0.0;
+ parsingCharactersStartTime = elapsed;
+
+ /*
+ // TODO: Reimplement this.
+ var future:Future> = []; // CharacterDataParser.loadCharacterCacheAsync();
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ parsingCharactersPercent = loaded / total;
+ });
+
+ future.onComplete((_result) -> {
+ trace('Completed parsing characters.');
+ });
+ */
+
+ parsingCharactersPercent = 1.0;
+ parsingCharactersComplete = true;
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedParsingCharacters:Float = elapsed - parsingCharactersStartTime;
+ if (parsingCharactersComplete && elapsedParsingCharacters >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.ParsingSongs;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (parsingCharactersPercent < (elapsedParsingCharacters / Constants.PRELOADER_MIN_STAGE_TIME)) return parsingCharactersPercent;
+ else
+ return elapsedParsingCharacters / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ else
+ {
+ if (parsingStagesComplete)
+ {
+ currentState = FunkinPreloaderState.ParsingSongs;
+ return 0.0;
+ }
+ }
+
+ return parsingCharactersPercent;
+
+ case ParsingSongs:
+ if (parsingSongsPercent < 0)
+ {
+ parsingSongsPercent = 0.0;
+ parsingSongsStartTime = elapsed;
+
+ /*
+ // TODO: Reimplement this.
+ var future:Future> = ;
+ // SongDataParser.loadSongCacheAsync();
+
+ future.onProgress((loaded:Int, total:Int) -> {
+ parsingSongsPercent = loaded / total;
+ });
+
+ future.onComplete((_result) -> {
+ trace('Completed parsing songs.');
+ });
+ */
+
+ parsingSongsPercent = 1.0;
+ parsingSongsComplete = true;
+
+ return 0.0;
+ }
+ else if (Constants.PRELOADER_MIN_STAGE_TIME > 0)
+ {
+ var elapsedParsingSongs:Float = elapsed - parsingSongsStartTime;
+ if (parsingSongsComplete && elapsedParsingSongs >= Constants.PRELOADER_MIN_STAGE_TIME)
+ {
+ currentState = FunkinPreloaderState.Complete;
+ return 0.0;
+ }
+ else
+ {
+ // We need to return SIMULATED progress here.
+ if (parsingSongsPercent < (elapsedParsingSongs / Constants.PRELOADER_MIN_STAGE_TIME))
+ {
+ return parsingSongsPercent;
+ }
+ else
+ {
+ return elapsedParsingSongs / Constants.PRELOADER_MIN_STAGE_TIME;
+ }
+ }
+ }
+ else
+ {
+ if (parsingSongsComplete)
+ {
+ currentState = FunkinPreloaderState.Complete;
+ return 0.0;
+ }
+ else
+ {
+ return parsingSongsPercent;
+ }
+ }
+ case FunkinPreloaderState.Complete:
+ if (completeTime < 0)
+ {
+ completeTime = elapsed;
+ }
+
+ return 1.0;
+ #if TOUCH_HERE_TO_PLAY
+ case FunkinPreloaderState.TouchHereToPlay:
+ if (completeTime < 0)
+ {
+ completeTime = elapsed;
+ }
+
+ if (touchHereToPlay.alpha < 1.0)
+ {
+ touchHereToPlay.alpha = 1.0;
+
+ addEventListener(MouseEvent.CLICK, onTouchHereToPlay);
+ }
+
+ return 1.0;
+ #end
+
+ default:
+ // Do nothing.
+ }
+
+ return 0.0;
+ }
+
+ #if TOUCH_HERE_TO_PLAY
+ function onTouchHereToPlay(e:MouseEvent):Void
+ {
+ removeEventListener(MouseEvent.CLICK, onTouchHereToPlay);
+
+ // This is the actual thing that makes the game load.
+ immediatelyStartGame();
+ }
+ #end
+
+ static final TOTAL_STEPS:Int = 11;
+ static final ELLIPSIS_TIME:Float = 0.5;
+
+ function updateGraphics(percent:Float, elapsed:Float):Void
+ {
+ // Render logo (including transitions)
+ if (completeTime > 0.0)
+ {
+ var elapsedFinished:Float = renderLogoFadeOut(elapsed);
+ // trace('Fading out logo... (' + elapsedFinished + 's)');
+ if (elapsedFinished > LOGO_FADE_TIME)
+ {
+ #if TOUCH_HERE_TO_PLAY
+ // The logo has faded out, but we're not quite done yet.
+ // In order to prevent autoplay issues, we need the user to click after the loading finishes.
+ currentState = FunkinPreloaderState.TouchHereToPlay;
+ #else
+ immediatelyStartGame();
+ #end
+ }
+ }
+ else
+ {
+ renderLogoFadeIn(elapsed);
+ }
+
+ // Render progress bar
+ var maxWidth = this._width - BAR_PADDING * 2;
+ var barWidth = maxWidth * percent;
+ progressBar.width = barWidth;
+
+ // Cycle ellipsis count to show loading
+ var ellipsisCount:Int = Std.int(elapsed / ELLIPSIS_TIME) % 3 + 1;
+ var ellipsis:String = '';
+ for (i in 0...ellipsisCount)
+ ellipsis += '.';
+
+ // Render status text
+ switch (currentState)
+ {
+ // case FunkinPreloaderState.NotStarted:
+ default:
+ updateProgressLeftText('Loading (0/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.DownloadingAssets:
+ updateProgressLeftText('Downloading assets (1/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.PreloadingPlayAssets:
+ updateProgressLeftText('Preloading assets (2/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.InitializingScripts:
+ updateProgressLeftText('Initializing scripts (3/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.CachingGraphics:
+ updateProgressLeftText('Caching graphics (4/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.CachingAudio:
+ updateProgressLeftText('Caching audio (5/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.CachingData:
+ updateProgressLeftText('Caching data (6/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.ParsingSpritesheets:
+ updateProgressLeftText('Parsing spritesheets (7/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.ParsingStages:
+ updateProgressLeftText('Parsing stages (8/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.ParsingCharacters:
+ updateProgressLeftText('Parsing characters (9/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.ParsingSongs:
+ updateProgressLeftText('Parsing songs (10/$TOTAL_STEPS)$ellipsis');
+ case FunkinPreloaderState.Complete:
+ updateProgressLeftText('Finishing up ($TOTAL_STEPS/$TOTAL_STEPS)$ellipsis');
+ #if TOUCH_HERE_TO_PLAY
+ case FunkinPreloaderState.TouchHereToPlay:
+ updateProgressLeftText(null);
+ #end
+ }
+
+ var percentage:Int = Math.floor(percent * 100);
+ trace('Preloader state: ' + currentState + ' (' + percentage + '%, ' + elapsed + 's)');
+
+ // Render percent text
+ progressRightText.text = '$percentage%';
+
+ super.update(percent);
+ }
+
+ function updateProgressLeftText(text:Null):Void
+ {
+ if (progressLeftText != null)
+ {
+ if (text == null)
+ {
+ progressLeftText.alpha = 0.0;
+ }
+ else if (progressLeftText.text != text)
+ {
+ // We have to keep updating the text format, because the font can take a frame or two to load.
+ var progressLeftTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
+ progressLeftTextFormat.align = TextFormatAlign.LEFT;
+ progressLeftText.defaultTextFormat = progressLeftTextFormat;
+ progressLeftText.text = text;
+ }
+ }
+ }
+
+ function immediatelyStartGame():Void
+ {
+ _loaded = true;
+ }
+
+ /**
+ * Fade out the logo.
+ * @param elapsed Elapsed time since the preloader started.
+ * @return Elapsed time since the logo started fading out.
+ */
+ function renderLogoFadeOut(elapsed:Float):Float
+ {
+ // Fade-out takes LOGO_FADE_TIME seconds.
+ var elapsedFinished = elapsed - completeTime;
+
+ logo.alpha = 1.0 - MathUtil.easeInOutCirc(elapsedFinished / LOGO_FADE_TIME);
+ logo.scaleX = (1.0 - MathUtil.easeInBack(elapsedFinished / LOGO_FADE_TIME)) * ratio;
+ logo.scaleY = (1.0 - MathUtil.easeInBack(elapsedFinished / LOGO_FADE_TIME)) * ratio;
+ logo.x = (this._width - logo.width) / 2;
+ logo.y = (this._height - logo.height) / 2;
+
+ // Fade out progress bar too.
+ progressBar.alpha = logo.alpha;
+ progressLeftText.alpha = logo.alpha;
+ progressRightText.alpha = logo.alpha;
+
+ return elapsedFinished;
+ }
+
+ function renderLogoFadeIn(elapsed:Float):Void
+ {
+ // Fade-in takes LOGO_FADE_TIME seconds.
+ logo.alpha = MathUtil.easeInOutCirc(elapsed / LOGO_FADE_TIME);
+ logo.scaleX = MathUtil.easeOutBack(elapsed / LOGO_FADE_TIME) * ratio;
+ logo.scaleY = MathUtil.easeOutBack(elapsed / LOGO_FADE_TIME) * ratio;
+ logo.x = (this._width - logo.width) / 2;
+ logo.y = (this._height - logo.height) / 2;
+ }
+
+ #if html5
+ // These fields only exist on Web builds.
+
+ /**
+ * Format the layout of the site lock screen.
+ */
+ override function createSiteLockFailureScreen():Void
+ {
+ addChild(createSiteLockFailureBackground(Constants.COLOR_PRELOADER_LOCK_BG, Constants.COLOR_PRELOADER_LOCK_BG));
+ addChild(createSiteLockFailureIcon(Constants.COLOR_PRELOADER_LOCK_FG, 0.9));
+ addChild(createSiteLockFailureText(30));
+ }
+
+ /**
+ * Format the text of the site lock screen.
+ */
+ override function adjustSiteLockTextFields(titleText:TextField, bodyText:TextField, hyperlinkText:TextField):Void
+ {
+ var titleFormat = titleText.defaultTextFormat;
+ titleFormat.align = TextFormatAlign.CENTER;
+ titleFormat.color = Constants.COLOR_PRELOADER_LOCK_FONT;
+ titleText.setTextFormat(titleFormat);
+
+ var bodyFormat = bodyText.defaultTextFormat;
+ bodyFormat.align = TextFormatAlign.CENTER;
+ bodyFormat.color = Constants.COLOR_PRELOADER_LOCK_FONT;
+ bodyText.setTextFormat(bodyFormat);
+
+ var hyperlinkFormat = hyperlinkText.defaultTextFormat;
+ hyperlinkFormat.align = TextFormatAlign.CENTER;
+ hyperlinkFormat.color = Constants.COLOR_PRELOADER_LOCK_LINK;
+ hyperlinkText.setTextFormat(hyperlinkFormat);
+ }
+ #end
+
+ override function destroy():Void
+ {
+ // Ensure the graphics are properly destroyed and GC'd.
+ removeChild(logo);
+ removeChild(progressBar);
+ logo = progressBar = null;
+ super.destroy();
+ }
+
+ override function onLoaded():Void
+ {
+ super.onLoaded();
+ // We're not ACTUALLY finished.
+ // This function gets called when the DownloadingAssets step is done.
+ // We need to wait for the other steps, then the logo to fade out.
+ _loaded = false;
+ downloadingAssetsComplete = true;
+ }
+}
+
+enum FunkinPreloaderState
+{
+ /**
+ * The state before downloading has begun.
+ * Moves to either `DownloadingAssets` or `CachingGraphics` based on platform.
+ */
+ NotStarted;
+
+ /**
+ * Downloading assets.
+ * On HTML5, Lime will do this for us, before calling `onLoaded`.
+ * On Desktop, this step will be completed immediately, and we'll go straight to `CachingGraphics`.
+ */
+ DownloadingAssets;
+
+ /**
+ * Preloading play assets.
+ * Loads the `manifest.json` for the `gameplay` library.
+ * If we make the base preloader do this, it will download all the assets as well,
+ * so we have to do it ourselves.
+ */
+ PreloadingPlayAssets;
+
+ /**
+ * Loading FireTongue, loading Polymod, parsing and instantiating module scripts.
+ */
+ InitializingScripts;
+
+ /**
+ * Loading all graphics from the `core` library to the cache.
+ */
+ CachingGraphics;
+
+ /**
+ * Loading all audio from the `core` library to the cache.
+ */
+ CachingAudio;
+
+ /**
+ * Loading all data files from the `core` library to the cache.
+ */
+ CachingData;
+
+ /**
+ * Parsing all XML files from the `core` library into FlxFramesCollections and caching them.
+ */
+ ParsingSpritesheets;
+
+ /**
+ * Parsing stage data and scripts.
+ */
+ ParsingStages;
+
+ /**
+ * Parsing character data and scripts.
+ */
+ ParsingCharacters;
+
+ /**
+ * Parsing song data and scripts.
+ */
+ ParsingSongs;
+
+ /**
+ * Finishing up.
+ */
+ Complete;
+
+ #if TOUCH_HERE_TO_PLAY
+ /**
+ * Touch Here to Play is displayed.
+ */
+ TouchHereToPlay;
+ #end
+}
diff --git a/source/funkin/ui/transition/preload/README.md b/source/funkin/ui/transition/preload/README.md
new file mode 100644
index 000000000..91efcf8e8
--- /dev/null
+++ b/source/funkin/ui/transition/preload/README.md
@@ -0,0 +1,17 @@
+# funkin.ui.loading.preload
+
+This package contains code powering the HTML5 preloader screen.
+
+The preloader performs the following tasks:
+- **Downloading assets**: Downloads the `core` asset library and loads its manifest
+- **Preloading play assets**: Downloads the `gameplay` asset library (manifest only)
+- **Initializing scripts**: Downloads and registers stage scripts, character scripts, song scripts, and module scripts.
+- **Caching graphics**: Downloads all graphics from the `core` asset library, uploads them to the GPU, then dumps them from RAM. This prepares them to be used very quickly in-game.
+- **Caching audio**: Downloads all audio files from the `core` asset library, and caches them. This prepares them to be used very quickly in-game.
+- **Caching data**: Downloads and caches all TXT files, all JSON files (it also parses them), and XML files (from the `core` library only). This prepares them to be used in the next steps.
+- **Parsing stages**: Parses all stage data and instantiates associated stage scripts. This prepares them to be used in-game.
+- **Parsing characters**: Parses all character data and instantiates associated character scripts. This prepares them to be used in-game.
+- **Parsing songs**: Parses all song data and instantiates associated song scripts. This prepares them to be used in-game.
+- **Finishing up**: Waits for the screen to fade out. Then, it loads the first state of the app.
+
+Due to the first few steps not being relevant on desktop, and due to this preloader being built in Lime rather than HaxeFlixel because of how Lime handles asset loading, this preloader is not used on desktop. The splash loader is used instead.
diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx
index d939f3194..3f22a49d3 100644
--- a/source/funkin/util/Constants.hx
+++ b/source/funkin/util/Constants.hx
@@ -1,8 +1,9 @@
package funkin.util;
+import flixel.system.FlxBasePreloader;
import flixel.util.FlxColor;
-import lime.app.Application;
import funkin.data.song.SongData.SongTimeFormat;
+import lime.app.Application;
/**
* A store of unchanging, globally relevant values.
@@ -59,6 +60,16 @@ class Constants
*/
// ==============================
+ /**
+ * Preloader sitelock.
+ * Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work.
+ * The first link in this list is the one users will be redirected to if they try to access the game from a different URL.
+ */
+ public static final SITE_LOCK:Array = [
+ "https://www.newgrounds.com/portal/view/770371", // Newgrounds, baybee!
+ FlxBasePreloader.LOCAL // localhost for dev stuff
+ ];
+
/**
* Link to download the game on Itch.io.
*/
@@ -116,6 +127,44 @@ class Constants
0xFFCC1111 // right (3)
];
+ /**
+ * Color for the preloader background
+ */
+ public static final COLOR_PRELOADER_BG:FlxColor = 0xFF000000;
+
+ /**
+ * Color for the preloader progress bar
+ */
+ public static final COLOR_PRELOADER_BAR:FlxColor = 0xFF00FF00;
+
+ /**
+ * Color for the preloader site lock background
+ */
+ public static final COLOR_PRELOADER_LOCK_BG:FlxColor = 0xFF1B1717;
+
+ /**
+ * Color for the preloader site lock foreground
+ */
+ public static final COLOR_PRELOADER_LOCK_FG:FlxColor = 0xB96F10;
+
+ /**
+ * Color for the preloader site lock text
+ */
+ public static final COLOR_PRELOADER_LOCK_FONT:FlxColor = 0xCCCCCC;
+
+ /**
+ * Color for the preloader site lock link
+ */
+ public static final COLOR_PRELOADER_LOCK_LINK:FlxColor = 0xEEB211;
+
+ /**
+ * LANGUAGE
+ */
+ // ==============================
+ public static final SITE_LOCK_TITLE:String = "You Loser!";
+
+ public static final SITE_LOCK_DESC:String = "This isn't Newgrounds!\nGo play Friday Night Funkin' on Newgrounds:";
+
/**
* GAME DEFAULTS
*/
@@ -157,6 +206,11 @@ class Constants
*/
public static final DEFAULT_VARIATION:String = 'default';
+ /**
+ * Standard variations used by the game.
+ */
+ public static final DEFAULT_VARIATION_LIST:Array = ['default', 'erect', 'pico'];
+
/**
* The default intensity for camera zooms.
*/
@@ -286,6 +340,19 @@ class Constants
*/
public static final MP3_DELAY_MS:Float = 528 / 44100 * Constants.MS_PER_SEC;
+ /**
+ * Each step of the preloader has to be on screen at least this long.
+ *
+ * 0 = The preloader immediately moves to the next step when it's ready.
+ * 1 = The preloader waits for 1 second before moving to the next step.
+ * The progress bare is automatically rescaled to match.
+ */
+ #if debug
+ public static final PRELOADER_MIN_STAGE_TIME:Float = 1.0;
+ #else
+ public static final PRELOADER_MIN_STAGE_TIME:Float = 0.1;
+ #end
+
/**
* HEALTH VALUES
*/
diff --git a/source/funkin/util/MathUtil.hx b/source/funkin/util/MathUtil.hx
index 532e92f15..93b9ca666 100644
--- a/source/funkin/util/MathUtil.hx
+++ b/source/funkin/util/MathUtil.hx
@@ -19,6 +19,7 @@ class MathUtil
*
* @return The interpolated value.
*/
+ @:deprecated('Use smoothLerp instead')
public static function coolLerp(base:Float, target:Float, ratio:Float):Float
{
return base + cameraLerp(ratio) * (target - base);
@@ -47,6 +48,36 @@ class MathUtil
return Math.log(value) / Math.log(base);
}
+ public static function easeInOutCirc(x:Float):Float
+ {
+ if (x <= 0.0) return 0.0;
+ if (x >= 1.0) return 1.0;
+ var result:Float = (x < 0.5) ? (1 - Math.sqrt(1 - 4 * x * x)) / 2 : (Math.sqrt(1 - 4 * (1 - x) * (1 - x)) + 1) / 2;
+ return (result == Math.NaN) ? 1.0 : result;
+ }
+
+ public static function easeInOutBack(x:Float, ?c:Float = 1.70158):Float
+ {
+ if (x <= 0.0) return 0.0;
+ if (x >= 1.0) return 1.0;
+ var result:Float = (x < 0.5) ? (2 * x * x * ((c + 1) * 2 * x - c)) / 2 : (1 - 2 * (1 - x) * (1 - x) * ((c + 1) * 2 * (1 - x) - c)) / 2;
+ return (result == Math.NaN) ? 1.0 : result;
+ }
+
+ public static function easeInBack(x:Float, ?c:Float = 1.70158):Float
+ {
+ if (x <= 0.0) return 0.0;
+ if (x >= 1.0) return 1.0;
+ return (1 + c) * x * x * x - c * x * x;
+ }
+
+ public static function easeOutBack(x:Float, ?c:Float = 1.70158):Float
+ {
+ if (x <= 0.0) return 0.0;
+ if (x >= 1.0) return 1.0;
+ return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2);
+ }
+
/**
* Get the base-2 logarithm of a value.
* @param x value
diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx
index d7e8109b8..9ac21d4b8 100644
--- a/source/funkin/util/plugins/ScreenshotPlugin.hx
+++ b/source/funkin/util/plugins/ScreenshotPlugin.hx
@@ -174,7 +174,7 @@ class ScreenshotPlugin extends FlxBasic
FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)});
// Play a sound (auto-play is true).
- FunkinSound.load(Paths.sound('screenshot'), 1.0, false, true, true);
+ FunkinSound.playOnce(Paths.sound('screenshot'), 1.0);
}
static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in.