Merge remote-tracking branch 'origin/rewrite/master' into feature/conductor-signal-rework

This commit is contained in:
EliteMasterEric 2024-03-27 01:52:58 -04:00
commit bb98c710a3
72 changed files with 910 additions and 619 deletions

View file

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

View file

@ -4,6 +4,7 @@ export
# Ignore all JSONS in the images folder (including FlxAnimate JSONs)
assets/preload/images
assets/shared/images
assets/weekend1/images
# Don't ignore data files
# TODO: These don't work.

15
.vscode/settings.json vendored
View file

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

2
assets

@ -1 +1 @@
Subproject commit 7cbe6ff4ed7d976e7c69d6677c4aa84988da0e8d
Subproject commit 8013845e331015b40c4cc35230f6d02bd2148d52

View file

@ -79,7 +79,7 @@
{
"props": {
"ignoreExtern": true,
"format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$",
"format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$",
"tokens": ["INLINE", "NOTINLINE"]
},
"type": "ConstantName"

View file

@ -11,9 +11,11 @@ import openfl.display.Sprite;
import openfl.events.Event;
import openfl.Lib;
import openfl.media.Video;
import funkin.util.CLIUtil;
import openfl.net.NetStream;
/**
* The main class which initializes HaxeFlixel and starts the game in its initial state.
*/
class Main extends Sprite
{
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
@ -76,26 +78,27 @@ class Main extends Sprite
var netStream:NetStream;
var overlay:Sprite;
/**
* A frame counter displayed at the top left.
*/
public static var fpsCounter:FPS;
/**
* A RAM counter displayed at the top left.
*/
public static var memoryCounter:MemoryCounter;
function setupGame():Void
{
/**
* The `zoom` argument of FlxGame was removed in the dev branch of Flixel,
* since it was considered confusing and unintuitive.
* If you want to change how the game scales when you resize the window,
* you can use `FlxG.scaleMode`.
* -Eric
*/
initHaxeUI();
// addChild gets called by the user settings code.
fpsCounter = new FPS(10, 3, 0xFFFFFF);
// addChild(fpsCounter); // Handled by Preferences.init
#if !html5
// addChild gets called by the user settings code.
// TODO: disabled on HTML5 (todo: find another method that works?)
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
// addChild(memoryCounter);
#end
// George recommends binding the save before FlxGame is created.
@ -112,6 +115,8 @@ class Main extends Sprite
#if hxcpp_debug_server
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
#else
trace('hxcpp_debug_server is disabled! This build does not support debugging.');
#end
}

View file

@ -3,34 +3,37 @@ package source; // Yeah, I know...
import sys.FileSystem;
import sys.io.File;
/**
* A script which executes after the game is built.
*/
class Postbuild
{
static inline final buildTimeFile = '.build_time';
static inline final BUILD_TIME_FILE:String = '.build_time';
static function main()
static function main():Void
{
printBuildTime();
}
static function printBuildTime()
static function printBuildTime():Void
{
// get buildEnd before fs operations since they are blocking
var end:Float = Sys.time();
if (FileSystem.exists(buildTimeFile))
if (FileSystem.exists(BUILD_TIME_FILE))
{
var fi = File.read(buildTimeFile);
var fi:sys.io.FileInput = File.read(BUILD_TIME_FILE);
var start:Float = fi.readDouble();
fi.close();
sys.FileSystem.deleteFile(buildTimeFile);
sys.FileSystem.deleteFile(BUILD_TIME_FILE);
var buildTime = roundToTwoDecimals(end - start);
var buildTime:Float = roundToTwoDecimals(end - start);
trace('Build took: ${buildTime} seconds');
}
}
private static function roundToTwoDecimals(value:Float):Float
static function roundToTwoDecimals(value:Float):Float
{
return Math.round(value * 100) / 100;
}

View file

@ -2,20 +2,23 @@ package source; // Yeah, I know...
import sys.io.File;
/**
* A script which executes before the game is built.
*/
class Prebuild
{
static inline final buildTimeFile = '.build_time';
static inline final BUILD_TIME_FILE:String = '.build_time';
static function main()
static function main():Void
{
saveBuildTime();
trace('Building...');
}
static function saveBuildTime()
static function saveBuildTime():Void
{
var fo = File.write(buildTimeFile);
var now = Sys.time();
var fo:sys.io.FileOutput = File.write(BUILD_TIME_FILE);
var now:Float = Sys.time();
fo.writeDouble(now);
fo.close();
}

View file

@ -1,234 +0,0 @@
package flixel.addons.transition;
import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
/**
* A `FlxSubState` which can perform visual transitions
*
* Usage:
*
* First, extend `FlxTransitionableSubState` as ie, `FooState`.
*
* Method 1:
*
* ```haxe
* var in:TransitionData = new TransitionData(...); // add your data where "..." is
* var out:TransitionData = new TransitionData(...);
*
* FlxG.switchState(() -> new FooState(in,out));
* ```
*
* Method 2:
*
* ```haxe
* FlxTransitionableSubState.defaultTransIn = new TransitionData(...);
* FlxTransitionableSubState.defaultTransOut = new TransitionData(...);
*
* FlxG.switchState(() -> new FooState());
* ```
*/
class FlxTransitionableSubState extends FlxSubState
{
// global default transitions for ALL states, used if transIn/transOut are null
public static var defaultTransIn(get, set):TransitionData;
static function get_defaultTransIn():TransitionData
{
return FlxTransitionableState.defaultTransIn;
}
static function set_defaultTransIn(value:TransitionData):TransitionData
{
return FlxTransitionableState.defaultTransIn = value;
}
public static var defaultTransOut(get, set):TransitionData;
static function get_defaultTransOut():TransitionData
{
return FlxTransitionableState.defaultTransOut;
}
static function set_defaultTransOut(value:TransitionData):TransitionData
{
return FlxTransitionableState.defaultTransOut = value;
}
public static var skipNextTransIn(get, set):Bool;
static function get_skipNextTransIn():Bool
{
return FlxTransitionableState.skipNextTransIn;
}
static function set_skipNextTransIn(value:Bool):Bool
{
return FlxTransitionableState.skipNextTransIn = value;
}
public static var skipNextTransOut(get, set):Bool;
static function get_skipNextTransOut():Bool
{
return FlxTransitionableState.skipNextTransOut;
}
static function set_skipNextTransOut(value:Bool):Bool
{
return FlxTransitionableState.skipNextTransOut = value;
}
// beginning & ending transitions for THIS state:
public var transIn:TransitionData;
public var transOut:TransitionData;
public var hasTransIn(get, never):Bool;
public var hasTransOut(get, never):Bool;
/**
* Create a state with the ability to do visual transitions
* @param TransIn Plays when the state begins
* @param TransOut Plays when the state ends
*/
public function new(?TransIn:TransitionData, ?TransOut:TransitionData)
{
transIn = TransIn;
transOut = TransOut;
if (transIn == null && defaultTransIn != null)
{
transIn = defaultTransIn;
}
if (transOut == null && defaultTransOut != null)
{
transOut = defaultTransOut;
}
super();
}
override function destroy():Void
{
super.destroy();
transIn = null;
transOut = null;
_onExit = null;
}
override function create():Void
{
super.create();
transitionIn();
}
override function startOutro(onOutroComplete:() -> Void)
{
if (!hasTransOut) onOutroComplete();
else if (!_exiting)
{
// play the exit transition, and when it's done call FlxG.switchState
_exiting = true;
transitionOut(onOutroComplete);
if (skipNextTransOut)
{
skipNextTransOut = false;
finishTransOut();
}
}
}
/**
* Starts the in-transition. Can be called manually at any time.
*/
public function transitionIn():Void
{
if (transIn != null && transIn.type != NONE)
{
if (skipNextTransIn)
{
skipNextTransIn = false;
if (finishTransIn != null)
{
finishTransIn();
}
return;
}
var _trans = createTransition(transIn);
_trans.setStatus(FULL);
openSubState(_trans);
_trans.finishCallback = finishTransIn;
_trans.start(OUT);
}
}
/**
* Starts the out-transition. Can be called manually at any time.
*/
public function transitionOut(?OnExit:Void->Void):Void
{
_onExit = OnExit;
if (hasTransOut)
{
var _trans = createTransition(transOut);
_trans.setStatus(EMPTY);
openSubState(_trans);
_trans.finishCallback = finishTransOut;
_trans.start(IN);
}
else
{
_onExit();
}
}
var transOutFinished:Bool = false;
var _exiting:Bool = false;
var _onExit:Void->Void;
function get_hasTransIn():Bool
{
return transIn != null && transIn.type != NONE;
}
function get_hasTransOut():Bool
{
return transOut != null && transOut.type != NONE;
}
function createTransition(data:TransitionData):Transition
{
return switch (data.type)
{
case TILES: new Transition(data);
case FADE: new Transition(data);
default: null;
}
}
function finishTransIn()
{
closeSubState();
}
function finishTransOut()
{
transOutFinished = true;
if (!_exiting)
{
closeSubState();
}
if (_onExit != null)
{
_onExit();
}
}
}

View file

@ -3,7 +3,6 @@ package funkin;
import funkin.util.Constants;
import flixel.util.FlxSignal;
import flixel.math.FlxMath;
import funkin.play.song.Song.SongDifficulty;
import funkin.data.song.SongData.SongTimeChange;
import funkin.data.song.SongDataUtils;
@ -36,7 +35,7 @@ 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(get, set):Conductor;
public static var instance(get, never):Conductor;
static var _instance:Null<Conductor> = null;
@ -156,6 +155,9 @@ class Conductor
return beatLengthMs / timeSignatureNumerator;
}
/**
* The numerator for the current time signature (the `3` in `3/4`).
*/
public var timeSignatureNumerator(get, never):Int;
function get_timeSignatureNumerator():Int
@ -165,6 +167,9 @@ class Conductor
return currentTimeChange.timeSignatureNum;
}
/**
* The denominator for the current time signature (the `4` in `3/4`).
*/
public var timeSignatureDenominator(get, never):Int;
function get_timeSignatureDenominator():Int
@ -341,7 +346,7 @@ class Conductor
* you should have a metadata file for it instead.
* We should probably deprecate this in the future.
*/
public function forceBPM(?bpm:Float = null)
public function forceBPM(?bpm:Float):Void
{
if (bpm != null)
{
@ -349,7 +354,7 @@ class Conductor
}
else
{
// trace('[CONDUCTOR] Resetting BPM to default');
trace('[CONDUCTOR] Resetting BPM to default');
}
this.bpmOverride = bpm;
@ -362,7 +367,7 @@ class Conductor
* @param songPosition The current position in the song in milliseconds.
* Leave blank to use the `FlxG.sound.music` position.
*/
public function update(?songPos:Float)
public function update(?songPos:Float):Void
{
if (songPos == null)
{
@ -370,9 +375,9 @@ class Conductor
songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
}
var oldMeasure = this.currentMeasure;
var oldBeat = this.currentBeat;
var oldStep = this.currentStep;
var oldMeasure:Float = this.currentMeasure;
var oldBeat:Float = this.currentBeat;
var oldStep:Float = this.currentStep;
// Set the song position we are at (for purposes of calculating note positions, etc).
this.songPosition = songPos;
@ -431,42 +436,42 @@ class Conductor
}
/**
* Apply the time changes from a SongMetadata file.
* @param songTimeChanges The time changes to apply.
* Apply the `SongTimeChange` data from the song metadata to this Conductor.
* @param songTimeChanges The SongTimeChanges.
*/
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>):Void
{
timeChanges = [];
// Sort in place just in case it's out of order.
SongDataUtils.sortTimeChanges(songTimeChanges);
for (currentTimeChange in songTimeChanges)
for (songTimeChange in songTimeChanges)
{
// TODO: Maybe handle this different?
// Do we care about BPM at negative timestamps?
// Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`.
if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0;
if (songTimeChange.timeStamp < 0.0) songTimeChange.timeStamp = 0.0;
if (currentTimeChange.timeStamp <= 0.0)
if (songTimeChange.timeStamp <= 0.0)
{
currentTimeChange.beatTime = 0.0;
songTimeChange.beatTime = 0.0;
}
else
{
// Calculate the beat time of this timestamp.
currentTimeChange.beatTime = 0.0;
songTimeChange.beatTime = 0.0;
if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
if (songTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
{
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
4);
}
}
timeChanges.push(currentTimeChange);
timeChanges.push(songTimeChange);
}
if (timeChanges.length > 0)
@ -479,8 +484,9 @@ class Conductor
}
/**
* @param ms A timestamp in milliseconds.
* @return The corresponding time in steps.
* Given a time in milliseconds, return a time in steps.
* @param ms The time in milliseconds.
* @return The time in steps.
*/
public function getTimeInSteps(ms:Float):Float
{
@ -510,15 +516,16 @@ class Conductor
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
resultStep += resultFractionalStep; // Math.floor();
resultStep += resultFractionalStep;
return resultStep;
}
}
/**
* @param stepTime A timestamp in steps.
* @return The corresponding time in milliseconds.
* Given a time in steps and fractional steps, return a time in milliseconds.
* @param stepTime The time in steps.
* @return The time in milliseconds.
*/
public function getStepTimeInMs(stepTime:Float):Float
{
@ -554,8 +561,9 @@ class Conductor
}
/**
* @param beatTime A timestamp in fractional beats.
* @return The corresponding time in milliseconds.
* Given a time in beats and fractional beats, return a time in milliseconds.
* @param beatTime The time in beats.
* @return The time in milliseconds.
*/
public function getBeatTimeInMs(beatTime:Float):Float
{
@ -589,4 +597,26 @@ class Conductor
return resultMs;
}
}
/**
* Add variables of the current Conductor instance to the Flixel debugger.
*/
public static function watchQuick(?target:Conductor):Void
{
if (target == null) target = Conductor.instance;
FlxG.watch.addQuick('songPosition', target.songPosition);
FlxG.watch.addQuick('bpm', target.bpm);
FlxG.watch.addQuick('currentMeasureTime', target.currentMeasureTime);
FlxG.watch.addQuick('currentBeatTime', target.currentBeatTime);
FlxG.watch.addQuick('currentStepTime', target.currentStepTime);
}
/**
* Reset the Conductor, replacing the current instance with a fresh one.
*/
public static function reset():Void
{
_instance = new Conductor();
}
}

View file

@ -12,7 +12,6 @@ import flixel.math.FlxRect;
import flixel.FlxSprite;
import flixel.system.debug.log.LogStyle;
import flixel.util.FlxColor;
import funkin.ui.options.PreferencesMenu;
import funkin.util.macro.MacroUtil;
import funkin.util.WindowUtil;
import funkin.play.PlayStatePlaylist;
@ -32,7 +31,6 @@ import funkin.ui.title.TitleState;
import funkin.util.CLIUtil;
import funkin.util.CLIUtil.CLIParams;
import funkin.util.TimerUtil;
import funkin.ui.transition.LoadingState;
import funkin.util.TrackerUtil;
#if discord_rpc
import Discord.DiscordClient;
@ -171,8 +169,9 @@ class InitState extends FlxState
AlbumRegistry.instance.loadEntries();
StageRegistry.instance.loadEntries();
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers.
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
// Move it to use a BaseRegistry.
CharacterDataParser.loadCharacterCache();
ModuleHandler.buildModuleCallbacks();
ModuleHandler.loadModuleCache();
@ -190,25 +189,35 @@ class InitState extends FlxState
*/
function startGame():Void
{
#if SONG // -DSONG=bopeebo
#if SONG
// -DSONG=bopeebo
startSong(defineSong(), defineDifficulty());
#elseif LEVEL // -DLEVEL=week1 -DDIFFICULTY=hard
#elseif LEVEL
// -DLEVEL=week1 -DDIFFICULTY=hard
startLevel(defineLevel(), defineDifficulty());
#elseif FREEPLAY // -DFREEPLAY
#elseif FREEPLAY
// -DFREEPLAY
FlxG.switchState(() -> new funkin.ui.freeplay.FreeplayState());
#elseif DIALOGUE // -DDIALOGUE
#elseif DIALOGUE
// -DDIALOGUE
FlxG.switchState(() -> new funkin.ui.debug.dialogue.ConversationDebugState());
#elseif ANIMATE // -DANIMATE
#elseif ANIMATE
// -DANIMATE
FlxG.switchState(() -> new funkin.ui.debug.anim.FlxAnimateTest());
#elseif WAVEFORM // -DWAVEFORM
#elseif WAVEFORM
// -DWAVEFORM
FlxG.switchState(() -> new funkin.ui.debug.WaveformTestState());
#elseif CHARTING // -DCHARTING
#elseif CHARTING
// -DCHARTING
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
#elseif STAGEBUILD // -DSTAGEBUILD
#elseif STAGEBUILD
// -DSTAGEBUILD
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
#elseif ANIMDEBUG // -DANIMDEBUG
#elseif ANIMDEBUG
// -DANIMDEBUG
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
#elseif LATENCY // -DLATENCY
#elseif LATENCY
// -DLATENCY
FlxG.switchState(() -> new funkin.LatencyState());
#else
startGameNormally();

View file

@ -11,144 +11,144 @@ class Paths
{
static var currentLevel:String;
static public function setCurrentLevel(name:String)
public static function setCurrentLevel(name:String):Void
{
currentLevel = name.toLowerCase();
}
public static function stripLibrary(path:String):String
{
var parts = path.split(':');
var parts:Array<String> = path.split(':');
if (parts.length < 2) return path;
return parts[1];
}
public static function getLibrary(path:String):String
{
var parts = path.split(':');
if (parts.length < 2) return "preload";
var parts:Array<String> = path.split(':');
if (parts.length < 2) return 'preload';
return parts[0];
}
static function getPath(file:String, type:AssetType, library:Null<String>)
static function getPath(file:String, type:AssetType, library:Null<String>):String
{
if (library != null) return getLibraryPath(file, library);
if (currentLevel != null)
{
var levelPath = getLibraryPathForce(file, currentLevel);
var levelPath:String = getLibraryPathForce(file, currentLevel);
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
}
var levelPath = getLibraryPathForce(file, "shared");
var levelPath:String = getLibraryPathForce(file, 'shared');
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
return getPreloadPath(file);
}
static public function getLibraryPath(file:String, library = "preload")
public static function getLibraryPath(file:String, library = 'preload'):String
{
return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library);
return if (library == 'preload' || library == 'default') getPreloadPath(file); else getLibraryPathForce(file, library);
}
inline static function getLibraryPathForce(file:String, library:String)
static inline function getLibraryPathForce(file:String, library:String):String
{
return '$library:assets/$library/$file';
}
inline static function getPreloadPath(file:String)
static inline function getPreloadPath(file:String):String
{
return 'assets/$file';
}
inline static public function file(file:String, type:AssetType = TEXT, ?library:String)
public static function file(file:String, type:AssetType = TEXT, ?library:String):String
{
return getPath(file, type, library);
}
public static inline function animateAtlas(path:String, ?library:String)
public static function animateAtlas(path:String, ?library:String):String
{
return getLibraryPath('images/$path', library);
}
inline static public function txt(key:String, ?library:String)
public static function txt(key:String, ?library:String):String
{
return getPath('data/$key.txt', TEXT, library);
}
inline static public function frag(key:String, ?library:String)
public static function frag(key:String, ?library:String):String
{
return getPath('shaders/$key.frag', TEXT, library);
}
inline static public function vert(key:String, ?library:String)
public static function vert(key:String, ?library:String):String
{
return getPath('shaders/$key.vert', TEXT, library);
}
inline static public function xml(key:String, ?library:String)
public static function xml(key:String, ?library:String):String
{
return getPath('data/$key.xml', TEXT, library);
}
inline static public function json(key:String, ?library:String)
public static function json(key:String, ?library:String):String
{
return getPath('data/$key.json', TEXT, library);
}
static public function sound(key:String, ?library:String)
public static function sound(key:String, ?library:String):String
{
return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library);
}
inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String)
public static function soundRandom(key:String, min:Int, max:Int, ?library:String):String
{
return sound(key + FlxG.random.int(min, max), library);
}
inline static public function music(key:String, ?library:String)
public static function music(key:String, ?library:String):String
{
return getPath('music/$key.${Constants.EXT_SOUND}', MUSIC, library);
}
inline static public function videos(key:String, ?library:String)
public static function videos(key:String, ?library:String):String
{
return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library);
}
inline static public function voices(song:String, ?suffix:String = '')
public static function voices(song:String, ?suffix:String = ''):String
{
if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files
if (suffix == null) suffix = ''; // no suffix, for a sorta backwards compatibility with older-ish voice files
return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}';
}
inline static public function inst(song:String, ?suffix:String = '')
public static function inst(song:String, ?suffix:String = ''):String
{
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}';
}
inline static public function image(key:String, ?library:String)
public static function image(key:String, ?library:String):String
{
return getPath('images/$key.png', IMAGE, library);
}
inline static public function font(key:String)
public static function font(key:String):String
{
return 'assets/fonts/$key';
}
inline static public function ui(key:String, ?library:String)
public static function ui(key:String, ?library:String):String
{
return xml('ui/$key', library);
}
static public function getSparrowAtlas(key:String, ?library:String)
public static function getSparrowAtlas(key:String, ?library:String):FlxAtlasFrames
{
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
}
inline static public function getPackerAtlas(key:String, ?library:String)
public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames
{
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
}

View file

@ -13,6 +13,7 @@ import flixel.util.FlxSignal;
*/
class PlayerSettings
{
// TODO: Finish implementation of second player.
public static var numPlayers(default, null) = 0;
public static var numAvatars(default, null) = 0;
public static var player1(default, null):PlayerSettings;
@ -21,12 +22,21 @@ class PlayerSettings
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
public static var onAvatarRemove(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
/**
* The player number associated with this settings object.
*/
public var id(default, null):Int;
/**
* The controls handler for this player.
*/
public var controls(default, null):Controls;
/**
* Return the PlayerSettings for the given player number, or `null` if that player isn't active.
*
* @param id The player number this represents.
* @return The PlayerSettings for the given player number, or `null` if that player isn't active.
*/
public static function get(id:Int):Null<PlayerSettings>
{
@ -38,6 +48,9 @@ class PlayerSettings
};
}
/**
* Initialize the PlayerSettings singletons for each player.
*/
public static function init():Void
{
if (player1 == null)
@ -56,22 +69,30 @@ class PlayerSettings
}
}
public static function reset()
/**
* Forcibly destroy the PlayerSettings singletons for each player.
*/
public static function reset():Void
{
player1 = null;
player2 = null;
numPlayers = 0;
}
static function onGamepadAdded(gamepad:FlxGamepad)
/**
* Callback invoked when a gamepad is added.
* @param gamepad The gamepad that was added.
*/
static function onGamepadAdded(gamepad:FlxGamepad):Void
{
// TODO: Make this detect and handle multiple players
player1.addGamepad(gamepad);
}
/**
* @param id The player number this represents. This was refactored to START AT `1`.
*/
private function new(id:Int)
function new(id:Int)
{
trace('loading player settings for id: $id');
@ -83,11 +104,11 @@ class PlayerSettings
function addKeyboard():Void
{
var useDefault = true;
var useDefault:Bool = true;
if (Save.instance.hasControls(id, Keys))
{
var keyControlData = Save.instance.getControls(id, Keys);
trace("keyControlData: " + haxe.Json.stringify(keyControlData));
trace('Loading keyboard control scheme from user save');
useDefault = false;
controls.fromSaveData(keyControlData, Keys);
}
@ -98,7 +119,7 @@ class PlayerSettings
if (useDefault)
{
trace("Loading default keyboard control scheme");
trace('Loading default keyboard control scheme');
controls.setKeyboardScheme(Solo);
}
@ -109,13 +130,13 @@ class PlayerSettings
* Called after an FlxGamepad has been detected.
* @param gamepad The gamepad that was detected.
*/
function addGamepad(gamepad:FlxGamepad)
function addGamepad(gamepad:FlxGamepad):Void
{
var useDefault = true;
if (Save.instance.hasControls(id, Gamepad(gamepad.id)))
{
var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id));
trace("padControlData: " + haxe.Json.stringify(padControlData));
trace('Loading gamepad control scheme from user save');
useDefault = false;
controls.addGamepadWithSaveData(gamepad.id, padControlData);
}
@ -126,7 +147,7 @@ class PlayerSettings
if (useDefault)
{
trace("Loading gamepad control scheme");
trace('Loading default gamepad control scheme');
controls.addDefaultGamepad(gamepad.id);
}
PreciseInputManager.instance.initializeButtons(controls, gamepad);
@ -135,12 +156,12 @@ class PlayerSettings
/**
* Save this player's controls to the game's persistent save.
*/
public function saveControls()
public function saveControls():Void
{
var keyData = controls.createSaveData(Keys);
if (keyData != null)
{
trace("saving key data: " + haxe.Json.stringify(keyData));
trace('Saving keyboard control scheme to user save');
Save.instance.setControls(id, Keys, keyData);
}
@ -149,7 +170,7 @@ class PlayerSettings
var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0]));
if (padData != null)
{
trace("saving pad data: " + haxe.Json.stringify(padData));
trace('Saving gamepad control scheme to user save');
Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
}
}

View file

@ -20,7 +20,7 @@ class Preferences
static function set_naughtyness(value:Bool):Bool
{
var save = Save.instance;
var save:Save = Save.instance;
save.options.naughtyness = value;
save.flush();
return value;
@ -39,7 +39,7 @@ class Preferences
static function set_downscroll(value:Bool):Bool
{
var save = Save.instance;
var save:Save = Save.instance;
save.options.downscroll = value;
save.flush();
return value;
@ -58,7 +58,7 @@ class Preferences
static function set_flashingLights(value:Bool):Bool
{
var save = Save.instance;
var save:Save = Save.instance;
save.options.flashingLights = value;
save.flush();
return value;
@ -77,7 +77,7 @@ class Preferences
static function set_zoomCamera(value:Bool):Bool
{
var save = Save.instance;
var save:Save = Save.instance;
save.options.zoomCamera = value;
save.flush();
return value;
@ -122,15 +122,20 @@ class Preferences
{
if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
var save = Save.instance;
var save:Save = Save.instance;
save.options.autoPause = value;
save.flush();
return value;
}
/**
* Loads the user's preferences from the save data and apply them.
*/
public static function init():Void
{
// Apply the autoPause setting (enables automatic pausing on focus lost).
FlxG.autoPause = Preferences.autoPause;
// Apply the debugDisplay setting (enables the FPS and RAM display).
toggleDebugDisplay(Preferences.debugDisplay);
}

View file

@ -3,7 +3,6 @@ package funkin;
import flash.Lib;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Sprite;
import flixel.system.FlxBasePreloader;
import openfl.display.Sprite;
@ -12,7 +11,8 @@ import openfl.text.TextField;
import openfl.text.TextFormat;
import flixel.system.FlxAssets;
@:bitmap("art/preloaderArt.png") class LogoImage extends BitmapData {}
@:bitmap('art/preloaderArt.png')
class LogoImage extends BitmapData {}
class Preloader extends FlxBasePreloader
{

View file

@ -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<FunkinSound>
@ -286,15 +289,28 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
* 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/<key>/<key>.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/<key>/<key>-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<SongMusicData> = SongRegistry.instance.parseMusicData(key);
// Will fall back and return null if the metadata doesn't exist or can't be parsed.
@ -308,10 +324,27 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
}
}
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 +359,18 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
* @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<FunkinSound>
{
@: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 +381,10 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
{
sound._label = embeddedSound;
}
else
{
sound._label = 'unknown';
}
sound.volume = volume;
sound.group = FlxG.sound.defaultSoundGroup;
@ -355,6 +399,38 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
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 +441,39 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
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;
}

View file

@ -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<FunkinSound>
*/
public function stop()
{
forEachAlive(function(sound:FunkinSound) {
sound.stop();
});
if (members != null)
{
forEachAlive(function(sound:FunkinSound) {
sound.stop();
});
}
}
public override function destroy()

View file

@ -160,7 +160,9 @@ class VoicesGroup extends SoundGroup
public override function destroy():Void
{
playerVoices.destroy();
playerVoices = null;
opponentVoices.destroy();
opponentVoices = null;
super.destroy();
}
}

View file

@ -55,6 +55,13 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
this.entries = new Map<String, T>();
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);
}
}
/**

View file

@ -15,7 +15,14 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
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<ConversationRegistry> = null;
static function get_instance():ConversationRegistry
{
if (_instance == null) _instance = new ConversationRegistry();
return _instance;
}
public function new()
{

View file

@ -15,7 +15,14 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
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<DialogueBoxRegistry> = null;
static function get_instance():DialogueBoxRegistry
{
if (_instance == null) _instance = new DialogueBoxRegistry();
return _instance;
}
public function new()
{

View file

@ -15,7 +15,14 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
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<SpeakerRegistry> = null;
static function get_instance():SpeakerRegistry
{
if (_instance == null) _instance = new SpeakerRegistry();
return _instance;
}
public function new()
{

View file

@ -15,7 +15,14 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
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<LevelRegistry> = null;
static function get_instance():LevelRegistry
{
if (_instance == null) _instance = new LevelRegistry();
return _instance;
}
public function new()
{

View file

@ -15,7 +15,14 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
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<NoteStyleRegistry> = null;
static function get_instance():NoteStyleRegistry
{
if (_instance == null) _instance = new NoteStyleRegistry();
return _instance;
}
public function new()
{

View file

@ -40,10 +40,17 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
}
/**
* 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<SongRegistry> = 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<Song, SongMetadata>
{
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<String> = openfl.Assets.getText(entryFilePath);
if (rawJson == null) return null;
rawJson = rawJson.trim();

View file

@ -15,7 +15,14 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
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<StageRegistry> = null;
static function get_instance():StageRegistry
{
if (_instance == null) _instance = new StageRegistry();
return _instance;
}
public function new()
{

View file

@ -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.*`

View file

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

View file

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

View file

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

View file

@ -366,7 +366,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;

View file

@ -1,24 +1,15 @@
package funkin.play;
import funkin.audio.FunkinSound;
import flixel.addons.display.FlxPieDial;
import flixel.addons.display.FlxPieDial;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.FlxTransitionableSubState;
import flixel.addons.transition.FlxTransitionableSubState;
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;
@ -26,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.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;
@ -48,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;
@ -69,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;
@ -948,8 +936,8 @@ class PlayState extends MusicBeatSubState
var pauseSubState:FlxSubState = new PauseSubState({mode: isChartingMode ? Charting : Standard});
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
pauseSubState.camera = camHUD;
openSubState(pauseSubState);
// boyfriendPos.put(); // TODO: Why is this here?
@ -1072,8 +1060,8 @@ class PlayState extends MusicBeatSubState
isChartingMode: isChartingMode,
transparent: persistentDraw
});
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
openSubState(gameOverSubState);
}
@ -1297,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();
@ -1357,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;
@ -1903,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();
@ -2093,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);
}
}
@ -2432,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));
}
}
@ -2487,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));
}
}
@ -2668,8 +2683,8 @@ class PlayState extends MusicBeatSubState
var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation});
persistentUpdate = false;
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
pauseSubState.camera = camCutscene;
openSubState(pauseSubState);
}
@ -2684,8 +2699,8 @@ class PlayState extends MusicBeatSubState
var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene});
persistentUpdate = false;
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
pauseSubState.camera = camCutscene;
openSubState(pauseSubState);
}
@ -2770,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;
@ -2848,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(
@ -2908,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);
@ -2922,7 +2944,10 @@ 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();
@ -2932,7 +2957,10 @@ 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();
@ -2945,7 +2973,6 @@ class PlayState extends MusicBeatSubState
{
remove(currentStage);
currentStage.kill();
dispatchEvent(new ScriptEvent(DESTROY, false));
currentStage = null;
}

View file

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

View file

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

View file

@ -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<FlxSprite>
{
@ -78,7 +79,7 @@ class ComboMilestone extends FlxTypedSpriteGroup<FlxSprite>
function setupCombo(daCombo:Int)
{
FlxG.sound.play(Paths.sound('comboSound'));
FunkinSound.playOnce(Paths.sound('comboSound'));
wasComboSetup = true;
var loopNum:Int = 0;

View file

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

View file

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

View file

@ -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.ConversationData;
import funkin.data.dialogue.ConversationData.DialogueEntryData;
import funkin.data.dialogue.ConversationRegistry;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.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

View file

@ -52,6 +52,11 @@ class ZoomCameraSongEvent extends SongEvent
super('ZoomCamera');
}
static final DEFAULT_ZOOM:Float = 1.0;
static final DEFAULT_DURATION:Float = 4.0;
static final DEFAULT_MODE:String = 'direct';
static final DEFAULT_EASE:String = 'linear';
public override function handleEvent(data:SongEventData):Void
{
// Does nothing if there is no PlayState camera or stage.
@ -60,25 +65,20 @@ class ZoomCameraSongEvent extends SongEvent
// Does nothing if we are minimal mode.
if (PlayState.instance.isMinimalMode) return;
var zoom:Null<Float> = data.getFloat('zoom');
if (zoom == null) zoom = 1.0;
var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM;
var duration:Null<Float> = data.getFloat('duration');
if (duration == null) duration = 4.0;
var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION;
var mode:Null<String> = data.getString('mode');
if (mode == null) mode = 'additive';
var mode:String = data.getString('mode') ?? DEFAULT_MODE;
var isDirectMode:Bool = mode == 'direct';
var ease:Null<String> = data.getString('ease');
if (ease == null) ease = 'linear';
var directMode:Bool = mode == 'direct';
var ease:String = data.getString('ease') ?? DEFAULT_EASE;
// If it's a string, check the value.
switch (ease)
{
case 'INSTANT':
PlayState.instance.tweenCameraZoom(zoom, 0, directMode);
PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode);
default:
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
@ -89,7 +89,7 @@ class ZoomCameraSongEvent extends SongEvent
return;
}
PlayState.instance.tweenCameraZoom(zoom, durSeconds, directMode, easeFunction);
PlayState.instance.tweenCameraZoom(zoom, durSeconds, isDirectMode, easeFunction);
}
}
@ -130,7 +130,7 @@ class ZoomCameraSongEvent extends SongEvent
{
name: 'mode',
title: 'Mode',
defaultValue: 'additive',
defaultValue: 'direct',
type: SongEventFieldType.ENUM,
keys: ['Additive' => 'additive', 'Direct' => 'direct']
},

View file

@ -139,7 +139,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (vari in _data.playData.songVariations)
{
var variMeta:Null<SongMetadata> = 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');
}
}
}
@ -383,7 +392,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (variationId in possibleVariations)
{
if (difficulties.exists('$diffId-$variationId')) return variationId;
var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : '';
if (difficulties.exists('$diffId$variationSuffix')) return variationId;
}
return null;

View file

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

View file

@ -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<T:MenuListItem> extends FlxTypedGroup<T>
{
@ -93,7 +94,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
if (newIndex != selectedIndex)
{
FlxG.sound.play(Paths.sound('scrollMenu'));
FunkinSound.playOnce(Paths.sound('scrollMenu'));
selectItem(newIndex);
}
@ -163,7 +164,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
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();

View file

@ -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;
@ -151,6 +152,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
}
else
{
FunkinSound.stopAllAudio();
onComplete();
}
}

View file

@ -1,6 +1,6 @@
package funkin.ui;
import flixel.addons.transition.FlxTransitionableSubState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.FlxSubState;
import flixel.text.FlxText;
import funkin.ui.mainmenu.MainMenuState;

View file

@ -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;
@ -71,7 +72,7 @@ class DebugMenuSubState extends MusicBeatSubState
if (controls.BACK)
{
FlxG.sound.play(Paths.sound('cancelMenu'));
FunkinSound.playOnce(Paths.sound('cancelMenu'));
exitDebugMenu();
}
}

View file

@ -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??');
});

View file

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

View file

@ -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> = FunkinSound.load(asset);
if (snd == null) return;
snd.autoDestroy = true;
snd.play(true);
snd.volume = volume;

View file

@ -71,8 +71,6 @@ class LatencyState extends MusicBeatSubState
// trace("EVENT LISTENER: " + key);
});
// FlxG.sound.playMusic(Paths.sound('soundTest'));
// funnyStatsGraph.hi
Conductor.instance.forceBPM(60);
@ -242,13 +240,6 @@ class LatencyState extends MusicBeatSubState
}
}
/* if (FlxG.keys.justPressed.SPACE)
{
FlxG.sound.music.stop();
FlxG.resetState();
}*/
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;

View file

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

View file

@ -4,8 +4,8 @@ import flixel.FlxSprite;
import flixel.util.FlxSignal;
import funkin.util.assets.FlxAnimationUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.sound.FlxSound;
import flixel.util.FlxTimer;
import funkin.audio.FunkinSound;
import funkin.audio.FlxStreamSound;
class DJBoyfriend extends FlxAtlasSprite
@ -178,7 +178,7 @@ class DJBoyfriend extends FlxAtlasSprite
if (cartoonSnd == null)
{
// tv is OFF, but getting turned on
FlxG.sound.play(Paths.sound('tv_on'));
FunkinSound.playOnce(Paths.sound('tv_on'));
cartoonSnd = new FlxStreamSound();
FlxG.sound.defaultSoundGroup.add(cartoonSnd);
@ -187,7 +187,7 @@ class DJBoyfriend extends FlxAtlasSprite
{
// plays it smidge after the click
new FlxTimer().start(0.1, function(_) {
FlxG.sound.play(Paths.sound('channel_switch'));
FunkinSound.playOnce(Paths.sound('channel_switch'));
});
}
// cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck"));

View file

@ -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);
@ -867,7 +871,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 +1017,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 +1062,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 +1095,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 +1137,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 +1180,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);
}
}

View file

@ -1,6 +1,6 @@
package funkin.ui.mainmenu;
import flixel.addons.transition.FlxTransitionableSubState;
import flixel.addons.transition.FlxTransitionableState;
import funkin.ui.debug.DebugMenuSubState;
import flixel.FlxObject;
import flixel.FlxSprite;
@ -103,8 +103,8 @@ class MainMenuState extends MusicBeatState
persistentDraw = true;
persistentUpdate = false;
// Freeplay has its own custom transition
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
openSubState(new FreeplayState());
});
@ -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());
}
}

View file

@ -5,9 +5,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
@ -143,7 +145,7 @@ class Page extends FlxGroup
{
if (canExit && controls.BACK)
{
FlxG.sound.play(Paths.sound('cancelMenu'));
FunkinSound.playOnce(Paths.sound('cancelMenu'));
exit();
}
}

View file

@ -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<String> = currentLevel.getDifficulties();
// "For now, NO erect in story mode" -Dave
var difficultyList:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST;
// Use this line to displays all difficulties
// var difficultyList:Array<String> = 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;

View file

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

View file

@ -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<Array<String>>
@ -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<String>)
@ -453,9 +455,9 @@ class TitleState extends MusicBeatState
switch (i + 1)
{
case 1:
createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8r']);
createCoolText(['The', 'Funkin Crew Inc']);
case 3:
addMoreText('present');
addMoreText('presents');
case 4:
deleteCoolText();
case 5:

View file

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

View file

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

View file

@ -13,12 +13,25 @@ class MathUtil
/**
* Perform linear interpolation between the base and the target, based on the current framerate.
* @param base The starting value, when `progress <= 0`.
* @param target The ending value, when `progress >= 1`.
* @param ratio Value used to interpolate between `base` and `target`.
*
* @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);
}
/**
* Perform linear interpolation based on the current framerate.
* @param lerp Value used to interpolate between `base` and `target`.
*
* @return The interpolated value.
*/
@:deprecated('Use smoothLerp instead')
public static function cameraLerp(lerp:Float):Float
{
return lerp * (FlxG.elapsed / (1 / 60));
@ -30,26 +43,30 @@ class MathUtil
* @param value The value to get the logarithm of.
* @return `log_base(value)`
*/
public static function logBase(base:Float, value:Float)
public static function logBase(base:Float, value:Float):Float
{
return Math.log(value) / Math.log(base);
}
/**
* @returns `2^x`
* Get the base-2 logarithm of a value.
* @param x value
* @return `2^x`
*/
public static function exp2(x:Float)
public static function exp2(x:Float):Float
{
return Math.pow(2, x);
}
/**
* Linearly interpolate between two values.
*
* @param base The starting value, when `progress <= 0`.
* @param target The ending value, when `progress >= 1`.
* @param progress Value used to interpolate between `base` and `target`.
* @return The interpolated value.
*/
public static function lerp(base:Float, target:Float, progress:Float)
public static function lerp(base:Float, target:Float, progress:Float):Float
{
return base + progress * (target - base);
}
@ -67,6 +84,7 @@ class MathUtil
*/
public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float
{
// An alternative algorithm which uses a separate half-life value:
// var halfLife:Float = -duration / logBase(2, precision);
// lerp(current, target, 1 - exp2(-elapsed / halfLife));

View file

@ -16,7 +16,7 @@ class MemoryUtil
public static function buildGCInfo():String
{
#if cpp
var result = "HXCPP-Immix:";
var result:String = 'HXCPP-Immix:';
result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes';
result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes';
result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes';
@ -35,10 +35,10 @@ class MemoryUtil
result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}';
result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}';
#elseif js
var result = "JS-MNS:";
var result:String = 'JS-MNS:';
result += '\n- Memory Used: ${getMemoryUsed()} bytes';
#else
var result = "Unknown GC";
var result:String = 'Unknown GC';
#end
return result;
@ -66,7 +66,7 @@ class MemoryUtil
#if cpp
cpp.vm.Gc.enable(true);
#else
throw "Not implemented!";
throw 'Not implemented!';
#end
}
@ -78,7 +78,7 @@ class MemoryUtil
#if cpp
cpp.vm.Gc.enable(false);
#else
throw "Not implemented!";
throw 'Not implemented!';
#end
}
@ -92,7 +92,7 @@ class MemoryUtil
#if cpp
cpp.vm.Gc.run(major);
#else
throw "Not implemented!";
throw 'Not implemented!';
#end
}
@ -107,7 +107,7 @@ class MemoryUtil
#if cpp
cpp.vm.Gc.compact();
#else
throw "Not implemented!";
throw 'Not implemented!';
#end
}
}

View file

@ -38,6 +38,9 @@ class MouseUtil
}
}
/**
* Increment the zoom level of the current camera by the mouse wheel scroll value.
*/
public static function mouseWheelZoom():Void
{
if (FlxG.mouse.wheel != 0) FlxG.camera.zoom += FlxG.mouse.wheel * (0.1 * FlxG.camera.zoom);

View file

@ -16,9 +16,9 @@ class PlatformUtil
#if mac
return true;
#elseif html5
return js.Browser.window.navigator.platform.startsWith("Mac")
|| js.Browser.window.navigator.platform.startsWith("iPad")
|| js.Browser.window.navigator.platform.startsWith("iPhone");
return js.Browser.window.navigator.platform.startsWith('Mac')
|| js.Browser.window.navigator.platform.startsWith('iPad')
|| js.Browser.window.navigator.platform.startsWith('iPhone');
#else
return false;
#end
@ -27,7 +27,7 @@ class PlatformUtil
/**
* Detects and returns the current host platform.
* Always returns `HTML5` on web, regardless of the computer running that browser.
* Returns `null` if the platform could not be detected.
* @return The host platform, or `null` if the platform could not be detected.
*/
public static function detectHostPlatform():Null<HostPlatform>
{

View file

@ -27,29 +27,53 @@ class SortUtil
/**
* You can use this function in FlxTypedGroup.sort() to sort FlxObjects by their z-index values.
* The value defaults to 0, but by assigning it you can easily rearrange objects as desired.
*
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
* @param a The first FlxObject to compare.
* @param b The second FlxObject to compare.
* @return 1 if `a` has a higher z-index, -1 if `b` has a higher z-index.
*/
public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int
public static inline function byZIndex(order:Int, a:FlxBasic, b:FlxBasic):Int
{
if (Obj1 == null || Obj2 == null) return 0;
return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex);
if (a == null || b == null) return 0;
return FlxSort.byValues(order, a.zIndex, b.zIndex);
}
/**
* Given two Notes, returns 1 or -1 based on whether `a` or `b` has an earlier strumtime.
*
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
* @param a The first Note to compare.
* @param b The second Note to compare.
* @return 1 if `a` has an earlier strumtime, -1 if `b` has an earlier strumtime.
*/
public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite)
public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite):Int
{
return noteDataByTime(order, a.noteData, b.noteData);
}
public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData)
/**
* Given two Note Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time.
*
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
* @param a The first Event to compare.
* @param b The second Event to compare.
* @return 1 if `a` has an earlier time, -1 if `b` has an earlier time.
*/
public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData):Int
{
return FlxSort.byValues(order, a.time, b.time);
}
public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData)
/**
* Given two Event Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time.
*
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
* @param a The first Event to compare.
* @param b The second Event to compare.
* @return 1 if `a` has an earlier time, -1 if `b` has an earlier time.
*/
public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData):Int
{
return FlxSort.byValues(order, a.time, b.time);
}
@ -58,8 +82,11 @@ class SortUtil
* Given two FlxFrames, sort their names alphabetically.
*
* @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING`
* @param a The first Frame to compare.
* @param b The second Frame to compare.
* @return 1 if `a` has an earlier time, -1 if `b` has an earlier time.
*/
public static inline function byFrameName(a:FlxFrame, b:FlxFrame)
public static inline function byFrameName(a:FlxFrame, b:FlxFrame):Int
{
return alphabetically(a.name, b.name);
}
@ -68,6 +95,7 @@ class SortUtil
* Sort predicate for sorting strings alphabetically.
* @param a The first string to compare.
* @param b The second string to compare.
* @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal
*/
public static function alphabetically(a:String, b:String):Int
{
@ -81,9 +109,11 @@ class SortUtil
/**
* Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first.
* Example usage: `array.sort(defaultThenAlphabetical.bind('test'))` will sort the array so that the string 'test' is first.
*
* @param defaultValue The value to prioritize.
* @param a The first string to compare.
* @param b The second string to compare.
* @param defaultValue The value to prioritize.
* @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal
*/
public static function defaultThenAlphabetically(defaultValue:String, a:String, b:String):Int
{
@ -96,9 +126,11 @@ class SortUtil
/**
* Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first.
* Example usage: `array.sort(defaultsThenAlphabetical.bind(['test']))` will sort the array so that the string 'test' is first.
*
* @param defaultValues The values to prioritize.
* @param a The first string to compare.
* @param b The second string to compare.
* @param defaultValues The values to prioritize.
* @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal
*/
public static function defaultsThenAlphabetically(defaultValues:Array<String>, a:String, b:String):Int
{

View file

@ -5,23 +5,42 @@ import haxe.Timer;
class TimerUtil
{
/**
* Store the current time.
*/
public static function start():Float
{
return Timer.stamp();
}
private static function took(start:Float, ?end:Float):Float
/**
* Return the elapsed time.
*/
static function took(start:Float, ?end:Float):Float
{
var endOrNow:Float = end != null ? end : Timer.stamp();
return endOrNow - start;
}
/**
* Return the elapsed time in seconds as a string.
* @param start The start time.
* @param end The end time.
* @param precision The number of decimal places to round to.
* @return The elapsed time in seconds as a string.
*/
public static function seconds(start:Float, ?end:Float, ?precision = 2):String
{
var seconds:Float = FloatTools.round(took(start, end), precision);
return '${seconds} seconds';
}
/**
* Return the elapsed time in milliseconds as a string.
* @param start The start time.
* @param end The end time.
* @return The elapsed time in milliseconds as a string.
*/
public static function ms(start:Float, ?end:Float):String
{
var seconds:Float = took(start, end);

View file

@ -18,7 +18,7 @@ class TrackerUtil
public static function initTrackers():Void
{
#if FLX_DEBUG
Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"]));
Tracker.addProfile(new TrackerProfile(Highscore, ['tallies']));
FlxG.console.registerClass(Highscore);
#end
}

View file

@ -15,6 +15,9 @@ class VersionUtil
/**
* Checks that a given verison number satisisfies a given version rule.
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.
* @param version The semantic version to validate.
* @param versionRule The version rule to validate against.
* @return `true` if the version satisfies the rule, `false` otherwise.
*/
public static function validateVersion(version:thx.semver.Version, versionRule:thx.semver.VersionRule):Bool
{
@ -32,6 +35,9 @@ class VersionUtil
/**
* Checks that a given verison number satisisfies a given version rule.
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.
* @param version The semantic version to validate.
* @param versionRule The version rule to validate against.
* @return `true` if the version satisfies the rule, `false` otherwise.
*/
public static function validateVersionStr(version:String, versionRule:String):Bool
{
@ -56,7 +62,7 @@ class VersionUtil
public static function getVersionFromJSON(input:Null<String>):Null<thx.semver.Version>
{
if (input == null) return null;
var parsed = SerializerUtil.fromJSON(input);
var parsed:Dynamic = SerializerUtil.fromJSON(input);
if (parsed == null) return null;
if (parsed.version == null) return null;
var versionStr:String = parsed.version; // Dynamic -> String cast
@ -64,6 +70,11 @@ class VersionUtil
return version;
}
/**
* Get and parse the semantic version from a JSON string.
* @param input The JSON string to parse.
* @return The semantic version, or null if it could not be parsed.
*/
public static function parseVersion(input:Dynamic):Null<thx.semver.Version>
{
if (input == null) return null;

View file

@ -24,7 +24,7 @@ class WindowUtil
{
#if CAN_OPEN_LINKS
#if linux
Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]);
Sys.command('/usr/bin/xdg-open', [targetUrl, '&']);
#else
// This should work on Windows and HTML5.
FlxG.openURL(targetUrl);
@ -42,7 +42,7 @@ class WindowUtil
{
#if CAN_OPEN_LINKS
#if windows
Sys.command('explorer', [targetPath.replace("/", "\\")]);
Sys.command('explorer', [targetPath.replace('/', '\\')]);
#elseif mac
Sys.command('open', [targetPath]);
#elseif linux
@ -61,9 +61,9 @@ class WindowUtil
{
#if CAN_OPEN_LINKS
#if windows
Sys.command('explorer', ["/select," + targetPath.replace("/", "\\")]);
Sys.command('explorer', ['/select,' + targetPath.replace('/', '\\')]);
#elseif mac
Sys.command('open', ["-R", targetPath]);
Sys.command('open', ['-R', targetPath]);
#elseif linux
// TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file.
Sys.command('open', [targetPath]);
@ -82,7 +82,7 @@ class WindowUtil
* Wires up FlxSignals that happen based on window activity.
* For example, we can run a callback when the window is closed.
*/
public static function initWindowEvents()
public static function initWindowEvents():Void
{
// onUpdate is called every frame just before rendering.
@ -95,7 +95,7 @@ class WindowUtil
/**
* Turns off that annoying "Report to Microsoft" dialog that pops up when the game crashes.
*/
public static function disableCrashHandler()
public static function disableCrashHandler():Void
{
#if (cpp && windows)
untyped __cpp__('SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);');

View file

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

View file

@ -1,3 +1,6 @@
package haxe.ui.backend.flixel;
/**
* Override HaxeUI to use `MusicBeatState` instead of `FlxState`.
*/
typedef UIStateBase = funkin.ui.MusicBeatState;

View file

@ -1,3 +1,6 @@
package haxe.ui.backend.flixel;
/**
* Override HaxeUI to use `MusicBeatSubState` instead of `FlxSubState`.
*/
typedef UISubStateBase = funkin.ui.MusicBeatSubState;

View file

@ -1,32 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="arrows.png">
<SubTexture name="staticLeft0001" x="0" y="0" width="17" height="17"/>
<SubTexture name="staticDown0001" x="17" y="0" width="17" height="17"/>
<SubTexture name="staticUp0001" x="34" y="0" width="17" height="17"/>
<SubTexture name="staticRight0001" x="51" y="0" width="17" height="17"/>
<SubTexture name="noteLeft0001" x="0" y="17" width="17" height="17"/>
<SubTexture name="noteDown0001" x="17" y="17" width="17" height="17"/>
<SubTexture name="noteUp0001" x="34" y="17" width="17" height="17"/>
<SubTexture name="noteRight0001" x="51" y="17" width="17" height="17"/>
<SubTexture name="pressedLeft0001" x="0" y="17" width="17" height="17"/>
<SubTexture name="pressedDown0001" x="17" y="17" width="17" height="17"/>
<SubTexture name="pressedUp0001" x="34" y="17" width="17" height="17"/>
<SubTexture name="pressedRight0001" x="51" y="17" width="17" height="17"/>
<SubTexture name="pressedLeft0002" x="0" y="34" width="17" height="17"/>
<SubTexture name="pressedDown0002" x="17" y="34" width="17" height="17"/>
<SubTexture name="pressedUp0002" x="34" y="34" width="17" height="17"/>
<SubTexture name="pressedRight0002" x="51" y="34" width="17" height="17"/>
<SubTexture name="confirmLeft0001" x="0" y="51" width="17" height="17"/>
<SubTexture name="confirmDown0001" x="17" y="51" width="17" height="17"/>
<SubTexture name="confirmUp0001" x="34" y="51" width="17" height="17"/>
<SubTexture name="confirmRight0001" x="51" y="51" width="17" height="17"/>
<SubTexture name="confirmLeft0002" x="0" y="68" width="17" height="17"/>
<SubTexture name="confirmDown0002" x="17" y="68" width="17" height="17"/>
<SubTexture name="confirmUp0002" x="34" y="68" width="17" height="17"/>
<SubTexture name="confirmRight0002" x="51" y="68" width="17" height="17"/>
<SubTexture name="staticLeft0001" x="0" y="0" width="17" height="17" />
<SubTexture name="staticDown0001" x="17" y="0" width="17" height="17" />
<SubTexture name="staticUp0001" x="34" y="0" width="17" height="17" />
<SubTexture name="staticRight0001" x="51" y="0" width="17" height="17" />
<SubTexture name="noteLeft0001" x="0" y="17" width="17" height="17" />
<SubTexture name="noteDown0001" x="17" y="17" width="17" height="17" />
<SubTexture name="noteUp0001" x="34" y="17" width="17" height="17" />
<SubTexture name="noteRight0001" x="51" y="17" width="17" height="17" />
<SubTexture name="pressedLeft0001" x="0" y="17" width="17" height="17" />
<SubTexture name="pressedDown0001" x="17" y="17" width="17" height="17" />
<SubTexture name="pressedUp0001" x="34" y="17" width="17" height="17" />
<SubTexture name="pressedRight0001" x="51" y="17" width="17" height="17" />
<SubTexture name="pressedLeft0002" x="0" y="34" width="17" height="17" />
<SubTexture name="pressedDown0002" x="17" y="34" width="17" height="17" />
<SubTexture name="pressedUp0002" x="34" y="34" width="17" height="17" />
<SubTexture name="pressedRight0002" x="51" y="34" width="17" height="17" />
<SubTexture name="confirmLeft0001" x="0" y="51" width="17" height="17" />
<SubTexture name="confirmDown0001" x="17" y="51" width="17" height="17" />
<SubTexture name="confirmUp0001" x="34" y="51" width="17" height="17" />
<SubTexture name="confirmRight0001" x="51" y="51" width="17" height="17" />
<SubTexture name="confirmLeft0002" x="0" y="68" width="17" height="17" />
<SubTexture name="confirmDown0002" x="17" y="68" width="17" height="17" />
<SubTexture name="confirmUp0002" x="34" y="68" width="17" height="17" />
<SubTexture name="confirmRight0002" x="51" y="68" width="17" height="17" />
</TextureAtlas>