Merge branch 'main' into raltyro-audio-rewrite

# Conflicts:
#	source/funkin/backend/utils/CoolUtil.hx
This commit is contained in:
Ralty
2025-07-19 23:36:06 +07:00
10 changed files with 214 additions and 94 deletions
+59 -32
View File
@@ -1,9 +1,11 @@
package funkin.backend.system;
import flixel.util.FlxColor;
import funkin.backend.assets.ModsFolder;
import funkin.backend.assets.IModsAssetLibrary;
import funkin.backend.assets.ScriptedAssetLibrary;
import funkin.backend.system.macros.GitCommitMacro;
import funkin.backend.utils.IniUtil;
import lime.app.Application;
import lime.utils.AssetLibrary as LimeAssetLibrary;
import lime.utils.AssetType;
@@ -13,7 +15,26 @@ import lime.utils.AssetType;
*/
@:build(funkin.backend.system.macros.FlagMacro.build())
class Flags {
// -- Codename's Addon Config --
@:bypass public static var addonFlags:Map<String, Dynamic> = [];
// -- Codename's Mod Config --
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
public static var MOD_API_VERSION:Int = 1;
public static var MOD_DOWNLOAD_LINK:String = "";
public static var MOD_DEPENDENCIES:Array<String> = [];
public static var MOD_ICON64:String = "";
public static var MOD_ICON32:String = "";
public static var MOD_ICON16:String = "";
public static var MOD_ICON:String = "";
public static var MOD_DISCORD_CLIENT_ID:String = "";
public static var MOD_DISCORD_LOGO_KEY:String = "";
public static var MOD_DISCORD_LOGO_TEXT:String = "";
// -- Codename's Default Flags --
public static var CURRENT_API_VERSION:Int = 1;
public static var COMMIT_NUMBER:Int = GitCommitMacro.commitNumber;
public static var COMMIT_HASH:String = GitCommitMacro.commitHash;
public static var COMMIT_MESSAGE:String = 'Commit $COMMIT_NUMBER ($COMMIT_HASH)';
@@ -159,29 +180,9 @@ class Flags {
@:bypass public static var customFlags:Map<String, String> = [];
public static function loadFromData(flags:Map<String, String>, data:String) {
var trimmed:String;
var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed];
var res = IniUtil.parseString(data);
for(line in splitContent) {
if(line.startsWith(";")) continue;
if(line.startsWith("#")) continue;
if(line.startsWith("//")) continue;
if(line.length == 0) continue;
if(line.charAt(0) == "[" && line.charAt(line.length-1) == "]") continue;
var index = line.indexOf("=");
if(index == -1) continue;
var name = line.substr(0, index).trim();
var value = line.substr(index+1).trim();
var wasQuoted = value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code;
if(wasQuoted) value = value.substr(1, value.length - 2);
if((!wasQuoted && value.length == 0) || name.length == 0)
continue;
if(!flags.exists(name))
flags[name] = value;
}
for (section in res) for (key => value in section) flags[key] = value;
}
public static function loadFromDatas(datas:Array<String>) {
@@ -205,16 +206,42 @@ class Flags {
* Loads the flags from the assets.
**/
public static function load(?libs:Array<LimeAssetLibrary> = null) {
if (libs == null)
libs = Paths.assetsTree.libraries;
final flagsPath = Paths.getPath("flags.ini");
var datas:Array<String> = [
for(lib in libs)
if(lib.exists(flagsPath, AssetType.TEXT))
lib.getAsset(flagsPath, AssetType.TEXT)
];
if (libs == null) {
libs = Paths.assetsTree.libraries.copy();
libs.reverse();
}
for(lib in libs) {
var l = lib;
if (l is openfl.utils.AssetLibrary) {
@:privateAccess
l = cast(l, openfl.utils.AssetLibrary).__proxy;
}
var flags:Map<String, String> = loadFromDatas(datas);
parseFlags(flags);
if (l is IModsAssetLibrary) {
var flagsTxt = "";
if (l.exists(Paths.ini("config/modpack"), AssetType.TEXT))
flagsTxt = l.getAsset(Paths.ini("config/modpack"), AssetType.TEXT);
if (cast(l, IModsAssetLibrary).modName == "assets") continue;
if (cast(l, IModsAssetLibrary).modName == ModsFolder.currentModFolder) {
var flags:Map<String, String> = [];
loadFromData(flags, flagsTxt);
parseFlags(flags);
}
else {
var flags:Map<String, String> = [];
loadFromData(flags, flagsTxt);
addonFlags.set(cast(l, IModsAssetLibrary).modName.toLowerCase().replace(" ", "").trim(), flags);
}
}
else {
var flagsTxt = "";
if (l.exists(Paths.getPath("data/config/flags.ini"), AssetType.TEXT))
flagsTxt = l.getAsset(Paths.getPath("data/config/flags.ini"), AssetType.TEXT);
var flags:Map<String, String> = [];
loadFromData(flags, flagsTxt);
parseFlags(flags);
}
}
}
}
+17 -2
View File
@@ -5,8 +5,10 @@ import sys.FileSystem;
#end
import flixel.FlxState;
import funkin.backend.assets.ModsFolder;
import funkin.backend.assets.ModsFolderLibrary;
import funkin.backend.chart.EventsData;
import funkin.backend.system.framerate.Framerate;
import funkin.editors.ModConfigWarning;
import funkin.menus.TitleState;
import haxe.io.Path;
@@ -112,10 +114,23 @@ class MainState extends FlxState {
if (Framerate.isLoaded)
Framerate.instance.reload();
FlxG.switchState(new TitleState());
#if sys
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
#end
if (Options.devMode) {
var lib:ModsFolderLibrary;
for (e in Paths.assetsTree.libraries) {
@:privateAccess if (!(e is openfl.utils.AssetLibrary) || !((lib = cast cast(e, openfl.utils.AssetLibrary).__proxy) is ModsFolderLibrary)) continue;
if (lib.modName == ModsFolder.currentModFolder) {
if (lib.exists(Paths.ini("config/modpack"), lime.utils.AssetType.TEXT)) break;
FlxG.switchState(new ModConfigWarning(lib));
return;
}
}
}
FlxG.switchState(new TitleState());
}
}
+15 -24
View File
@@ -422,33 +422,24 @@ class CoolUtil
FlxG.sound.music.persist = Persist;
FlxG.sound.defaultMusicGroup.add(FlxG.sound.music);
var infoPath = '${Path.withoutExtension(path)}.ini';
if (Assets.exists(infoPath)) {
var musicInfo = IniUtil.parseAsset(infoPath, [
"BPM" => null,
"TimeSignature" => Flags.DEFAULT_BEATS_PER_MEASURE + "/" + Flags.DEFAULT_STEPS_PER_BEAT,
"LoopTime" => '${Flags.DEFAULT_LOOP_TIME}',
"EndTime" => null,
"Offset" => '0'
]);
var iniPath = '${Path.withoutExtension(path)}.ini';
var musicData = Assets.exists(iniPath) ? IniUtil.parseAsset(iniPath)["Global"] : null;
if (musicData != null) {
if (musicData["LoopTime"] != null) FlxG.sound.music.loopTime = Std.parseFloat(musicData["LoopTime"]) * 1000;
if (musicData["EndTime"] != null) FlxG.sound.music.endTime = Std.parseFloat(musicData["EndTime"]) * 1000;
if (musicData["Offset"] != null) FlxG.sound.music.endTime = Std.parseFloat(musicData["Offset"]) * 1000;
FlxG.sound.music.loopTime = Std.parseFloat(musicInfo["LoopTime"]) * 1000;
if (musicInfo["EndTime"] != null) FlxG.sound.music.endTime = Std.parseFloat(musicInfo["EndTime"]) * 1000;
FlxG.sound.music.offset = Std.parseFloat(musicInfo["Offset"]) * 1000;
var timeSignParsed:Array<Null<Float>> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)];
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE;
var stepsPerBeat:Int = Flags.DEFAULT_STEPS_PER_BEAT;
// Check later, i don't think timeSignParsed can contain null, only nan
if (timeSignParsed.length == 2 && !timeSignParsed.contains(null)) {
beatsPerMeasure = timeSignParsed[0] == null || timeSignParsed[0] <= 0 ? Flags.DEFAULT_BEATS_PER_MEASURE : cast timeSignParsed[0];
stepsPerBeat = timeSignParsed[1] == null || timeSignParsed[1] <= 0 ? Flags.DEFAULT_STEPS_PER_BEAT : cast timeSignParsed[1];
var timeSignParsed:Array<Null<Float>> = musicData["TimeSignature"] == null ? [] : [for(s in musicData["TimeSignature"].split("/")) Std.parseFloat(s)];
var beatsPerMeasure:Float = Flags.DEFAULT_BEATS_PER_MEASURE, stepsPerBeat:Float = Flags.DEFAULT_STEPS_PER_BEAT;
if (timeSignParsed.length == 2) {
beatsPerMeasure = Math.isNaN(timeSignParsed[0]) ? Flags.DEFAULT_BEATS_PER_MEASURE : timeSignParsed[0];
stepsPerBeat = Math.isNaN(timeSignParsed[1]) ? Flags.DEFAULT_STEPS_PER_BEAT : (16 / timeSignParsed[1]); // from denominator
}
var bpm:Null<Float> = Std.parseFloat(musicInfo["BPM"]).getDefault(DefaultBPM);
Conductor.changeBPM(bpm, beatsPerMeasure, stepsPerBeat);
} else
var bpm:Float = Std.parseFloat(musicData["BPM"]).getDefault(DefaultBPM);
Conductor.changeBPM(bpm, beatsPerMeasure, Math.floor(stepsPerBeat));
}
else
Conductor.changeBPM(DefaultBPM);
FlxG.sound.music.play();
@@ -89,6 +89,10 @@ class DiscordUtil
config.logoKey = config.logoKey.getDefault(Flags.DEFAULT_DISCORD_LOGO_KEY);
config.logoText = config.logoText.getDefault(Flags.DEFAULT_DISCORD_LOGO_TEXT);
config.clientID = config.clientID.getDefault(Flags.DEFAULT_DISCORD_CLIENT_ID);
if (Flags.MOD_DISCORD_CLIENT_ID.length > 0) config.clientID = Flags.MOD_DISCORD_CLIENT_ID;
if (Flags.MOD_DISCORD_LOGO_KEY.length > 0) config.logoKey = Flags.MOD_DISCORD_LOGO_KEY;
if (Flags.MOD_DISCORD_LOGO_TEXT.length > 0) config.logoText = Flags.MOD_DISCORD_LOGO_TEXT;
currentID = config.clientID;
#end
}
+24 -30
View File
@@ -1,39 +1,33 @@
package funkin.backend.utils;
/**
* DOESN'T SUPPORT CATEGORIES YET!!
*/
class IniUtil {
public static inline function parseAsset(assetPath:String, ?defaultVariables:Map<String, String>)
return parseString(Assets.getText(assetPath), defaultVariables);
public static inline function parseAsset(assetPath:String)
return parseString(Assets.getText(assetPath));
public static function parseString(data:String, ?defaultVariables:Map<String, String>) {
var trimmed:String;
var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed];
public static function parseString(data:String):Map<String, Map<String, String>> {
var res:Map<String, Map<String, String>> = [];
parseStringToMap(res, data);
return res;
}
var finalMap:Map<String, String> = [];
if (defaultVariables != null)
for(k=>e in defaultVariables)
finalMap[k] = e;
public static function parseStringToMap(map:Map<String, Map<String, String>>, data:String) {
var section:Map<String, String> = null;
for(line in splitContent) {
// comment
if (line.startsWith(";")) continue;
// categories; not supported yet
if (line.startsWith("[") && line.endsWith("]")) continue;
var regexSec = ~/\\[(.+)\\]/g, regexVal = ~/(.+)=(.+)/g, quote = ~/[\\'"](.+)[\\'"]/g, iln = 0;
do {
var line = data.substring(iln, ((iln = data.indexOf('\n', iln) + 1) == 0 ? data.length : iln - 1));
if (regexSec.match(line)) {
if ((section = map.get(regexSec.matched(1))) == null) map.set(regexSec.matched(1), section = []);
}
else if (regexVal.match(line)) {
var s = regexVal.matched(2).trim();
if (quote.match(s)) s = quote.matched(1);
var index = line.indexOf("=");
var name = line.substr(0, index).trim();
var value = line.substr(index+1).trim();
if (value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code)
value = value.substr(1, value.length - 2);
if (value.length == 0 || name.length == 0)
continue;
finalMap[name] = value;
}
return finalMap;
if (section == null) {
if ((section = map.get("Global")) == null) map.set("Global", section = []);
}
section.set(regexVal.matched(1).trim(), s);
}
} while (iln != 0);
}
}
+75
View File
@@ -0,0 +1,75 @@
package funkin.editors;
import funkin.backend.assets.ModsFolderLibrary;
import flixel.FlxState;
class ModConfigWarning extends UIState {
public static var hadPopup:Bool = false;
var library:ModsFolderLibrary = null;
var goToState:Class<FlxState>;
public static inline var defaultModConfigText =
'[Common]
MOD_NAME="YOUR MOD NAME HERE"
MOD_DESCRIPTION="YOUR MOD DESCRIPTION HERE"
# DO NOT EDIT!! this is used to check for version compatibility!
MOD_API_VERSION=1
MOD_DOWNLOAD_LINK="YOUR MOD PAGE LINK HERE"
# Not supported yet
;MOD_ICON64="path/to/icon64.png"
;MOD_ICON32="path/to/icon32.png"
;MOD_ICON16="path/to/icon16.png"
;MOD_ICON="path/to/icon.ico"
[Flags]
DISABLE_BETA_WARNING_SCREEN=true
[Discord]
MOD_DISCORD_CLIENT_ID=""
MOD_DISCORD_LOGO_KEY=""
MOD_DISCORD_LOGO_TEXT=""';
public function new(library:ModsFolderLibrary, ?goToState:Class<FlxState>) {
super();
this.library = library;
this.goToState = goToState != null ? goToState : funkin.menus.TitleState;
}
override function createPost() {
super.createPost();
hadPopup = true;
var substate = new UIWarningSubstate("Missing mod folder configuration!", "Your mod is currently missing a mod config file!\n\n\nWould you like to automatically generate one?\n\n(PS: If this is not your mod please disable Developer mode to stop this popup from appearing.)", [
{
label: "Not Now",
color: 0x969533,
onClick: function (_) {
MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
{
label: "Yes",
onClick: function(_) {
var path = '${library.folderPath}/data/config/modpack.ini';
CoolUtil.safeSaveFile(path, defaultModConfigText);
openSubState(new UIWarningSubstate("Created mod config!", "Your mod config file has been created at " + path + "!", [
{
label: "Ok",
onClick: function (_) {
MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
], false));
}
}
], false);
substate.bHeight = 300;
openSubState(substate);
}
}
@@ -361,8 +361,19 @@ class CharacterEditor extends UIState {
if (charXML.exists("flipX") && !character.flipX) charXML.remove("flipX");
if (charXML.exists("scale") && character.scale.x == 1) charXML.remove("scale");
if (charXML.exists("antialiasing") && character.antialiasing) charXML.remove("antialiasing");
if (charXML.exists("sprite") && character.sprite == character.curCharacter) charXML.remove("sprite");
if (charXML.exists("icon") && character.icon == character.curCharacter) charXML.remove("icon");
return "<!DOCTYPE codename-engine-character>\n" + Printer.print(charXML, true);
for (a in charXML.elements())
if (a.nodeName == "anim") {
if (a.exists("x") && a.get("x") == "0") a.remove("x");
if (a.exists("y") && a.get("y") == "0") a.remove("y");
if (a.exists("fps") && a.get("fps") == "24") a.remove("fps");
if (a.exists("loop") && a.get("loop") == "false") a.remove("loop");
}
var xmlThingYea:String = "<!DOCTYPE codename-engine-character>\n" + Printer.print(charXML, Options.editorPrettyPrint);
return Options.editorPrettyPrint ? xmlThingYea : xmlThingYea.replace("\n", "");
}
function _edit_undo(_) {
@@ -654,4 +665,4 @@ enum CharacterChange {
CDeleteAnim(animID:Int, animData:AnimData);
CChangeOffset(name:String, change:FlxPoint);
CResetOffsets(oldOffsets:Map<String, FlxPoint>);
}
}
@@ -32,6 +32,8 @@ class UIWarningSubstate extends MusicBeatSubstate {
var warnCam:FlxCamera;
public var bHeight:Int = 232;
public override function onSubstateOpen() {
super.onSubstateOpen();
parent.persistentUpdate = false;
@@ -69,7 +71,7 @@ class UIWarningSubstate extends MusicBeatSubstate {
warnCam.zoom = 0.1;
FlxG.cameras.add(warnCam, false);
var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), 232, 'editors/ui/${isError ? "normal" : "grayscale"}-popup');
var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), bHeight, 'editors/ui/${isError ? "normal" : "grayscale"}-popup');
spr.x = (FlxG.width - spr.bWidth) / 2;
spr.y = (FlxG.height - spr.bHeight) / 2;
spr.color = isError ? 0xFFFF0000 : 0xFFFFFF00;
+1
View File
@@ -29,6 +29,7 @@ class MainMenuState extends MusicBeatState
override function create()
{
super.create();
DiscordUtil.call("onMenuLoaded", ["Main Menu"]);
+3 -3
View File
@@ -128,9 +128,9 @@ class Options
// Misc
public static var P2_RESET:Array<FlxKey> = [];
public static var P2_SWITCHMOD:Array<FlxKey> = [];
public static var P2_VOLUME_UP:Array<FlxKey> = [NUMPADPLUS];
public static var P2_VOLUME_DOWN:Array<FlxKey> = [NUMPADMINUS];
public static var P2_VOLUME_MUTE:Array<FlxKey> = [NUMPADZERO];
public static var P2_VOLUME_UP:Array<FlxKey> = [PLUS];
public static var P2_VOLUME_DOWN:Array<FlxKey> = [MINUS];
public static var P2_VOLUME_MUTE:Array<FlxKey> = [ZERO];
// Debugs
public static var P2_DEV_ACCESS:Array<FlxKey> = [];