new highscore system

This commit is contained in:
Yoshman29
2022-11-03 04:03:26 +01:00
parent daa35cbdad
commit 46d3d956b0
13 changed files with 215 additions and 217 deletions
+1 -1
View File
@@ -1 +1 @@
1002
1009
-74
View File
@@ -1,74 +0,0 @@
package funkin.data;
import flixel.util.FlxColor;
import openfl.utils.Assets;
import funkin.menus.FreeplayState;
import haxe.Json;
class FreeplaySonglist {
public var addOGSongs:Bool = false;
public var songs:Array<SongMetadata> = [];
public function new() {
}
private function _addJSONSongs(songs:Array<FreeplaySong>) {
if (songs != null && songs is Array) {
for(e in songs) {
if (e is Dynamic) {
if (e.name == null) continue;
if (e.icon == null) e.icon = "bf";
if (e.color == null) e.color = FreeplayState.defaultColor;
this.songs.push(new SongMetadata(e.name,
CoolUtil.getDefault(e.icon, "bf"),
CoolUtil.getDefault(CoolUtil.getColorFromDynamic(e.color), FreeplayState.defaultColor)));
}
}
}
}
public static function get() {
var songList = new FreeplaySonglist();
var jsonPath = Paths.json("freeplaySonglist");
var baseJsonPath = Paths.getPath('data/freeplaySonglist.json', TEXT, null, true);
try {
var json:FreeplayJSON = Json.parse(Assets.getText(jsonPath));
var addOGSongs = CoolUtil.getDefault(json.addOGSongs, true);
songList._addJSONSongs(json.songs);
if (addOGSongs && (jsonPath != baseJsonPath)) {
var json:FreeplayJSON = Json.parse(Assets.getText(baseJsonPath));
songList._addJSONSongs(json.songs);
}
}
return songList;
}
}
typedef FreeplayJSON = {
public var addOGSongs:Null<Bool>;
public var songs:Array<FreeplaySong>;
}
typedef FreeplaySong = {
public var name:String;
public var icon:String;
public var color:Dynamic;
}
class SongMetadata
{
public var songName:String = "";
public var color:FlxColor = FreeplayState.defaultColor;
public var songCharacter:String = "";
public function new(song:String, songCharacter:String, color:FlxColor)
{
this.songName = song;
this.color = color;
this.songCharacter = songCharacter;
}
}
+1 -1
View File
@@ -1004,7 +1004,7 @@ class ChartingState extends MusicBeatState
function loadJson(song:String):Void
{
PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), CoolUtil.difficultyString());
PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase(), PlayState.difficulty);
FlxG.resetState();
}
+60 -43
View File
@@ -4,84 +4,101 @@ import flixel.FlxG;
class Highscore
{
#if (haxe >= "4.0.0")
public static var songScores:Map<String, Int> = new Map();
#else
public static var songScores:Map<String, Int> = new Map<String, Int>();
#end
public static var songScores:Map<String, SongScore> = new Map();
public static var weekScores:Map<String, SongScore> = new Map();
public static function saveScore(song:String, score:Int = 0, ?diff:Int = 0):Void
public static function saveScore(song:String, score:SongScore, ?diff:String = "normal"):Void
{
prepareScore(score);
var daSong:String = formatSong(song, diff);
if (songScores.exists(daSong))
{
if (songScores.get(daSong) < score)
setScore(daSong, score);
}
else
if (!songScores.exists(daSong) || songScores.get(daSong).score < score.score)
setScore(daSong, score);
}
public static function saveWeekScore(week:Int = 1, score:Int = 0, ?diff:Int = 0):Void
public static function saveWeekScore(weekName:String, score:SongScore, ?diff:String = "normal"):Void
{
prepareScore(score);
var daWeek:String = formatSong(weekName, diff);
var daWeek:String = formatSong('week' + week, diff);
if (songScores.exists(daWeek))
{
if (songScores.get(daWeek) < score)
setScore(daWeek, score);
}
else
setScore(daWeek, score);
if (!weekScores.exists(daWeek) || weekScores.get(daWeek).score < score.score)
setWeekScore(daWeek, score);
}
/**
* YOU SHOULD FORMAT SONG WITH formatSong() BEFORE TOSSING IN SONG VARIABLE
*/
static function setScore(song:String, score:Int):Void
static function setScore(song:String, score:SongScore):Void
{
// Reminder that I don't need to format this song, it should come formatted!
if (score == null) return;
songScores.set(song, score);
save();
}
static function setWeekScore(song:String, score:SongScore):Void
{
if (score == null) return;
weekScores.set(song, score);
save();
}
public static function save() {
FlxG.save.data.songScores = songScores;
FlxG.save.data.weekScores = weekScores;
FlxG.save.flush();
}
public static function formatSong(song:String, diff:Int):String
public static function formatSong(song:String, diff:String):String
{
var daSong:String = song;
if (diff == 0)
daSong += '-easy';
else if (diff == 2)
daSong += '-hard';
diff = diff.toLowerCase();
if (diff != "normal")
daSong += '-$diff';
return daSong;
}
public static function getScore(song:String, diff:Int):Int
public static function getScore(song:String, diff:String):SongScore
{
if (!songScores.exists(formatSong(song, diff)))
setScore(formatSong(song, diff), 0);
var daSong = formatSong(song, diff);
if (!songScores.exists(daSong))
setScore(daSong, {});
return songScores.get(formatSong(song, diff));
return prepareScore(songScores.get(formatSong(song, diff)));
}
public static function getWeekScore(week:Int, diff:Int):Int
public static function getWeekScore(week:String, diff:String):SongScore
{
if (!songScores.exists(formatSong('week' + week, diff)))
setScore(formatSong('week' + week, diff), 0);
var daWeek = formatSong(week, diff);
if (!weekScores.exists(daWeek))
setScore(daWeek, {});
return songScores.get(formatSong('week' + week, diff));
return prepareScore(weekScores.get(daWeek));
}
public static function prepareScore(score:SongScore):SongScore {
if (score == null) score = {};
score.setFieldDefault('score', 0);
score.setFieldDefault('accuracy', 0);
score.setFieldDefault('misses', 0);
score.setFieldDefault('hits', new Map<String, Int>());
return score;
}
public static function load():Void
{
if (FlxG.save.data.songScores != null)
{
songScores = FlxG.save.data.songScores;
weekScores = FlxG.save.data.weekScores;
}
for(k=>e in songScores)
if (e is Int)
songScores.remove(k);
}
}
typedef SongScore = {
@:optional var score:Int;
@:optional var accuracy:Float;
@:optional var misses:Int;
@:optional var hits:Map<String, Int>;
}
+23 -18
View File
@@ -1,5 +1,6 @@
package funkin.game;
import funkin.menus.StoryMenuState.WeekData;
import funkin.ui.FunkinText;
import flixel.group.FlxSpriteGroup;
import funkin.options.Options;
@@ -67,9 +68,9 @@ class PlayState extends MusicBeatState
*/
public static var SONG:SwagSong;
public static var isStoryMode:Bool = false;
public static var storyWeek:Int = 0;
public static var storyWeek:WeekData = null;
public static var storyPlaylist:Array<String> = [];
public static var storyDifficulty:Int = 1;
public static var difficulty:String = "normal";
public static var fromMods:Bool = false;
public var scripts:ScriptPack;
@@ -142,7 +143,7 @@ class PlayState extends MusicBeatState
#if desktop
// Discord RPC variables
public var storyDifficultyText:String = "";
public var difficultyText:String = "";
public var iconRPC:String = "";
public var songLength:Float = 0;
public var detailsText:String = "";
@@ -186,7 +187,7 @@ class PlayState extends MusicBeatState
// String that contains the mode defined here so it isn't necessary to call changePresence for each mode
if (isStoryMode)
{
detailsText = "Story Mode: Week " + storyWeek;
detailsText = "Story Mode: " + storyWeek.name;
}
else
{
@@ -197,7 +198,7 @@ class PlayState extends MusicBeatState
detailsPausedText = "Paused - " + detailsText;
// Updating Discord Rich Presence.
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC);
#end
dad = new Character(100, 100, SONG.player2);
@@ -625,11 +626,11 @@ class PlayState extends MusicBeatState
#if desktop
if (startTimer.finished)
{
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength - Conductor.songPosition);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC, true, songLength - Conductor.songPosition);
}
else
{
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC);
}
#end
}
@@ -645,11 +646,11 @@ class PlayState extends MusicBeatState
{
if (Conductor.songPosition > 0.0)
{
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength - Conductor.songPosition);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC, true, songLength - Conductor.songPosition);
}
else
{
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC);
}
}
#end
@@ -663,7 +664,7 @@ class PlayState extends MusicBeatState
#if desktop
if (health > 0 && !paused)
{
DiscordClient.changePresence(detailsPausedText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
DiscordClient.changePresence(detailsPausedText, SONG.song + " (" + difficultyText + ")", iconRPC);
}
#end
@@ -713,7 +714,7 @@ class PlayState extends MusicBeatState
songLength = FlxG.sound.music.length;
// Updating Discord Rich Presence (with Time Left)
DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength);
DiscordClient.changePresence(detailsText, SONG.song + " (" + difficultyText + ")", iconRPC, true, songLength);
#end
scripts.call("onDiscordPresenceUpdate");
}
@@ -915,7 +916,7 @@ class PlayState extends MusicBeatState
#if desktop
// Game Over doesn't get his own variable because it's only used here
DiscordClient.changePresence("Game Over - " + detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC);
DiscordClient.changePresence("Game Over - " + detailsText, SONG.song + " (" + difficultyText + ")", iconRPC);
#end
}
@@ -995,7 +996,11 @@ class PlayState extends MusicBeatState
if (SONG.validScore)
{
#if !switch
Highscore.saveScore(SONG.song, songScore, storyDifficulty);
// TODO: Accuracy stuff
Highscore.saveScore(SONG.song, {
score: songScore,
misses: misses
}, difficulty);
#end
}
@@ -1015,17 +1020,17 @@ class PlayState extends MusicBeatState
if (SONG.validScore)
{
Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty);
// TODO: more week info saving
Highscore.saveWeekScore(storyWeek.name, {
score: campaignScore
}, difficulty);
}
FlxG.save.flush();
}
else
{
// TODO: NOT INT DIFFICULTIES
var difficulty:String = CoolUtil.difficultyString();
trace('LOADING NEXT SONG');
trace(PlayState.storyPlaylist[0].toLowerCase() + difficulty);
trace(PlayState.storyPlaylist[0].toLowerCase(), difficulty);
if (SONG.song.toLowerCase() == 'eggnog')
{
+97 -57
View File
@@ -14,10 +14,10 @@ import flixel.text.FlxText;
import flixel.util.FlxColor;
import lime.utils.Assets;
import funkin.system.Song;
import funkin.data.FreeplaySonglist;
import funkin.ui.Alphabet;
import funkin.game.HealthIcon;
import funkin.game.Highscore;
import haxe.Json;
using StringTools;
@@ -116,7 +116,7 @@ class FreeplayState extends MusicBeatState
add(scoreText);
changeSelection();
changeSelection(0, true);
changeDiff();
// FlxG.sound.playMusic(Paths.music('title'), 0);
@@ -186,29 +186,14 @@ class FreeplayState extends MusicBeatState
#if PRELOAD_ALL
autoplayElapsed += elapsed;
if (!songInstPlaying && (autoplayElapsed > timeUntilAutoplay || FlxG.keys.justPressed.SPACE)) {
if (curPlayingInst != (curPlayingInst = Paths.inst(songs[curSelected].songName)))
if (curPlayingInst != (curPlayingInst = Paths.inst(songs[curSelected].songName, songs[curSelected].difficulties[curDifficulty])))
FlxG.sound.playMusic(curPlayingInst, 0);
songInstPlaying = true;
}
#end
var upP = controls.UP_P;
var downP = controls.DOWN_P;
var accepted = controls.ACCEPT;
if (upP)
{
changeSelection(-1);
}
if (downP)
{
changeSelection(1);
}
if (controls.LEFT_P)
changeDiff(-1);
if (controls.RIGHT_P)
changeDiff(1);
changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0));
changeDiff((controls.LEFT_P ? -1 : 0) + (controls.RIGHT_P ? 1 : 0));
if (controls.BACK)
{
@@ -216,10 +201,9 @@ class FreeplayState extends MusicBeatState
FlxG.switchState(new MainMenuState());
}
if (accepted)
if (controls.ACCEPT)
{
PlayState.storyDifficulty = curDifficulty;
PlayState.SONG = Song.loadFromJson(songs[curSelected].songName, CoolUtil.difficultyString());
PlayState.SONG = Song.loadFromJson(songs[curSelected].songName, songs[curSelected].difficulties[curDifficulty]);
PlayState.isStoryMode = false;
// PlayState.storyWeek = songs[curSelected].week;
@@ -228,46 +212,34 @@ class FreeplayState extends MusicBeatState
}
}
function changeDiff(change:Int = 0)
function changeDiff(change:Int = 0, force:Bool = false)
{
curDifficulty += change;
if (change == 0 && !force) return;
if (curDifficulty < 0)
curDifficulty = 2;
if (curDifficulty > 2)
curDifficulty = 0;
var curSong = songs[curSelected];
curDifficulty = FlxMath.wrap(curDifficulty + change, 0, curSong.difficulties.length-1);
#if !switch
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
intendedScore = Highscore.getScore(songs[curSelected].songName, songs[curSelected].difficulties[curDifficulty]).score;
#end
switch (curDifficulty)
{
case 0:
diffText.text = "< EASY >";
case 1:
diffText.text = '< NORMAL >';
case 2:
diffText.text = "< HARD >";
}
if (curSong.difficulties.length > 1)
diffText.text = '< ${curSong.difficulties[curDifficulty]} >';
else
diffText.text = curSong.difficulties[curDifficulty];
}
function changeSelection(change:Int = 0)
function changeSelection(change:Int = 0, force:Bool = false)
{
if (change == 0 && !force) return;
CoolUtil.playMenuSFX(0, 0.4);
curSelected += change;
if (curSelected < 0)
curSelected = songs.length - 1;
if (curSelected >= songs.length)
curSelected = 0;
// selector.y = (70 * curSelected) + 30;
curSelected = FlxMath.wrap(curSelected + change, 0, songs.length-1);
changeDiff(0, true);
#if !switch
intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
// lerpScore = 0;
intendedScore = Highscore.getScore(songs[curSelected].songName, songs[curSelected].difficulties[curDifficulty]).score;
#end
#if PRELOAD_ALL
@@ -280,9 +252,7 @@ class FreeplayState extends MusicBeatState
var bullShit:Int = 0;
for (i in 0...iconArray.length)
{
iconArray[i].alpha = lerp(iconArray[i].alpha, #if PRELOAD_ALL songInstPlaying ? 0.45 : #end 0.6, 0.25);
}
iconArray[curSelected].alpha = 1;
@@ -292,13 +262,83 @@ class FreeplayState extends MusicBeatState
bullShit++;
item.alpha = lerp(item.alpha, #if PRELOAD_ALL songInstPlaying ? 0.45 : #end 0.6, 0.25);
// item.setGraphicSize(Std.int(item.width * 0.8));
if (item.targetY == 0)
{
item.alpha = 1;
// item.setGraphicSize(Std.int(item.width));
}
}
}
}
}
class FreeplaySonglist {
public var addOGSongs:Bool = false;
public var songs:Array<SongMetadata> = [];
public function new() {
}
private function _addJSONSongs(songs:Array<FreeplaySong>) {
if (songs != null && songs is Array) {
for(e in songs) {
if (e is Dynamic) {
if (e.name == null) continue;
if (e.icon == null) e.icon = "bf";
if (e.color == null) e.color = FreeplayState.defaultColor;
this.songs.push(new SongMetadata(e.name,
e.icon.getDefault("bf"),
CoolUtil.getColorFromDynamic(e.color).getDefault(FreeplayState.defaultColor), e.difficulties));
}
}
}
}
public static function get() {
var songList = new FreeplaySonglist();
var jsonPath = Paths.json("freeplaySonglist");
var baseJsonPath = Paths.getPath('data/freeplaySonglist.json', TEXT, null, true);
try {
var json:FreeplayJSON = Json.parse(Assets.getText(jsonPath));
var addOGSongs = CoolUtil.getDefault(json.addOGSongs, true);
songList._addJSONSongs(json.songs);
if (addOGSongs && (jsonPath != baseJsonPath)) {
var json:FreeplayJSON = Json.parse(Assets.getText(baseJsonPath));
songList._addJSONSongs(json.songs);
}
}
return songList;
}
}
typedef FreeplayJSON = {
public var addOGSongs:Null<Bool>;
public var songs:Array<FreeplaySong>;
}
typedef FreeplaySong = {
public var name:String;
public var icon:String;
public var color:Dynamic;
public var difficulties:Array<String>;
}
class SongMetadata
{
public var songName:String = "";
public var color:FlxColor = FreeplayState.defaultColor;
public var songCharacter:String = "";
public var difficulties:Array<String> = ["EASY", "NORMAL", "HARD"];
public function new(song:String, songCharacter:String, color:FlxColor, ?difficulties:Array<String>)
{
this.songName = song;
this.color = color;
this.songCharacter = songCharacter;
if (difficulties != null && difficulties.length > 0) {
this.difficulties = difficulties;
}
}
}
+2 -4
View File
@@ -38,15 +38,13 @@ class PauseSubState extends MusicBeatSubstate
bg.scrollFactor.set();
add(bg);
var levelInfo:FlxText = new FlxText(20, 15, 0, "", 32);
levelInfo.text += PlayState.SONG.song;
var levelInfo:FlxText = new FlxText(20, 15, 0, PlayState.SONG.song, 32);
levelInfo.scrollFactor.set();
levelInfo.setFormat(Paths.font("vcr.ttf"), 32);
levelInfo.updateHitbox();
add(levelInfo);
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, "", 32);
levelDifficulty.text += CoolUtil.difficultyString();
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, PlayState.difficulty, 32);
levelDifficulty.scrollFactor.set();
levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32);
levelDifficulty.updateHitbox();
+1 -1
View File
@@ -300,7 +300,7 @@ class StoryMenuState extends MusicBeatState
diffic = '-hard';
}
PlayState.storyDifficulty = curDifficulty;
PlayState.difficulty = curDifficulty;
PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase() + diffic, PlayState.storyPlaylist[0].toLowerCase());
PlayState.storyWeek = curWeek;
@@ -18,6 +18,8 @@ import flixel.FlxG;
class ChangeKeybindSubState extends MusicBeatSubstate {
var callback:FlxKey->Void;
var cancelCallback:Void->Void;
var stillPressed:Bool = true;
public override function new(callback:FlxKey->Void, cancelCallback:Void->Void) {
this.callback = callback;
this.cancelCallback = cancelCallback;
@@ -26,11 +28,15 @@ class ChangeKeybindSubState extends MusicBeatSubstate {
public override function update(elapsed:Float) {
super.update(elapsed);
var key:FlxKey = FlxG.keys.firstJustReleased();
if (stillPressed && controls.ACCEPT)
return;
stillPressed = false;
var key:FlxKey = FlxG.keys.firstJustPressed();
if (cast(key, Int) <= 0) return;
if (key == ESCAPE && !FlxG.keys.pressed.SHIFT) {
close();
close();
cancelCallback();
return;
}
@@ -140,8 +140,6 @@ class KeybindsOptions extends MusicBeatSubstate {
k++;
for(e in category.settings) {
var xOffset:Float = 200;
var sparrowIcon:String = null;
var sparrowAnim:String = null;
if (e.name.startsWith('{note')) {// is actually a note!!
@@ -222,6 +220,7 @@ class KeybindsOptions extends MusicBeatSubstate {
canSelect = true;
}, p2Selected);
}
return;
}
if (controls.LEFT_P || controls.RIGHT_P) {
+10 -5
View File
@@ -28,6 +28,16 @@ class CoolUtil
return v == null ? defaultValue : v;
}
public static function setFieldDefault<T>(v:Dynamic, name:String, defaultValue:T):T {
if (Reflect.hasField(v, name)) {
var f = Reflect.field(v, name);
if (f != null && Std.isOfType(f, T))
return cast f;
}
Reflect.setField(v, name, defaultValue);
return defaultValue;
}
public static function addZeros(str:String, num:Int) {
while(str.length < num) str = '0${str}';
return str;
@@ -130,11 +140,6 @@ class CoolUtil
}), volume);
}
public static function difficultyString():String
{
return difficultyArray[PlayState.storyDifficulty];
}
public static function coolTextFile(path:String):Array<String>
{
return [for(e in Assets.getText(path).trim().split('\n')) e.trim()];
+5 -5
View File
@@ -26,10 +26,10 @@ class Logs {
var color:ConsoleColor = LIGHTGRAY;
var level:Level = INFO;
if (Style == LogStyle.CONSOLE) {prefix = "> "; color = WHITE; level = INFO; }
if (Style == LogStyle.ERROR) {prefix = "[FLIXEL ERROR]"; color = RED; level = ERROR; }
if (Style == LogStyle.ERROR) {prefix = "[FLIXEL]"; color = RED; level = ERROR; }
if (Style == LogStyle.NORMAL) {prefix = "[FLIXEL]"; color = WHITE; level = INFO; }
if (Style == LogStyle.NOTICE) {prefix = "[FLIXEL NOTICE]"; color = GREEN; level = WARNING;}
if (Style == LogStyle.WARNING) {prefix = "[FLIXEL WARNING]"; color = YELLOW; level = WARNING;}
if (Style == LogStyle.NOTICE) {prefix = "[FLIXEL]"; color = GREEN; level = WARNING;}
if (Style == LogStyle.WARNING) {prefix = "[FLIXEL]"; color = YELLOW; level = WARNING;}
var d:Dynamic = Data;
if (!(d is Array))
@@ -45,9 +45,9 @@ class Logs {
public static function prepareColoredTrace(text:Array<LogText>, level:Level = INFO) {
var time = Date.now();
var superCoolText = [
logText('[ '),
logText('[ '),
logText('${Std.string(time.getHours()).addZeros(2)}:${Std.string(time.getMinutes()).addZeros(2)}:${Std.string(time.getSeconds()).addZeros(2)}', DARKMAGENTA),
logText(' |'),
logText(' |'),
switch(level) {
case WARNING: logText(' WARNING ', DARKYELLOW);
case ERROR: logText(' ERROR ', DARKRED);
+6 -4
View File
@@ -95,14 +95,16 @@ class Paths
return getPath('music/$key.$SOUND_EXT', MUSIC, library);
}
inline static public function voices(song:String)
inline static public function voices(song:String, difficulty:String = "normal")
{
return getPath('songs/${song.toLowerCase()}/Voices.$SOUND_EXT', MUSIC, null);
var diff = getPath('songs/${song.toLowerCase()}/Voices-$difficulty.$SOUND_EXT', MUSIC, null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/${song.toLowerCase()}/Voices.$SOUND_EXT', MUSIC, null);
}
inline static public function inst(song:String)
inline static public function inst(song:String, difficulty:String = "normal")
{
return getPath('songs/${song.toLowerCase()}/Inst.$SOUND_EXT', MUSIC, null);
var diff = getPath('songs/${song.toLowerCase()}/Inst-$difficulty.$SOUND_EXT', MUSIC, null);
return OpenFlAssets.exists(diff) ? diff : getPath('songs/${song.toLowerCase()}/Inst.$SOUND_EXT', MUSIC, null);
}
inline static public function image(key:String, ?library:String)