mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-23 08:07:54 -05:00
Merge branch 'rewrite/master' into feature/pico-flicker
This commit is contained in:
commit
e0eb00d01a
31 changed files with 571 additions and 209 deletions
2
.github/workflows/build-shit.yml
vendored
2
.github/workflows/build-shit.yml
vendored
|
@ -77,7 +77,7 @@ jobs:
|
|||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build windows -v -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD
|
||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
|
||||
- name: Upload build artifacts
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
<!-- Uncomment this to wipe your input settings. -->
|
||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
||||
|
||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5">
|
||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5 || GITHUB_BUILD">
|
||||
<!--
|
||||
Use the parent assets folder rather than the exported one
|
||||
No more will we accidentally undo our changes!
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 46bad6850be34afa3742640f57da07d89d5573b8
|
||||
Subproject commit ae92cf9bf353f0fa958ff68f2c78fdcb377fe9ce
|
|
@ -30,7 +30,7 @@ import funkin.modding.module.ModuleHandler;
|
|||
import funkin.ui.title.TitleState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.TrackerUtil;
|
||||
#if discord_rpc
|
||||
|
@ -90,69 +90,7 @@ class InitState extends FlxState
|
|||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
TrackerUtil.initTrackers();
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
|
||||
FlxG.debugger.visible = false;
|
||||
});
|
||||
|
||||
// Adds a red button to the debugger.
|
||||
// This pauses the game AND the music! This ensures the Conductor stops.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
|
||||
if (FlxG.vcr.paused)
|
||||
{
|
||||
FlxG.vcr.resume();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.resume();
|
||||
}
|
||||
|
||||
FlxG.sound.music.resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.vcr.pause();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
});
|
||||
|
||||
// Adds a blue button to the debugger.
|
||||
// This skips forward in the song.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
|
||||
FlxG.game.debugger.vcr.onStep();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
snd.time += FlxG.elapsed * 1000;
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
#end
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
setupFlixelDebug();
|
||||
|
||||
//
|
||||
// FLIXEL TRANSITIONS
|
||||
|
@ -221,7 +159,7 @@ class InitState extends FlxState
|
|||
// NOTE: Registries must be imported and not referenced with fully qualified names,
|
||||
// to ensure build macros work properly.
|
||||
trace('Parsing game data...');
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
|
@ -238,7 +176,7 @@ class InitState extends FlxState
|
|||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
|
||||
trace('Parsing game data took: ${TimerTools.ms(perfStart)}');
|
||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -349,6 +287,80 @@ class InitState extends FlxState
|
|||
});
|
||||
}
|
||||
|
||||
function setupFlixelDebug():Void
|
||||
{
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
TrackerUtil.initTrackers();
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
|
||||
FlxG.debugger.visible = false;
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
});
|
||||
|
||||
// Adds a red button to the debugger.
|
||||
// This pauses the game AND the music! This ensures the Conductor stops.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
|
||||
if (FlxG.vcr.paused)
|
||||
{
|
||||
FlxG.vcr.resume();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.resume();
|
||||
}
|
||||
|
||||
FlxG.sound.music.resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.vcr.pause();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
});
|
||||
|
||||
// Adds a blue button to the debugger.
|
||||
// This skips forward in the song.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
|
||||
FlxG.game.debugger.vcr.onStep();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
snd.time += FlxG.elapsed * 1000;
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function defineSong():String
|
||||
{
|
||||
return MacroUtil.getDefine('SONG');
|
||||
|
|
|
@ -8,6 +8,8 @@ import flixel.sound.FlxSound;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import funkin.util.tools.ICloneable;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import flixel.math.FlxMath;
|
||||
|
@ -28,7 +30,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
/**
|
||||
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
|
||||
*/
|
||||
static var cache(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
|
||||
static var pool(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
|
||||
|
||||
public var muted(default, set):Bool = false;
|
||||
|
||||
|
@ -265,23 +267,55 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinSound` object.
|
||||
* Creates a new `FunkinSound` object and loads it as the current music track.
|
||||
*
|
||||
* @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
|
||||
* @param volume How loud to play it (0 to 1).
|
||||
* @param looped Whether to loop this sound.
|
||||
* @param group The group to add this sound to.
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* @param key The key of the music you want to play. Music should be at `music/<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.
|
||||
* Data should be at `music/<key>/<key>-metadata.json`.
|
||||
*/
|
||||
public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void
|
||||
{
|
||||
if (!overrideExisting && FlxG.sound.music?.playing) return;
|
||||
|
||||
if (mapTimeChanges)
|
||||
{
|
||||
var songMusicData:Null<SongMusicData> = SongRegistry.instance.parseMusicData(key);
|
||||
// Will fall back and return null if the metadata doesn't exist or can't be parsed.
|
||||
if (songMusicData != null)
|
||||
{
|
||||
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.warn('Tried and failed to find music metadata for $key');
|
||||
}
|
||||
}
|
||||
|
||||
FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key'));
|
||||
|
||||
// Prevent repeat update() and onFocus() calls.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinSound` object synchronously.
|
||||
*
|
||||
* @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
|
||||
* @param volume How loud to play it (0 to 1).
|
||||
* @param looped Whether to loop this sound.
|
||||
* @param group The group to add this sound to.
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object.
|
||||
*/
|
||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||
?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound
|
||||
{
|
||||
var sound:FunkinSound = cache.recycle(construct);
|
||||
var sound:FunkinSound = pool.recycle(construct);
|
||||
|
||||
// Load the sound.
|
||||
// Sets `exists = true` as a side effect.
|
||||
|
@ -297,9 +331,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
sound.persist = true;
|
||||
if (autoPlay) sound.play();
|
||||
|
||||
// Call OnlLoad() because the sound already loaded
|
||||
// Call onLoad() because the sound already loaded
|
||||
if (onLoad != null && sound._sound != null) onLoad();
|
||||
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
|
@ -307,7 +343,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
{
|
||||
var sound:FunkinSound = new FunkinSound();
|
||||
|
||||
cache.add(sound);
|
||||
pool.add(sound);
|
||||
FlxG.sound.list.add(sound);
|
||||
|
||||
return sound;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.audio.waveform;
|
||||
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
class WaveformDataParser
|
||||
{
|
||||
|
@ -73,7 +73,7 @@ class WaveformDataParser
|
|||
|
||||
var outputData:Array<Int> = [];
|
||||
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
for (pointIndex in 0...outputPointCount)
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ class WaveformDataParser
|
|||
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
|
||||
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
|
||||
|
||||
trace('[WAVEFORM] Interpreted audio buffer in ${TimerTools.seconds(perfStart)}.');
|
||||
trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.data;
|
||||
|
||||
import openfl.Assets;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.Constraints.Constructible;
|
||||
|
@ -19,12 +18,23 @@ typedef EntryConstructorFunction = String->Void;
|
|||
@:generic
|
||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||
{
|
||||
/**
|
||||
* The ID of the registry. Used when logging.
|
||||
*/
|
||||
public final registryId:String;
|
||||
|
||||
final dataFilePath:String;
|
||||
|
||||
/**
|
||||
* A map of entry IDs to entries.
|
||||
*/
|
||||
final entries:Map<String, T>;
|
||||
|
||||
/**
|
||||
* A map of entry IDs to scripted class names.
|
||||
*/
|
||||
final scriptedEntryIds:Map<String, String>;
|
||||
|
||||
/**
|
||||
* The version rule to use when loading entries.
|
||||
* If the entry's version does not match this rule, migration is needed.
|
||||
|
@ -37,17 +47,18 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* @param registryId A readable ID for this registry, used when logging.
|
||||
* @param dataFilePath The path (relative to `assets/data`) to search for JSON files.
|
||||
*/
|
||||
public function new(registryId:String, dataFilePath:String, versionRule:thx.semver.VersionRule = null)
|
||||
public function new(registryId:String, dataFilePath:String, ?versionRule:thx.semver.VersionRule)
|
||||
{
|
||||
this.registryId = registryId;
|
||||
this.dataFilePath = dataFilePath;
|
||||
this.versionRule = versionRule == null ? "1.0.x" : versionRule;
|
||||
this.versionRule = versionRule == null ? '1.0.x' : versionRule;
|
||||
|
||||
this.entries = new Map<String, T>();
|
||||
this.scriptedEntryIds = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Create a `loadEntriesAsync()` function.
|
||||
* TODO: Create a `loadEntriesAsync(onProgress, onComplete)` function.
|
||||
*/
|
||||
public function loadEntries():Void
|
||||
{
|
||||
|
@ -66,7 +77,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
{
|
||||
entry = createScriptedEntry(entryCls);
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
catch (e)
|
||||
{
|
||||
log('Failed to create scripted entry (${entryCls})');
|
||||
continue;
|
||||
|
@ -76,6 +87,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
{
|
||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||
entries.set(entry.id, entry);
|
||||
scriptedEntryIds.set(entry.id, entryCls);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -102,7 +114,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
entries.set(entry.id, entry);
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
catch (e)
|
||||
{
|
||||
// Print the error.
|
||||
trace(' Failed to load entry data: ${entryId}');
|
||||
|
@ -130,6 +142,36 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
return entries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the entry ID is known to have an attached script.
|
||||
* @param id The ID of the entry.
|
||||
* @return `true` if the entry has an attached script, `false` otherwise.
|
||||
*/
|
||||
public function isScriptedEntry(id:String):Bool
|
||||
{
|
||||
return scriptedEntryIds.exists(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class name of the scripted entry with the given ID, if it exists.
|
||||
* @param id The ID of the entry.
|
||||
* @return The class name, or `null` if it does not exist.
|
||||
*/
|
||||
public function getScriptedEntryClassName(id:String):String
|
||||
{
|
||||
return scriptedEntryIds.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the registry has successfully parsed an entry with the given ID.
|
||||
* @param id The ID of the entry.
|
||||
* @return `true` if the entry exists, `false` otherwise.
|
||||
*/
|
||||
public function hasEntry(id:String):Bool
|
||||
{
|
||||
return entries.exists(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an entry by its ID.
|
||||
* @param id The ID of the entry to fetch.
|
||||
|
@ -145,6 +187,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data for an entry and parse its Semantic Version.
|
||||
* @param id The ID of the entry.
|
||||
* @return The entry's version, or `null` if it does not exist or is invalid.
|
||||
*/
|
||||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||
{
|
||||
var entryStr:String = loadEntryFile(id).contents;
|
||||
|
@ -185,6 +232,8 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param id The ID of the entry.
|
||||
* @return The created entry.
|
||||
*/
|
||||
public abstract function parseEntryData(id:String):Null<J>;
|
||||
|
||||
|
@ -194,6 +243,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
* @return The created entry.
|
||||
*/
|
||||
public abstract function parseEntryDataRaw(contents:String, ?fileName:String):Null<J>;
|
||||
|
||||
|
@ -202,6 +252,9 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* accounting for old versions of the data.
|
||||
*
|
||||
* NOTE: Extend this function to handle migration.
|
||||
* @param id The ID of the entry.
|
||||
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||
* @return The created entry.
|
||||
*/
|
||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||
{
|
||||
|
@ -220,12 +273,17 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
throw '[${registryId}] Entry ${id} does not support migration to version ${versionRule}.';
|
||||
}
|
||||
|
||||
// Example:
|
||||
// if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||
// return parseEntryData_v0_1_x(id);
|
||||
// } else {
|
||||
// super.parseEntryDataWithMigration(id, version);
|
||||
// }
|
||||
/*
|
||||
* An example of what you should override this with:
|
||||
*
|
||||
* ```haxe
|
||||
* if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||
* return parseEntryData_v0_1_x(id);
|
||||
* } else {
|
||||
* super.parseEntryDataWithMigration(id, version);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,10 +313,15 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
{
|
||||
DataError.printError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
|
|
|
@ -68,6 +68,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
{
|
||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||
entries.set(entry.id, entry);
|
||||
scriptedEntryIds.set(entry.id, entryCls);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -441,6 +442,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return {fileName: entryFilePath, contents: rawJson};
|
||||
}
|
||||
|
||||
function hasMusicDataFile(id:String, ?variation:String):Bool
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||
return openfl.Assets.exists(entryFilePath);
|
||||
}
|
||||
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
|
|
@ -81,9 +81,10 @@ class FunkinSprite extends FlxSprite
|
|||
*/
|
||||
public function loadTexture(key:String):FunkinSprite
|
||||
{
|
||||
if (!isTextureCached(key)) FlxG.log.warn('Texture not cached, may experience stuttering! $key');
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
loadGraphic(key);
|
||||
loadGraphic(graphicKey);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -95,7 +96,7 @@ class FunkinSprite extends FlxSprite
|
|||
*/
|
||||
public function loadSparrow(key:String):FunkinSprite
|
||||
{
|
||||
var graphicKey = Paths.image(key);
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
this.frames = Paths.getSparrowAtlas(key);
|
||||
|
@ -110,7 +111,7 @@ class FunkinSprite extends FlxSprite
|
|||
*/
|
||||
public function loadPacker(key:String):FunkinSprite
|
||||
{
|
||||
var graphicKey = Paths.image(key);
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
this.frames = Paths.getPackerAtlas(key);
|
||||
|
|
|
@ -215,7 +215,7 @@ class Countdown
|
|||
|
||||
if (spritePath == null) return;
|
||||
|
||||
var countdownSprite:FunkinSprite = FunkinSprite.create(Paths.image(spritePath));
|
||||
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
||||
countdownSprite.scrollFactor.set(0, 0);
|
||||
|
||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||
|
|
|
@ -28,7 +28,7 @@ class GitarooPause extends MusicBeatState
|
|||
{
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
|
||||
var bg:FunkinSprite = FunkinSprite.create(Paths.image('pauseAlt/pauseBG'));
|
||||
var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG');
|
||||
add(bg);
|
||||
|
||||
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -1416,7 +1417,7 @@ class PlayState extends MusicBeatSubState
|
|||
function initHealthBar():Void
|
||||
{
|
||||
var healthBarYPos:Float = Preferences.downscroll ? FlxG.height * 0.1 : FlxG.height * 0.9;
|
||||
healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar'));
|
||||
healthBarBG = FunkinSprite.create(0, healthBarYPos, 'healthBar');
|
||||
healthBarBG.screenCenter(X);
|
||||
healthBarBG.scrollFactor.set(0, 0);
|
||||
healthBarBG.zIndex = 800;
|
||||
|
@ -1453,7 +1454,7 @@ class PlayState extends MusicBeatSubState
|
|||
function initMinimalMode():Void
|
||||
{
|
||||
// Create the green background.
|
||||
var menuBG = FunkinSprite.create(Paths.image('menuDesat'));
|
||||
var menuBG = FunkinSprite.create('menuDesat');
|
||||
menuBG.color = 0xFF4CAF50;
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.updateHitbox();
|
||||
|
@ -2711,7 +2712,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
if (targetSongId == null)
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
FunkinSound.playMusic('freakyMenu');
|
||||
|
||||
// transIn = FlxTransitionableState.defaultTransIn;
|
||||
// transOut = FlxTransitionableState.defaultTransOut;
|
||||
|
|
|
@ -6,7 +6,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxDirection;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
public function displayRating(daRating:String)
|
||||
{
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (daRating == null) daRating = "good";
|
||||
|
||||
|
@ -25,7 +25,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
|
||||
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||
|
||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, Paths.image(ratingPath));
|
||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
|
||||
rating.scrollFactor.set(0.2, 0.2);
|
||||
|
||||
rating.zIndex = 1000;
|
||||
|
@ -59,12 +59,12 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
startDelay: Conductor.instance.beatLengthMs * 0.001
|
||||
});
|
||||
|
||||
trace('displayRating took: ${TimerTools.seconds(perfStart)}');
|
||||
trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
|
||||
}
|
||||
|
||||
public function displayCombo(?combo:Int = 0):Int
|
||||
{
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (combo == null) combo = 0;
|
||||
|
||||
|
@ -76,7 +76,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
pixelShitPart1 = 'weeb/pixelUI/';
|
||||
pixelShitPart2 = '-pixel';
|
||||
}
|
||||
var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
|
||||
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
|
||||
comboSpr.y = FlxG.camera.height * 0.4 + 80;
|
||||
comboSpr.x = FlxG.width * 0.50;
|
||||
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
||||
|
@ -124,7 +124,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
var daLoop:Int = 1;
|
||||
for (i in seperatedScore)
|
||||
{
|
||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
|
||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
|
@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
daLoop++;
|
||||
}
|
||||
|
||||
trace('displayCombo took: ${TimerTools.seconds(perfStart)}');
|
||||
trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
|
||||
|
||||
return combo;
|
||||
}
|
||||
|
|
|
@ -57,6 +57,9 @@ class FocusCameraSongEvent extends SongEvent
|
|||
// Does nothing if there is no PlayState camera or stage.
|
||||
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
|
||||
|
||||
// Does nothing if we are minimal mode.
|
||||
if (PlayState.instance.isMinimalMode) return;
|
||||
|
||||
var posX:Null<Float> = data.getFloat('x');
|
||||
if (posX == null) posX = 0.0;
|
||||
var posY:Null<Float> = data.getFloat('y');
|
||||
|
|
|
@ -55,7 +55,10 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
public override function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
// Does nothing if there is no PlayState camera or stage.
|
||||
if (PlayState.instance == null) return;
|
||||
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
|
||||
|
||||
// Does nothing if we are minimal mode.
|
||||
if (PlayState.instance.isMinimalMode) return;
|
||||
|
||||
var zoom:Null<Float> = data.getFloat('zoom');
|
||||
if (zoom == null) zoom = 1.0;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.play.song;
|
||||
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.IRegistryEntry;
|
||||
|
@ -13,9 +12,8 @@ import funkin.data.song.SongData.SongOffsets;
|
|||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongData.SongTimeFormat;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
|
@ -31,14 +29,44 @@ import openfl.utils.Assets;
|
|||
@:nullSafety
|
||||
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
||||
{
|
||||
public static final DEFAULT_SONGNAME:String = "Unknown";
|
||||
public static final DEFAULT_ARTIST:String = "Unknown";
|
||||
/**
|
||||
* The default value for the song's name
|
||||
*/
|
||||
public static final DEFAULT_SONGNAME:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The default value for the song's artist
|
||||
*/
|
||||
public static final DEFAULT_ARTIST:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The default value for the song's time format
|
||||
*/
|
||||
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* The default value for the song's divisions
|
||||
*/
|
||||
public static final DEFAULT_DIVISIONS:Null<Int> = null;
|
||||
|
||||
/**
|
||||
* The default value for whether the song loops.
|
||||
*/
|
||||
public static final DEFAULT_LOOPED:Bool = false;
|
||||
public static final DEFAULT_STAGE:String = "mainStage";
|
||||
|
||||
/**
|
||||
* The default value for the song's playable stage.
|
||||
*/
|
||||
public static final DEFAULT_STAGE:String = 'mainStage';
|
||||
|
||||
/**
|
||||
* The default value for the song's scroll speed.
|
||||
*/
|
||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||
|
||||
/**
|
||||
* The internal ID of the song.
|
||||
*/
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
|
@ -53,6 +81,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
final _metadata:Map<String, SongMetadata>;
|
||||
final difficulties:Map<String, SongDifficulty>;
|
||||
|
||||
/**
|
||||
* The list of variations a song has.
|
||||
*/
|
||||
public var variations(get, never):Array<String>;
|
||||
|
||||
function get_variations():Array<String>
|
||||
|
@ -65,6 +96,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*/
|
||||
public var validScore:Bool = true;
|
||||
|
||||
/**
|
||||
* The readable name of the song.
|
||||
*/
|
||||
public var songName(get, never):String;
|
||||
|
||||
function get_songName():String
|
||||
|
@ -74,6 +108,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
return DEFAULT_SONGNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* The artist of the song.
|
||||
*/
|
||||
public var songArtist(get, never):String;
|
||||
|
||||
function get_songArtist():String
|
||||
|
@ -101,7 +138,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
{
|
||||
for (vari in _data.playData.songVariations)
|
||||
{
|
||||
var variMeta = fetchVariationMetadata(id, vari);
|
||||
var variMeta:Null<SongMetadata> = fetchVariationMetadata(id, vari);
|
||||
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
|
||||
}
|
||||
}
|
||||
|
@ -115,27 +152,62 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
populateDifficulties();
|
||||
}
|
||||
|
||||
@:allow(funkin.play.song.Song)
|
||||
/**
|
||||
* Build a song from existing metadata rather than loading it from the `assets` folder.
|
||||
* Used by the Chart Editor.
|
||||
*
|
||||
* @param songId The ID of the song.
|
||||
* @param metadata The metadata of the song.
|
||||
* @param variations The list of variations this song has.
|
||||
* @param charts The chart data for each variation.
|
||||
* @param includeScript Whether to initialize the scripted class tied to the song, if it exists.
|
||||
* @param validScore Whether the song is elegible for highscores.
|
||||
* @return The constructed song object.
|
||||
*/
|
||||
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
||||
validScore:Bool = false):Song
|
||||
includeScript:Bool = true, validScore:Bool = false):Song
|
||||
{
|
||||
var result:Song = new Song(songId);
|
||||
@:privateAccess
|
||||
var result:Null<Song>;
|
||||
|
||||
if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
|
||||
{
|
||||
var songClassName:String = SongRegistry.instance.getScriptedEntryClassName(songId);
|
||||
|
||||
@:privateAccess
|
||||
result = SongRegistry.instance.createScriptedEntry(songClassName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@:privateAccess
|
||||
result = SongRegistry.instance.createEntry(songId);
|
||||
}
|
||||
|
||||
if (result == null) throw 'ERROR: Could not build Song instance ($songId), is the attached script bad?';
|
||||
|
||||
result._metadata.clear();
|
||||
for (meta in metadata)
|
||||
{
|
||||
result._metadata.set(meta.variation, meta);
|
||||
}
|
||||
|
||||
result.difficulties.clear();
|
||||
result.populateDifficulties();
|
||||
|
||||
for (variation => chartData in charts)
|
||||
{
|
||||
result.applyChartData(chartData, variation);
|
||||
}
|
||||
|
||||
result.validScore = validScore;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of the raw metadata for the song.
|
||||
* @return The metadata JSON objects for the song's variations.
|
||||
*/
|
||||
public function getRawMetadata():Array<SongMetadata>
|
||||
{
|
||||
return _metadata.values();
|
||||
|
@ -192,6 +264,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
|
||||
/**
|
||||
* Parse and cache the chart for all difficulties of this song.
|
||||
* @param force Whether to forcibly clear the list of charts first.
|
||||
*/
|
||||
public function cacheCharts(force:Bool = false):Void
|
||||
{
|
||||
|
|
|
@ -212,7 +212,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
else
|
||||
{
|
||||
// Initalize static sprite.
|
||||
propSprite.loadTexture(Paths.image(dataProp.assetPath));
|
||||
propSprite.loadTexture(dataProp.assetPath);
|
||||
|
||||
// Disables calls to update() for a performance boost.
|
||||
propSprite.active = false;
|
||||
|
|
|
@ -602,6 +602,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var enabledDebuggerPopup:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether song scripts should be enabled during playtesting.
|
||||
* You should probably check the box if the song has custom mechanics.
|
||||
*/
|
||||
var playtestSongScripts:Bool = true;
|
||||
|
||||
// Visuals
|
||||
|
||||
/**
|
||||
|
@ -1396,7 +1402,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
function get_currentSongId():String
|
||||
{
|
||||
return currentSongName.toLowerKebabCase().replace('.', '').replace(' ', '-');
|
||||
return currentSongName.toLowerKebabCase().replace(' ', '-').sanitize();
|
||||
}
|
||||
|
||||
var currentSongArtist(get, set):String;
|
||||
|
@ -5320,7 +5326,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var targetSong:Song;
|
||||
try
|
||||
{
|
||||
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
||||
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, playtestSongScripts, false);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
|
@ -5328,9 +5334,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return;
|
||||
}
|
||||
|
||||
LogStyle.WARNING.openConsole = enabledDebuggerPopup;
|
||||
LogStyle.ERROR.openConsole = enabledDebuggerPopup;
|
||||
|
||||
// TODO: Rework asset system so we can remove this.
|
||||
switch (currentSongStage)
|
||||
{
|
||||
|
@ -5348,7 +5351,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
Paths.setCurrentLevel('week6');
|
||||
case 'tankmanBattlefield':
|
||||
Paths.setCurrentLevel('week7');
|
||||
case 'phillyStreets' | 'phillyBlazin':
|
||||
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
||||
Paths.setCurrentLevel('weekend1');
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import funkin.audio.FunkinSound;
|
|||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.assets.SoundUtil;
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
|
@ -129,41 +129,41 @@ class ChartEditorAudioHandler
|
|||
|
||||
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool
|
||||
{
|
||||
var perfA:Float = TimerTools.start();
|
||||
var perfA:Float = TimerUtil.start();
|
||||
|
||||
var result:Bool = playInstrumental(state, instId);
|
||||
if (!result) return false;
|
||||
|
||||
var perfB:Float = TimerTools.start();
|
||||
var perfB:Float = TimerUtil.start();
|
||||
|
||||
stopExistingVocals(state);
|
||||
|
||||
var perfC:Float = TimerTools.start();
|
||||
var perfC:Float = TimerUtil.start();
|
||||
|
||||
result = playVocals(state, BF, playerId, instId);
|
||||
|
||||
var perfD:Float = TimerTools.start();
|
||||
var perfD:Float = TimerUtil.start();
|
||||
|
||||
// if (!result) return false;
|
||||
result = playVocals(state, DAD, opponentId, instId);
|
||||
// if (!result) return false;
|
||||
|
||||
var perfE:Float = TimerTools.start();
|
||||
var perfE:Float = TimerUtil.start();
|
||||
|
||||
state.hardRefreshOffsetsToolbox();
|
||||
|
||||
var perfF:Float = TimerTools.start();
|
||||
var perfF:Float = TimerUtil.start();
|
||||
|
||||
state.hardRefreshFreeplayToolbox();
|
||||
|
||||
var perfG:Float = TimerTools.start();
|
||||
var perfG:Float = TimerUtil.start();
|
||||
|
||||
trace('Switched to instrumental in ${TimerTools.seconds(perfA, perfB)}.');
|
||||
trace('Stopped existing vocals in ${TimerTools.seconds(perfB, perfC)}.');
|
||||
trace('Played BF vocals in ${TimerTools.seconds(perfC, perfD)}.');
|
||||
trace('Played DAD vocals in ${TimerTools.seconds(perfD, perfE)}.');
|
||||
trace('Hard refreshed offsets toolbox in ${TimerTools.seconds(perfE, perfF)}.');
|
||||
trace('Hard refreshed freeplay toolbox in ${TimerTools.seconds(perfF, perfG)}.');
|
||||
trace('Switched to instrumental in ${TimerUtil.seconds(perfA, perfB)}.');
|
||||
trace('Stopped existing vocals in ${TimerUtil.seconds(perfB, perfC)}.');
|
||||
trace('Played BF vocals in ${TimerUtil.seconds(perfC, perfD)}.');
|
||||
trace('Played DAD vocals in ${TimerUtil.seconds(perfD, perfE)}.');
|
||||
trace('Hard refreshed offsets toolbox in ${TimerUtil.seconds(perfE, perfF)}.');
|
||||
trace('Hard refreshed freeplay toolbox in ${TimerUtil.seconds(perfF, perfG)}.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -175,9 +175,9 @@ class ChartEditorAudioHandler
|
|||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData);
|
||||
trace('Built instrumental track in ${TimerTools.seconds(perfStart)} seconds.');
|
||||
trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.');
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
|
@ -205,9 +205,9 @@ class ChartEditorAudioHandler
|
|||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData);
|
||||
trace('Built vocal track in ${TimerTools.seconds(perfStart)}.');
|
||||
trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
|
||||
|
@ -218,9 +218,9 @@ class ChartEditorAudioHandler
|
|||
case BF:
|
||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.');
|
||||
trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (waveformData != null)
|
||||
{
|
||||
|
@ -244,9 +244,9 @@ class ChartEditorAudioHandler
|
|||
case DAD:
|
||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.');
|
||||
trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (waveformData != null)
|
||||
{
|
||||
|
|
|
@ -318,6 +318,17 @@ class ChartEditorToolboxHandler
|
|||
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
||||
};
|
||||
|
||||
var checkboxSongScripts:Null<CheckBox> = toolbox.findComponent('playtestSongScriptsCheckbox', CheckBox);
|
||||
|
||||
if (checkboxSongScripts == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestSongScriptsCheckbox component.';
|
||||
|
||||
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||
|
||||
checkboxSongScripts.onClick = _ -> {
|
||||
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||
};
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import funkin.audio.waveform.WaveformDataParser;
|
|||
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
|
||||
import funkin.ui.haxeui.components.WaveformPlayer;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
import haxe.ui.backend.flixel.components.SpriteWrapper;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
|
@ -289,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
|
|||
|
||||
// Build player waveform.
|
||||
// waveformMusic.waveform.forceUpdate = true;
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData1 = playerVoice?.waveformData;
|
||||
var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file!
|
||||
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
|
||||
var waveformData = waveformData3.merge(waveformData1).merge(waveformData2);
|
||||
trace('Waveform data merging took: ${TimerTools.seconds(perfStart)}');
|
||||
trace('Waveform data merging took: ${TimerUtil.seconds(perfStart)}');
|
||||
|
||||
waveformMusic.waveform.waveformData = waveformData;
|
||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
package funkin.ui.freeplay;
|
||||
|
||||
import flash.text.TextField;
|
||||
import openfl.text.TextField;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.ui.FlxInputText;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.group.FlxGroup;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.debug.watch.Tracker.TrackerProfile;
|
||||
import flixel.text.FlxText;
|
||||
|
@ -24,9 +22,12 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.AngleMask;
|
||||
import funkin.graphics.shaders.HSVShader;
|
||||
import funkin.graphics.shaders.PureColor;
|
||||
|
@ -187,10 +188,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
isDebug = true;
|
||||
#end
|
||||
|
||||
if (FlxG.sound.music == null || (FlxG.sound.music != null && !FlxG.sound.music.playing))
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
}
|
||||
FunkinSound.playMusic('freakyMenu');
|
||||
|
||||
// Add a null entry that represents the RANDOM option
|
||||
songs.push(null);
|
||||
|
@ -227,7 +225,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
trace(FlxG.camera.initialZoom);
|
||||
trace(FlxCamera.defaultZoom);
|
||||
|
||||
var pinkBack:FunkinSprite = FunkinSprite.create(Paths.image('freeplay/pinkBack'));
|
||||
var pinkBack:FunkinSprite = FunkinSprite.create('freeplay/pinkBack');
|
||||
pinkBack.color = 0xFFffd4e9; // sets it to pink!
|
||||
pinkBack.x -= pinkBack.width;
|
||||
|
||||
|
@ -590,7 +588,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
|
||||
public function generateSongList(?filterStuff:SongFilter, force:Bool = false)
|
||||
public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void
|
||||
{
|
||||
curSelected = 1;
|
||||
|
||||
|
@ -693,7 +691,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var busy:Bool = false; // Set to true once the user has pressed enter to select a song.
|
||||
|
||||
override function update(elapsed:Float)
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
|
@ -983,7 +981,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
function changeDiff(change:Int = 0)
|
||||
function changeDiff(change:Int = 0):Void
|
||||
{
|
||||
touchTimer = 0;
|
||||
|
||||
|
@ -1173,7 +1171,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
difficultyStars.difficulty = daSong?.songRating ?? 0;
|
||||
}
|
||||
|
||||
function changeSelection(change:Int = 0)
|
||||
function changeSelection(change:Int = 0):Void
|
||||
{
|
||||
// NGio.logEvent('Fresh');
|
||||
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
|
||||
|
@ -1228,7 +1226,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
// TODO: Stream the instrumental of the selected song?
|
||||
if (prevSelected == 0)
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
FunkinSound.playMusic('freakyMenu');
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
}
|
||||
|
@ -1259,7 +1257,7 @@ class DifficultySelector extends FlxSprite
|
|||
flipX = flipped;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
if (flipX && controls.UI_RIGHT_P) moveShitDown();
|
||||
if (!flipX && controls.UI_LEFT_P) moveShitDown();
|
||||
|
@ -1267,7 +1265,7 @@ class DifficultySelector extends FlxSprite
|
|||
super.update(elapsed);
|
||||
}
|
||||
|
||||
function moveShitDown()
|
||||
function moveShitDown():Void
|
||||
{
|
||||
offset.y -= 5;
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ import flixel.util.typeLimit.NextState;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import flixel.util.FlxTimer;
|
||||
|
@ -51,7 +53,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (!(FlxG?.sound?.music?.playing ?? false))
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
playMenuMusic();
|
||||
}
|
||||
|
||||
persistentUpdate = persistentDraw = true;
|
||||
|
@ -151,6 +153,11 @@ class MainMenuState extends MusicBeatState
|
|||
// NG.core.calls.event.logEvent('swag').send();
|
||||
}
|
||||
|
||||
function playMenuMusic():Void
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu');
|
||||
}
|
||||
|
||||
function resetCamStuff()
|
||||
{
|
||||
FlxG.cameras.reset(new FunkinCamera());
|
||||
|
|
|
@ -16,6 +16,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayState;
|
||||
|
@ -234,17 +235,7 @@ class StoryMenuState extends MusicBeatState
|
|||
|
||||
function playMenuMusic():Void
|
||||
{
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
|
||||
{
|
||||
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
|
||||
if (freakyMenuMetadata != null)
|
||||
{
|
||||
Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
}
|
||||
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
}
|
||||
FunkinSound.playMusic('freakyMenu');
|
||||
}
|
||||
|
||||
function updateData():Void
|
||||
|
|
|
@ -18,6 +18,7 @@ import funkin.graphics.FunkinSprite;
|
|||
import funkin.ui.MusicBeatState;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.graphics.shaders.TitleOutline;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.AtlasText;
|
||||
import openfl.Assets;
|
||||
|
@ -219,16 +220,11 @@ class TitleState extends MusicBeatState
|
|||
|
||||
function playMenuMusic():Void
|
||||
{
|
||||
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
|
||||
{
|
||||
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
|
||||
if (freakyMenuMetadata != null)
|
||||
{
|
||||
Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
|
||||
}
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
|
||||
FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
}
|
||||
var shouldFadeIn = (FlxG.sound.music == null);
|
||||
// Load music. Includes logic to handle BPM changes.
|
||||
FunkinSound.playMusic('freakyMenu', false, true);
|
||||
// Fade from 0.0 to 0.7 over 4 seconds
|
||||
if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7);
|
||||
}
|
||||
|
||||
function getIntroTextShit():Array<Array<String>>
|
||||
|
|
|
@ -48,7 +48,7 @@ class LoadingState extends MusicBeatState
|
|||
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d);
|
||||
add(bg);
|
||||
|
||||
funkay = FunkinSprite.create(Paths.image('funkay'));
|
||||
funkay = FunkinSprite.create('funkay');
|
||||
funkay.setGraphicSize(0, FlxG.height);
|
||||
funkay.updateHitbox();
|
||||
add(funkay);
|
||||
|
@ -238,11 +238,38 @@ class LoadingState extends MusicBeatState
|
|||
FunkinSprite.cacheTexture(Paths.image('shit', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this
|
||||
|
||||
// List all image assets in the level's library.
|
||||
// This is crude and I want to remove it when we have a proper asset caching system.
|
||||
// TODO: Get rid of this junk!
|
||||
var library = openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId);
|
||||
var assets = library.list(lime.utils.AssetType.IMAGE);
|
||||
trace('Got ${assets.length} assets: ${assets}');
|
||||
|
||||
// TODO: assets includes non-images! This is a bug with Polymod
|
||||
for (asset in assets)
|
||||
{
|
||||
// Exclude items of the wrong type.
|
||||
var path = '${PlayStatePlaylist.campaignId}:${asset}';
|
||||
// TODO DUMB HACK DUMB HACK why doesn't filtering by AssetType.IMAGE above work
|
||||
// I will fix this properly later I swear -eric
|
||||
if (!path.endsWith('.png')) continue;
|
||||
|
||||
FunkinSprite.cacheTexture(path);
|
||||
|
||||
// Another dumb hack: FlxAnimate fetches from OpenFL's BitmapData cache directly and skips the FlxGraphic cache.
|
||||
// Since FlxGraphic tells OpenFL to not cache it, we have to do it manually.
|
||||
if (path.endsWith('spritemap1.png'))
|
||||
{
|
||||
openfl.Assets.getBitmapData(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
// FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above!
|
||||
// FunkinSprite.cacheAllCharacterTextures(player)
|
||||
// FunkinSprite.cacheAllCharacterTextures(girlfriend)
|
||||
// FunkinSprite.cacheAllCharacterTextures(opponent)
|
||||
// FunkinSprite.cacheAllStageTextures(stage)
|
||||
// FunkinSprite.cacheAllSongTextures(stage)
|
||||
|
||||
FunkinSprite.purgeCache();
|
||||
|
||||
|
@ -389,9 +416,15 @@ class MultiCallback
|
|||
public function getUnfired():Array<Void->Void>
|
||||
return unfired.array();
|
||||
|
||||
/**
|
||||
* Perform an FlxG.switchState with a nice transition
|
||||
* @param state
|
||||
* @param transitionTex
|
||||
* @param time
|
||||
*/
|
||||
public static function coolSwitchState(state:NextState, transitionTex:String = "shaderTransitionStuff/coolDots", time:Float = 2)
|
||||
{
|
||||
var screenShit:FunkinSprite = FunkinSprite.create(Paths.image("shaderTransitionStuff/coolDots"));
|
||||
var screenShit:FunkinSprite = FunkinSprite.create('shaderTransitionStuff/coolDots');
|
||||
var screenWipeShit:ScreenWipeShader = new ScreenWipeShader();
|
||||
|
||||
screenWipeShit.funnyShit.input = screenShit.pixels;
|
||||
|
|
|
@ -313,7 +313,7 @@ class StickerSprite extends FunkinSprite
|
|||
public function new(x:Float, y:Float, stickerSet:String, stickerName:String):Void
|
||||
{
|
||||
super(x, y);
|
||||
loadTexture(Paths.image('transitionSwag/' + stickerSet + '/' + stickerName));
|
||||
loadTexture('transitionSwag/' + stickerSet + '/' + stickerName);
|
||||
updateHitbox();
|
||||
scrollFactor.set();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package funkin.util.tools;
|
||||
package funkin.util;
|
||||
|
||||
import funkin.util.tools.FloatTools;
|
||||
import haxe.Timer;
|
||||
|
||||
class TimerTools
|
||||
class TimerUtil
|
||||
{
|
||||
public static function start():Float
|
||||
{
|
76
source/funkin/util/logging/Perf.hx
Normal file
76
source/funkin/util/logging/Perf.hx
Normal file
|
@ -0,0 +1,76 @@
|
|||
package funkin.util.logging;
|
||||
|
||||
/**
|
||||
* A small utility class for timing how long functions take.
|
||||
* Specify a string as a label (or don't, by default it uses the name of the function it was called from.)
|
||||
*
|
||||
* Example:
|
||||
* ```haxe
|
||||
*
|
||||
* var perf = new Perf();
|
||||
* ...
|
||||
* perf.print();
|
||||
* ```
|
||||
*/
|
||||
class Perf
|
||||
{
|
||||
final startTime:Float;
|
||||
final label:Null<String>;
|
||||
final posInfos:Null<haxe.PosInfos>;
|
||||
|
||||
/**
|
||||
* Create a new performance marker.
|
||||
* @param label Optionally specify a label to use for the performance marker. Defaults to the function name.
|
||||
* @param posInfos The position of the calling function. Used to build the default label.
|
||||
* Note: `haxe.PosInfos` is magic and automatically populated by the compiler!
|
||||
*/
|
||||
public function new(?label:String, ?posInfos:haxe.PosInfos)
|
||||
{
|
||||
this.label = label;
|
||||
this.posInfos = posInfos;
|
||||
startTime = current();
|
||||
}
|
||||
|
||||
/**
|
||||
* The current timestamp, in fractional seconds.
|
||||
* @return The current timestamp.
|
||||
*/
|
||||
static function current():Float
|
||||
{
|
||||
#if sys
|
||||
// This one is more accurate if it's available.
|
||||
return Sys.time();
|
||||
#else
|
||||
return haxe.Timer.stamp();
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* The duration in seconds since this `Perf` was created.
|
||||
* @return The duration in seconds
|
||||
*/
|
||||
public function duration():Float
|
||||
{
|
||||
return current() - startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* A rounded millisecond duration
|
||||
* @return The duration in milliseconds
|
||||
*/
|
||||
public function durationClean():Float
|
||||
{
|
||||
var round:Float = 100;
|
||||
return Math.floor(duration() * Constants.MS_PER_SEC * round) / round;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanly prints the duration since this `Perf` was created.
|
||||
*/
|
||||
public function print():Void
|
||||
{
|
||||
var label:String = label ?? (posInfos == null ? 'unknown' : '${posInfos.className}#${posInfos.methodName}()');
|
||||
|
||||
trace('[PERF] [$label] Took ${durationClean()}ms.');
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.util.plugins;
|
||||
|
||||
import flixel.FlxBasic;
|
||||
import funkin.util.tools.TimerTools;
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
/**
|
||||
* A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection.
|
||||
|
@ -24,9 +24,9 @@ class MemoryGCPlugin extends FlxBasic
|
|||
|
||||
if (FlxG.keys.justPressed.INSERT)
|
||||
{
|
||||
var perfStart:Float = TimerTools.start();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
funkin.util.MemoryUtil.collect(true);
|
||||
trace('Memory GC took: ${TimerTools.seconds(perfStart)}');
|
||||
trace('Memory GC took: ${TimerUtil.seconds(perfStart)}');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,20 +13,50 @@ class StringTools
|
|||
*/
|
||||
public static function toTitleCase(value:String):String
|
||||
{
|
||||
var words:Array<String> = value.split(" ");
|
||||
var result:String = "";
|
||||
var words:Array<String> = value.split(' ');
|
||||
var result:String = '';
|
||||
for (i in 0...words.length)
|
||||
{
|
||||
var word:String = words[i];
|
||||
result += word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
|
||||
if (i < words.length - 1)
|
||||
{
|
||||
result += " ";
|
||||
result += ' ';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip a given prefix from a string.
|
||||
* @param value The string to strip.
|
||||
* @param prefix The prefix to strip. If the prefix isn't found, the original string is returned.
|
||||
* @return The stripped string.
|
||||
*/
|
||||
public static function stripPrefix(value:String, prefix:String):String
|
||||
{
|
||||
if (value.startsWith(prefix))
|
||||
{
|
||||
return value.substr(prefix.length);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip a given suffix from a string.
|
||||
* @param value The string to strip.
|
||||
* @param suffix The suffix to strip. If the suffix isn't found, the original string is returned.
|
||||
* @return The stripped string.
|
||||
*/
|
||||
public static function stripSuffix(value:String, suffix:String):String
|
||||
{
|
||||
if (value.endsWith(suffix))
|
||||
{
|
||||
return value.substr(0, value.length - suffix.length);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to lower kebab case. For example, "Hello World" becomes "hello-world".
|
||||
*
|
||||
|
@ -35,7 +65,7 @@ class StringTools
|
|||
*/
|
||||
public static function toLowerKebabCase(value:String):String
|
||||
{
|
||||
return value.toLowerCase().replace(' ', "-");
|
||||
return value.toLowerCase().replace(' ', '-');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,13 +76,30 @@ class StringTools
|
|||
*/
|
||||
public static function toUpperKebabCase(value:String):String
|
||||
{
|
||||
return value.toUpperCase().replace(' ', "-");
|
||||
return value.toUpperCase().replace(' ', '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* The regular expression to sanitize strings.
|
||||
*/
|
||||
static final SANTIZE_REGEX:EReg = ~/[^-a-zA-Z0-9]/g;
|
||||
|
||||
/**
|
||||
* Remove all instances of symbols other than alpha-numeric characters (and dashes)from a string.
|
||||
* @param value The string to sanitize.
|
||||
* @return The sanitized string.
|
||||
*/
|
||||
public static function sanitize(value:String):String
|
||||
{
|
||||
return SANTIZE_REGEX.replace(value, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string data as JSON and returns the resulting object.
|
||||
* This is here so you can use `string.parseJSON()` when `using StringTools`.
|
||||
*
|
||||
* TODO: Remove this and replace with `json2object`
|
||||
* @param value The
|
||||
* @return The parsed object.
|
||||
*/
|
||||
public static function parseJSON(value:String):Dynamic
|
||||
|
|
Loading…
Reference in a new issue