Merge branch 'rewrite/master' into bugfix/cut-pico-dadbattle

This commit is contained in:
Cameron Taylor 2024-03-25 13:42:50 -04:00
commit 9fd780e00c
32 changed files with 437 additions and 443 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.

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;
@ -136,6 +135,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
@ -145,6 +147,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
@ -245,7 +250,7 @@ class Conductor
* WARNING: Avoid this for things like setting the BPM of the title screen music,
* you should have a metadata file for it instead.
*/
public function forceBPM(?bpm:Float = null)
public function forceBPM(?bpm:Float):Void
{
if (bpm != null)
{
@ -253,7 +258,7 @@ class Conductor
}
else
{
// trace('[CONDUCTOR] Resetting BPM to default');
trace('[CONDUCTOR] Resetting BPM to default');
}
this.bpmOverride = bpm;
@ -266,7 +271,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)
{
@ -274,9 +279,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;
@ -338,39 +343,43 @@ class Conductor
}
}
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
/**
* Apply the `SongTimeChange` data from the song metadata to this Conductor.
* @param songTimeChanges The SongTimeChanges.
*/
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)
@ -384,6 +393,8 @@ class Conductor
/**
* 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
{
@ -413,7 +424,7 @@ 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;
}
@ -421,6 +432,8 @@ class Conductor
/**
* 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
{
@ -457,6 +470,8 @@ class Conductor
/**
* 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
{
@ -491,13 +506,16 @@ class Conductor
}
}
/**
* Add variables of the current Conductor instance to the Flixel debugger.
*/
public static function watchQuick():Void
{
FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition);
FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
FlxG.watch.addQuick('songPosition', Conductor.instance.songPosition);
FlxG.watch.addQuick('bpm', Conductor.instance.bpm);
FlxG.watch.addQuick('currentMeasureTime', Conductor.instance.currentMeasureTime);
FlxG.watch.addQuick('currentBeatTime', Conductor.instance.currentBeatTime);
FlxG.watch.addQuick('currentStepTime', Conductor.instance.currentStepTime);
}
/**

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

@ -2,11 +2,7 @@ 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;
@ -948,8 +944,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 +1068,8 @@ class PlayState extends MusicBeatSubState
isChartingMode: isChartingMode,
transparent: persistentDraw
});
FlxTransitionableSubState.skipNextTransIn = true;
FlxTransitionableSubState.skipNextTransOut = true;
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
openSubState(gameOverSubState);
}
@ -2093,8 +2089,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);
}
}
@ -2668,8 +2663,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 +2679,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);
}

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

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

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

@ -1013,7 +1013,14 @@ class FreeplayState extends MusicBeatSubState
// Set the difficulty star count on the right.
albumRoll.setDifficultyStars(daSong?.songRating);
albumRoll.albumId = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID;
// Set the album graphic and play the animation if relevant.
var newAlbumId:String = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID;
if (albumRoll.albumId != newAlbumId)
{
albumRoll.albumId = newAlbumId;
albumRoll.playIntro();
}
}
// Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)

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

View file

@ -453,9 +453,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

@ -13,12 +13,24 @@ 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.
*/
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 +42,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 +83,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

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

@ -4,27 +4,22 @@
<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" />