Made the code work + Cleaned up Code

This commit is contained in:
NeeEoo
2024-04-10 18:57:18 +02:00
parent d955e00ece
commit 244fddc3c7
13 changed files with 240 additions and 117 deletions
@@ -83,7 +83,8 @@ class AssetsLibraryList extends AssetLibrary {
if (l is IModsAssetLibrary) {
var lib = cast(l, IModsAssetLibrary);
for(e in lib.getFolders(folder))
content.push(e);
if(!content.contains(e))
content.push(e);
}
#end
}
+1 -1
View File
@@ -62,7 +62,7 @@ class ModsFolder {
Options.lastLoadedMod = currentModFolder = mod;
reloadMods();
Logs.traceColored([
Logs.logText('Switched to mod ', GRAY),
Logs.logText('Switched to mod: '),
Logs.logText(mod, GREEN)
], VERBOSE);
}
-8
View File
@@ -23,8 +23,6 @@ class Paths
*/
public static inline final SOUND_EXT = #if web "mp3" #else "ogg" #end;
public static inline var translFolderName:String = "translations";
public static var assetsTree:AssetsLibraryList;
public static var tempFramesCache:Map<String, FlxFramesCollection> = [];
@@ -38,12 +36,6 @@ class Paths
public static inline function getPath(file:String, ?library:String)
return library != null ? '$library:assets/$library/$file' : 'assets/$file';
public static inline function translationsMain(key:String)
return getPath('$translFolderName/$key');
public static inline function translations(key:String)
return translationsMain('${TranslationUtil.curLanguage}/$key');
public static inline function video(key:String, ?ext:String = "mp4")
return getPath('videos/$key.$ext');
@@ -10,14 +10,14 @@ class TranslatedAssetLibrary extends AssetLibrary implements IModsAssetLibrary {
public var libName:String;
public var modName:String;
public var basePath:String;
public var prefix:String = Paths.translFolderName + "/";
public var prefix:String = TranslationUtil.LANG_FOLDER + "/";
public var langFolder(get, set):String;
@:noCompletion private inline function get_langFolder():String {
return libName;
}
@:noCompletion private inline function set_langFolder(value:String):String {
basePath = prefix + (libName = modName = (value != null ? value : TranslationUtil.DEFAULT_LANGUAGE.split("/")[0])) + "/";
basePath = prefix + (libName = modName = (value != null ? value : TranslationUtil.DEFAULT_LANGUAGE)) + "/";
return libName;
}
@@ -1,27 +1,15 @@
package funkin.backend.assets;
import lime.utils.Log;
import lime.utils.AssetManifest;
import haxe.io.Path;
import lime.app.Event;
import lime.app.Future;
import lime.app.Promise;
import lime.media.AudioBuffer;
import lime.graphics.Image;
import lime.text.Font;
import lime.utils.AssetType;
import lime.utils.Bytes;
import lime.utils.Assets as LimeAssets;
import openfl.text.Font as OpenFLFont;
import openfl.utils.AssetLibrary;
#if MOD_SUPPORT
import sys.FileStat;
import sys.FileSystem;
import sys.io.File;
import haxe.zip.Reader;
import funkin.backend.utils.SysZip;
import funkin.backend.utils.SysZip.SysZipEntry;
+14 -11
View File
@@ -98,23 +98,26 @@ class Logs {
public static function traceColored(text:Array<LogText>, level:Level = INFO) {
__showInConsole(prepareColoredTrace(text, level));
}
public static function trace(text:String, level:Level = INFO, color:ConsoleColor = LIGHTGRAY) {
traceColored([{
text: text,
color: color
}], level);
public static function trace(text:String, level:Level = INFO, color:ConsoleColor = LIGHTGRAY, ?prefix:String) {
var text = [logText(text, color)];
if(prefix != null) text.insert(0, getPrefix(prefix));
traceColored(text, level);
}
public inline static function verbose(text:String, level:Level = VERBOSE, color:ConsoleColor = LIGHTGRAY) {
Logs.trace(text, level, color);
public inline static function getPrefix(prefix:String) {
return logText('[${prefix}] ', BLUE);
}
public inline static function warn(text:String, level:Level = WARNING, color:ConsoleColor = YELLOW) {
Logs.trace(text, level, color);
public inline static function verbose(text:String, level:Level = VERBOSE, color:ConsoleColor = LIGHTGRAY, ?prefix:String) {
Logs.trace(text, level, color, prefix);
}
public inline static function error(text:String, level:Level = ERROR, color:ConsoleColor = RED) {
Logs.trace(text, level, color);
public inline static function warn(text:String, level:Level = WARNING, color:ConsoleColor = YELLOW, ?prefix:String) {
Logs.trace(text, level, color, prefix);
}
public inline static function error(text:String, level:Level = ERROR, color:ConsoleColor = RED, ?prefix:String) {
Logs.trace(text, level, color, prefix);
}
}
@@ -55,6 +55,7 @@ class MainState extends FlxState {
Main.refreshAssets();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder);
TranslationUtil.findAllLanguages();
TranslationUtil.setLanguage();
DiscordUtil.init();
EventsData.reloadEvents();
+23 -1
View File
@@ -140,7 +140,16 @@ class CoolUtil
}
/**
* Returns the last of an Array
* Returns the first element of an Array
* @param array Array
* @return T Last element
*/
public static inline function first<T>(array:Array<T>):T {
return array[0];
}
/**
* Returns the last element of an Array
* @param array Array
* @return T Last element
*/
@@ -703,6 +712,19 @@ class CoolUtil
@:noUsing public static inline function flxeaseFromString(mainEase:String, suffix:String)
return Reflect.field(FlxEase, mainEase + (mainEase == "linear" ? "" : suffix));
public static function sortAlphabetically(array:Array<String>, ?lowercase:Bool=false) {
array.sort(function(a1, a2):Int {
if(lowercase) {
a1 = a1.toLowerCase();
a2 = a2.toLowerCase();
}
if (a1 < a2) return -1;
if (a1 > a2) return 1;
return 0;
});
return array;
}
}
/**
+6 -6
View File
@@ -285,7 +285,7 @@ class DiscordUtil
user = DUser.initRaw(request);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.getPrefix("Discord"),
Logs.logText("Connected to User " + user.globalName + " ("),
Logs.logText(user.handle, GRAY),
Logs.logText(")")
@@ -301,7 +301,7 @@ class DiscordUtil
var finalMsg:String = cast(message, String);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.getPrefix("Discord"),
Logs.logText("Disconnected ("),
Logs.logText('$errorCode: $finalMsg', RED),
Logs.logText(")")
@@ -315,7 +315,7 @@ class DiscordUtil
var finalMsg:String = cast(message, String);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.getPrefix("Discord"),
Logs.logText('Error ($errorCode: $finalMsg)', RED)
], ERROR);
@@ -324,7 +324,7 @@ class DiscordUtil
private static function onJoin(joinSecret:cpp.ConstCharStar):Void
{
Logs.traceColored([Logs.logText("[Discord] ", BLUE), Logs.logText("Someone has just joined", GREEN)], INFO);
Logs.traceColored([Logs.getPrefix("Discord"), Logs.logText("Someone has just joined", GREEN)], INFO);
call("onJoinGame", [cast(joinSecret, String)]);
}
@@ -332,7 +332,7 @@ class DiscordUtil
private static function onSpectate(spectateSecret:cpp.ConstCharStar):Void
{
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.getPrefix("Discord"),
Logs.logText("Someone started spectating your game", YELLOW)
], INFO);
@@ -342,7 +342,7 @@ class DiscordUtil
private static function onJoinReq(request:cpp.RawConstPointer<DiscordUser>):Void
{
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.getPrefix("Discord"),
Logs.logText("Someone has just requested to join", YELLOW)
], INFO);
+7 -5
View File
@@ -1,18 +1,20 @@
package funkin.backend.utils;
typedef IniMap = Map<String, String>;
/**
* DOESNT SUPPORT CATEGORIES YET!!
* Parses an ini file and returns a map of its contents.
* WARNING: This is not a full ini parser, it only supports the basics. So no sections
*/
class IniUtil {
public static inline function parseAsset(assetPath:String, ?defaultVariables:Map<String, String>)
public static inline function parseAsset(assetPath:String, ?defaultVariables:IniMap):IniMap
return parseString(Assets.getText(assetPath), defaultVariables);
public static function parseString(data:String, ?defaultVariables:Map<String, String>) {
public static function parseString(data:String, ?defaultVariables:IniMap):IniMap {
var trimmed:String;
var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed];
var finalMap:Map<String, String> = [];
var finalMap:IniMap = [];
if (defaultVariables != null)
for(k=>e in defaultVariables)
finalMap[k] = e;
@@ -20,7 +22,7 @@ class IniUtil {
for(line in splitContent) {
// comment
if (line.startsWith(";")) continue;
// categories; not supported yet
// sections; not supported yet
if (line.startsWith("[") && line.endsWith("]")) continue;
var index = line.indexOf("=");
@@ -10,7 +10,7 @@ class SortedArrayUtil {
* @param getVal Function that returns the position value of T
* @return Index
*/
public static inline function binarySearch<T>(array:Array<T>, val:Float, getVal:T->Float):Int {
public static function binarySearch<T>(array:Array<T>, val:Float, getVal:T->Float):Int {
if (array.length <= 0) return 0; // if the array is empty, it should be equal to zero (the beginning)
if (getVal(array[0]) > val) return 0; // in case its the minimum
if (getVal(array[array.length-1]) < val) return array.length; // in case its the maximum
@@ -46,4 +46,10 @@ class SortedArrayUtil {
if (val != null)
array.insert(binarySearch(array, getVal(val), getVal), val);
}
public static function removeSorted<T>(array:Array<T>, val:T, getVal:T->Float) {
var index = binarySearch(array, getVal(val), getVal);
if (index != -1)
array.splice(index, 1);
}
}
+173 -64
View File
@@ -1,10 +1,8 @@
package funkin.backend.utils;
import funkin.backend.utils.IniUtil;
import funkin.backend.assets.TranslatedAssetLibrary;
import funkin.backend.assets.ModsFolderLibrary;
import funkin.backend.assets.ModsFolder;
import funkin.backend.assets.IModsAssetLibrary;
import funkin.backend.assets.ZipFolderLibrary;
import openfl.utils.Assets;
import haxe.io.Path;
import haxe.xml.Access;
@@ -15,6 +13,7 @@ import haxe.Exception;
*
* Made by @NexIsDumb originally for the Poldhub mod.
*/
@:allow(funkin.backend.assets.TranslatedAssetLibrary)
class TranslationUtil
{
/**
@@ -22,42 +21,51 @@ class TranslationUtil
*
* It'll never be `null`.
*/
public static var transMap(default, set):Map<String, IFormatInfo> = [];
@:noCompletion private static function set_transMap(value:Map<String, IFormatInfo>):Map<String, IFormatInfo> {
if (value == null) value = [];
return transMap = value;
}
public static var stringMap(default, set):Map<String, IFormatInfo> = [];
/**
* The default language used inside of the source code.
*/
public static var DEFAULT_LANGUAGE:String = 'en/English'; // no inline so psycopathic mods can edit it - Nex
public static var DEFAULT_LANGUAGE:String = 'en'; // no inline so psychopathic mods can edit it - Nex
/**
* Returns the current language config.
*/
public static var config:IniMap = [];
/**
* Returns the current language.
*/
public static var curLanguage(get, set):String;
@:noCompletion private static function get_curLanguage():String {
return Options.language;
}
@:noCompletion private static function set_curLanguage(value:String):String {
return Options.language = value;
}
/**
* Returns the current language name.
*/
public static var curLanguageName(get, set):String;
/**
* Returns if the current language is the default one (`DEFAULT_LANGUAGE`).
*/
public static var isDefaultLanguage(get, never):Bool;
@:noCompletion private static function get_isDefaultLanguage():Bool
return Options.language == DEFAULT_LANGUAGE;
/**
* Returns if any translation are loaded.
*/
public static var isAnyTransLoaded(get, never):Bool;
@:noCompletion private static function get_isAnyTransLoaded():Bool
return Lambda.count(transMap) > 0;
public static var isLanguageLoaded(get, never):Bool;
/**
* Returns an array has a list of the languages that were found.
*/
public static var foundLanguages:Array<String> = [];
// Private
private static inline var LANG_FOLDER:String = "languages";
private static var langConfigs:Map<String, IniMap> = [];
private static var nameMap:Map<String, String> = [];
private static inline function getDefaultNameMap():Map<String, String> {
return ['en' => 'English'];
}
private static inline function getDefaultConfig(name:String):IniMap {
return ["name" => getLanguageName(name), "credits" => "", "version" => "1.0.0"];
}
/**
* Updates the language.
@@ -69,11 +77,19 @@ class TranslationUtil
public static function setLanguage(?name:String) {
#if TRANSLATIONS_SUPPORT
if(name == null) name = curLanguage;
name = name.split("/")[0];
transMap = loadLanguage(name);
for(mod in ModsFolder.getLoadedModsLibs()) if(mod is TranslatedAssetLibrary)
cast(mod, TranslatedAssetLibrary).langFolder = name.split("/")[0];
config = getConfig(name);
stringMap = loadLanguage(name);
Logs.traceColored([
Logs.getPrefix("Language"),
Logs.logText("Set Language To: "),
Logs.logText('${getLanguageName(name)} ($name)', GREEN)
], VERBOSE);
for(mod in ModsFolder.getLoadedModsLibs(false))
if(mod is TranslatedAssetLibrary)
cast(mod, TranslatedAssetLibrary).langFolder = name;
#end
}
@@ -86,7 +102,7 @@ class TranslationUtil
{
#if TRANSLATIONS_SUPPORT
if (id == null) id = defString;
if (transMap.exists(id)) return transMap.get(id).format(params);
if (stringMap.exists(id)) return stringMap.get(id).format(params);
#end
return FormatUtil.get(defString).format(params);
}
@@ -94,64 +110,149 @@ class TranslationUtil
/**
* Returns an array that specifies which languages were found.
*/
public static function getLanguages():Array<String>
public static function findAllLanguages()
{
var languages:Array<String> = [];
#if TRANSLATIONS_SUPPORT
var main:String = Paths.translationsMain('');
for (l in Paths.assetsTree.getFolders(main)) {
var path:String = main + l + "/config.ini";
if(Assets.exists(path)) languages.push('$l/' + IniUtil.parseAsset(path, ["name" => l])["name"]);
else for(f in Paths.assetsTree.getFiles(main + l)) if(Path.extension(path = '$l/$f') == "xml") {
languages.push(Path.withoutExtension(path));
break; // only gets the first xml's name if theres no config.ini! - Nex
foundLanguages = [];
nameMap = getDefaultNameMap();
var mainPath:String = translationsMain("");
var langName:String = null;
for (lang in Paths.assetsTree.getFolders("assets/" + mainPath)) {
var path:String = Path.join([mainPath, lang, "config.ini"]);
var config = getDefaultConfig(lang);
if(Assets.exists(path)) {
config = IniUtil.parseAsset(path, config);
langName = config["name"];
} else { // if there was no config.ini, use the file name as the language name
for(file in Paths.getFolderContent(mainPath + lang).sortAlphabetically()) {
if(Path.extension(path = '$lang/$file') == "xml") {
config["name"] = (langName = Path.withoutExtension(file));
break;
}
}
}
nameMap.set(lang, langName);
langConfigs.set(lang, config);
foundLanguages.push('$lang/$langName');
}
// Ensure that the default language is always first
var englishName = TranslationUtil.DEFAULT_LANGUAGE + "/" + getLanguageName(TranslationUtil.DEFAULT_LANGUAGE);
if (foundLanguages.contains(englishName))
foundLanguages.remove(englishName);
foundLanguages.insert(0, englishName);
#end
return languages;
}
/**
* Returns a map of translations based on its XML.
*/
public static function loadLanguage(folderName:String):Map<String, IFormatInfo>
public static function loadLanguage(lang:String):Map<String, IFormatInfo>
{
#if TRANSLATIONS_SUPPORT
var mainPath:String = Paths.translationsMain(folderName);
FormatUtil.clear(); // Clean up the format cache
var mainPath:String = translationsMain(lang);
var leMap:Map<String, IFormatInfo> = [];
for(file in Paths.getFolderContent(mainPath)) if(Path.extension(file) == "xml") {
var xml:Access = null;
try xml = new Access(Xml.parse(Assets.getText('$mainPath/$file')))
catch(e) {
Logs.error('Error while parsing $file: ${Std.string(e)}');
return [];
}
if (xml == null) return [];
if (!xml.hasNode.translations) {
Logs.warn("A translation xml file requires a translations root element.");
return [];
}
FormatUtil.clear(); // Clean up the format cache
for(node in xml.node.translations.elements) {
switch(node.name) {
case "trans":
if (!node.has.id) {
Logs.warn("A translation node requires an ID attribute.");
continue;
}
var string:String = node.has.string ? node.att.string : node.x.nodeValue;
string = string.replace("\\n", "\n").replace("\r", ""); // remove stupid windows line breaks and convert newline literals to newlines
leMap.set(node.att.id, FormatUtil.get(string));
}
var translations = [];
function parseXml(xml:Access) {
for(node in xml.elements) {
if (node.name == "group") // Cosmetic name
parseXml(node);
else if(node.name == "text" || node.name == "trans" || node.name == "lang" || node.name == "string")
translations.push(node);
}
}
for(file in Paths.getFolderContent(mainPath, true).sortAlphabetically()) {
if(Path.extension(file) != "xml") continue;
// Parse the XML
var xml:Access = null;
try xml = new Access(Xml.parse(Assets.getText(file)))
catch(e) Logs.error('Error while parsing $file: ${Std.string(e)}', "Language");
if (xml == null) continue;
if (!xml.hasNode.language) {
Logs.warn('File $file requires a <language> root element.', "Language");
continue;
}
var langNode = xml.node.language;
//if (langNode.has.name) {
// getConfig(lang).set("name", langNode.att.name);
//}
parseXml(langNode);
}
for(node in translations) {
if (!node.has.id) {
Logs.warn('A <${node.name}> node requires an ID attribute.', "Language");
continue;
}
var value:String = node.has.string ? node.att.string : node.innerData;
value = value.replace("\\n", "\n").replace("\r", ""); // remove stupid windows line breaks and convert newline literals to newlines
leMap.set(node.att.id, FormatUtil.get(value));
}
return leMap;
#else
return [];
#end
}
// Utils
public static function getLanguageName(lang:String) {
return nameMap.exists(lang) ? nameMap.get(lang) : lang;
}
public static function getLanguageFromName(name:String) {
var reverseMap = new Map<String, String>();
for(key => val in nameMap) reverseMap.set(val, key);
return reverseMap.exists(name) ? reverseMap.get(name) : name;
}
public static function getConfig(lang:String):IniMap {
return langConfigs.exists(lang) ? langConfigs.get(lang) : getDefaultConfig(lang);
}
public static inline function translationsMain(key:String)
return '$LANG_FOLDER/$key';
public static inline function translations(key:String)
return translationsMain('$curLanguage/$key');
// getters & setters
@:noCompletion private static function set_stringMap(value:Map<String, IFormatInfo>):Map<String, IFormatInfo> {
if (value == null) value = [];
return stringMap = value;
}
@:noCompletion private static function get_curLanguage():String {
return Options.language;
}
@:noCompletion private static function set_curLanguage(value:String):String {
return Options.language = value;
}
@:noCompletion private static function get_curLanguageName():String {
return getLanguageName(Options.language);
}
@:noCompletion private static function set_curLanguageName(value:String):String {
return Options.language = getLanguageFromName(value);
}
@:noCompletion private static function get_isDefaultLanguage():Bool
return Options.language == DEFAULT_LANGUAGE;
@:noCompletion private static function get_isLanguageLoaded():Bool
return Lambda.count(stringMap) > 0;
}
/**
@@ -187,6 +288,10 @@ class StrFormatInfo implements IFormatInfo {
public function format(params:Array<Dynamic>):String {
return string;
}
public function toString():String {
return "StrFormatInfo(" + string + ")";
}
}
// TODO: add support for @:({0}==1?(Hello):(World))
@@ -235,6 +340,10 @@ class ParamFormatInfo implements IFormatInfo {
return str;
}
public function toString():String {
return 'ParamFormatInfo([${strings.join(", ")}] [${indexes.join(", ")}])';
}
}
interface IFormatInfo {
@@ -6,15 +6,14 @@ class MiscOptions extends OptionsScreen {
super("Miscellaneous", "Use this menu to reset save data or engine settings.");
{ // Language Option
var lanArray:Array<String> = TranslationUtil.getLanguages();
if (lanArray.contains(TranslationUtil.DEFAULT_LANGUAGE))
lanArray.remove(TranslationUtil.DEFAULT_LANGUAGE);
lanArray.insert(0, TranslationUtil.DEFAULT_LANGUAGE);
var lanArray:Array<String> = TranslationUtil.foundLanguages;
// TODO: add credits based on the config file
add(new ArrayOption(
"Language",
'The language that the engine currently uses (the default one is ${TranslationUtil.DEFAULT_LANGUAGE}).',
lanArray, [for(lan in lanArray) lan.split("/").last()], "language",
[for(lan in lanArray) lan.split("/").first()],
[for(lan in lanArray) lan.split("/").last()], "language",
function(path:String) {
TranslationUtil.setLanguage(path);
parent.remove(this);