From f4b90d01cf04b80622c51652fb33951dddd4f9ba Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 14:10:14 +0200 Subject: [PATCH 01/26] Add Config --- src/Managers/Config.cs | 120 +++++++++++++++++++++++++++++++++++++++++ src/PolyScriptMod.cs | 23 ++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/Managers/Config.cs create mode 100644 src/PolyScriptMod.cs diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs new file mode 100644 index 0000000..f213c11 --- /dev/null +++ b/src/Managers/Config.cs @@ -0,0 +1,120 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PolyMod.Managers; + +/// +/// allows mods to save config. +/// +public class Config where T : class +{ + private T? _currentConfig; + private readonly string _modName; + private readonly ConfigTypes _configType; + private static readonly string ExposedConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); + private readonly string _perModConfigPath; + private T? _defaultConfig; + public Config(string modName, ConfigTypes configType) + { + _modName = modName; + _configType = configType; + _perModConfigPath = Path.Combine(Plugin.MODS_PATH, $"{modName}.json"); + Load(); + } + + internal void Load() // can be called internally if config changes; gui config not implemented yet + { + switch (_configType) + { + case ConfigTypes.PerMod: + { + if (!File.Exists(_perModConfigPath)) + { + return; + } + var jsonText = File.ReadAllText(_perModConfigPath); + _currentConfig = JsonConvert.DeserializeObject(jsonText); + break; + } + case ConfigTypes.Exposed: + { + if (!File.Exists(ExposedConfigPath)) + { + return; + } + var jsonText = File.ReadAllText(ExposedConfigPath); + _currentConfig = JObject.Parse(jsonText)[_modName].ToObject(); + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + /// + /// Sets the default if the config does not exist yet. Always call this before reading from the config. + /// + public void SetDefaultConfig(T defaultValue) + { + _defaultConfig = defaultValue; + if (_currentConfig is not null) return; + Write(_defaultConfig); + SaveChanges(); + } + + /// + /// Writes the **entire** config. Usage not recommended, use Edit() instead + /// + public void Write(T config) + { + _currentConfig = config; + } + /// + /// Gets the config. Should only be called after setting a default. + /// + public T Get() + { + return _currentConfig ?? throw new InvalidOperationException("Must set default before reading config."); + } + /// + /// edits the config. Should only be called after setting a default. + /// + /// Call SaveChanges after editing + public void Edit(Action editor) + { + editor(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); + } + /// + /// Gets part of the config. Should only be called after setting a default + /// + public TResult Get(Func getter) + { + return getter(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); + } + /// + /// writes the config to disk + /// + public void SaveChanges() + { + switch (_configType) + { + case ConfigTypes.PerMod: + var json = JsonConvert.SerializeObject(_currentConfig, Formatting.Indented); + File.WriteAllText(_perModConfigPath, json); + break; + case ConfigTypes.Exposed: + var modsConfigText = File.ReadAllText(ExposedConfigPath); + var modsConfigJson = JObject.Parse(modsConfigText); + modsConfigJson[_modName] = JToken.FromObject(_currentConfig); + File.WriteAllText(ExposedConfigPath, modsConfigJson.ToString(Formatting.Indented)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public enum ConfigTypes + { + PerMod, + Exposed + } +} \ No newline at end of file diff --git a/src/PolyScriptMod.cs b/src/PolyScriptMod.cs new file mode 100644 index 0000000..7c1782d --- /dev/null +++ b/src/PolyScriptMod.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json.Linq; +using PolyMod.Managers; + +namespace PolyMod; + +public abstract class PolyScriptMod where TConfig : class where TExposedConfig : class +{ + internal void Initialize(string name) + { + ModName = name; + Config = new Config(name, Config.ConfigTypes.PerMod); + ExposedConfig = new Config(name, Config.ConfigTypes.Exposed); + } + public string ModName { get; private set; } + protected Config Config { get; private set; } = null!; + protected Config ExposedConfig { get; private set; } = null!; + protected virtual JObject DefaultConfig => new JObject(); + public abstract void OnLoad(); +} + +public abstract class PolyScriptMod : PolyScriptMod +{ +} \ No newline at end of file From 7c15e0463601e6844915481ee56d48164a659a9e Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 15:04:05 +0200 Subject: [PATCH 02/26] Actually load the mod --- src/Loader.cs | 17 ++++++++++++++++- src/PolyScriptMod.cs | 14 +++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index bdf79a2..05c5787 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -195,7 +195,7 @@ internal static void LoadMods(Dictionary mods) files.Add(new(entry.FullName, entry.ReadBytes())); } } - + #region ValidateManifest() if (manifest == null) { Plugin.logger.LogError($"Mod manifest not found in {modContainer}"); @@ -226,6 +226,7 @@ internal static void LoadMods(Dictionary mods) Plugin.logger.LogError($"Mod {manifest.id} already exists"); continue; } + #endregion mods.Add(manifest.id, new( manifest, Mod.Status.Success, @@ -234,6 +235,11 @@ internal static void LoadMods(Dictionary mods) Plugin.logger.LogInfo($"Registered mod {manifest.id}"); } + CheckDependencies(mods); + } + + private static void CheckDependencies(Dictionary mods) + { foreach (var (id, mod) in mods) { foreach (var dependency in mod.dependencies ?? Array.Empty()) @@ -335,6 +341,15 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file) try { Assembly assembly = Assembly.Load(file.bytes); + if (assembly + .GetTypes() + .FirstOrDefault(t => t.IsSubclassOf(typeof(PolyScriptModBase))) + is { } modType) + { + var modInstance = (PolyScriptModBase) Activator.CreateInstance(modType)!; + modInstance.Initialize(mod.id); + modInstance.Load(); + } foreach (Type type in assembly.GetTypes()) { MethodInfo? loadWithLogger = type.GetMethod("Load", new Type[] { typeof(ManualLogSource) }); diff --git a/src/PolyScriptMod.cs b/src/PolyScriptMod.cs index 7c1782d..fe9ba3f 100644 --- a/src/PolyScriptMod.cs +++ b/src/PolyScriptMod.cs @@ -3,9 +3,18 @@ namespace PolyMod; -public abstract class PolyScriptMod where TConfig : class where TExposedConfig : class +public abstract class PolyScriptModBase { - internal void Initialize(string name) + internal abstract void Initialize(string name); + public abstract void Load(); + public abstract void UnLoad(); + internal PolyScriptModBase() + { + } +} +public abstract class PolyScriptMod : PolyScriptModBase where TConfig : class where TExposedConfig : class +{ + internal override void Initialize(string name) { ModName = name; Config = new Config(name, Config.ConfigTypes.PerMod); @@ -15,7 +24,6 @@ internal void Initialize(string name) protected Config Config { get; private set; } = null!; protected Config ExposedConfig { get; private set; } = null!; protected virtual JObject DefaultConfig => new JObject(); - public abstract void OnLoad(); } public abstract class PolyScriptMod : PolyScriptMod From 6ebddb17787b38dfacb88fc1f2ce24388f5198ba Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 16:07:26 +0200 Subject: [PATCH 03/26] Slight refactor and fix of warning --- resources/localization.json | 2 +- src/Loader.cs | 34 ++++++++++++++++++++++++++++++++-- src/Managers/Config.cs | 4 ++-- src/Managers/Main.cs | 28 +--------------------------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/resources/localization.json b/resources/localization.json index e9afd56..068d435 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -22,7 +22,7 @@ "German (Germany)": "UNSER DISCORD" }, "polymod_hub_footer": { - "English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", "Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", "Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", "Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", diff --git a/src/Loader.cs b/src/Loader.cs index 05c5787..e984072 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -147,7 +147,7 @@ public static void AddPatchDataType(string typeId, Type type) typeMappings.Add(typeId, type); } - internal static void LoadMods(Dictionary mods) + internal static void RegisterMods(Dictionary mods) { Directory.CreateDirectory(Plugin.MODS_PATH); string[] modContainers = Directory.GetDirectories(Plugin.MODS_PATH) @@ -238,6 +238,36 @@ internal static void LoadMods(Dictionary mods) CheckDependencies(mods); } + internal static void LoadMods(Dictionary mods) + { + var dependencyCycle = !SortMods(Registry.mods); + if (dependencyCycle) return; + + StringBuilder checksumString = new(); + foreach (var (id, mod) in Registry.mods) + { + if (mod.status != Mod.Status.Success) continue; + foreach (var file in mod.files) + { + checksumString.Append(JsonSerializer.Serialize(file)); + if (Path.GetExtension(file.name) == ".dll") + { + LoadAssemblyFile(mod, file); + } + if (Path.GetFileName(file.name) == "sprites.json") + { + LoadSpriteInfoFile(mod, file); + } + } + if (!mod.client && id != "polytopia") + { + checksumString.Append(id); + checksumString.Append(mod.version.ToString()); + } + } + Compatibility.HashSignatures(checksumString); + + } private static void CheckDependencies(Dictionary mods) { foreach (var (id, mod) in mods) @@ -277,7 +307,7 @@ private static void CheckDependencies(Dictionary mods) } } - internal static bool SortMods(Dictionary mods) + private static bool SortMods(Dictionary mods) { Stopwatch s = new(); Dictionary> graph = new(); diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index f213c11..c4b164a 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -43,7 +43,7 @@ public Config(string modName, ConfigTypes configType) return; } var jsonText = File.ReadAllText(ExposedConfigPath); - _currentConfig = JObject.Parse(jsonText)[_modName].ToObject(); + _currentConfig = JObject.Parse(jsonText)[_modName]!.ToObject(); break; } default: @@ -104,7 +104,7 @@ public void SaveChanges() case ConfigTypes.Exposed: var modsConfigText = File.ReadAllText(ExposedConfigPath); var modsConfigJson = JObject.Parse(modsConfigText); - modsConfigJson[_modName] = JToken.FromObject(_currentConfig); + modsConfigJson[_modName] = JToken.FromObject(_currentConfig!); File.WriteAllText(ExposedConfigPath, modsConfigJson.ToString(Formatting.Indented)); break; default: diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 68892ed..91087c9 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -330,34 +330,8 @@ internal static void Init() Array.Empty() ); Registry.mods.Add(polytopia.id, new(polytopia, Mod.Status.Success, new())); + Loader.RegisterMods(Registry.mods); Loader.LoadMods(Registry.mods); - dependencyCycle = !Loader.SortMods(Registry.mods); - if (dependencyCycle) return; - - StringBuilder checksumString = new(); - foreach (var (id, mod) in Registry.mods) - { - if (mod.status != Mod.Status.Success) continue; - foreach (var file in mod.files) - { - checksumString.Append(JsonSerializer.Serialize(file)); - if (Path.GetExtension(file.name) == ".dll") - { - Loader.LoadAssemblyFile(mod, file); - } - if (Path.GetFileName(file.name) == "sprites.json") - { - Loader.LoadSpriteInfoFile(mod, file); - } - } - if (!mod.client && id != "polytopia") - { - checksumString.Append(id); - checksumString.Append(mod.version.ToString()); - } - } - Compatibility.HashSignatures(checksumString); - stopwatch.Stop(); } From bba12511f04760bf97c384d2aff24b92f6bb3ff1 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 17:04:51 +0200 Subject: [PATCH 04/26] Use System.Text.json instead of newtonsoft --- src/Loader.cs | 2 +- src/Managers/Config.cs | 18 +++++++++--------- src/PolyScriptMod.cs | 8 +++++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index e984072..61ed906 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -377,7 +377,7 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file) is { } modType) { var modInstance = (PolyScriptModBase) Activator.CreateInstance(modType)!; - modInstance.Initialize(mod.id); + modInstance.Initialize(mod.id, BepInEx.Logging.Logger.CreateLogSource($"PolyMod] [{mod.id}")); modInstance.Load(); } foreach (Type type in assembly.GetTypes()) diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index c4b164a..e7efcbd 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; namespace PolyMod.Managers; @@ -33,7 +33,7 @@ public Config(string modName, ConfigTypes configType) return; } var jsonText = File.ReadAllText(_perModConfigPath); - _currentConfig = JsonConvert.DeserializeObject(jsonText); + _currentConfig = JsonSerializer.Deserialize(jsonText); break; } case ConfigTypes.Exposed: @@ -43,7 +43,7 @@ public Config(string modName, ConfigTypes configType) return; } var jsonText = File.ReadAllText(ExposedConfigPath); - _currentConfig = JObject.Parse(jsonText)[_modName]!.ToObject(); + _currentConfig = JsonNode.Parse(jsonText)![_modName]!.Deserialize(); break; } default: @@ -98,14 +98,14 @@ public void SaveChanges() switch (_configType) { case ConfigTypes.PerMod: - var json = JsonConvert.SerializeObject(_currentConfig, Formatting.Indented); - File.WriteAllText(_perModConfigPath, json); + var perModJson = JsonSerializer.Serialize(_currentConfig, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(_perModConfigPath, perModJson); break; case ConfigTypes.Exposed: var modsConfigText = File.ReadAllText(ExposedConfigPath); - var modsConfigJson = JObject.Parse(modsConfigText); - modsConfigJson[_modName] = JToken.FromObject(_currentConfig!); - File.WriteAllText(ExposedConfigPath, modsConfigJson.ToString(Formatting.Indented)); + var modsConfigJson = JsonNode.Parse(modsConfigText)!.AsObject(); + modsConfigJson[_modName] = JsonSerializer.SerializeToNode(_currentConfig!); + File.WriteAllText(ExposedConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/PolyScriptMod.cs b/src/PolyScriptMod.cs index fe9ba3f..b116767 100644 --- a/src/PolyScriptMod.cs +++ b/src/PolyScriptMod.cs @@ -1,3 +1,4 @@ +using BepInEx.Logging; using Newtonsoft.Json.Linq; using PolyMod.Managers; @@ -5,7 +6,7 @@ namespace PolyMod; public abstract class PolyScriptModBase { - internal abstract void Initialize(string name); + internal abstract void Initialize(string name, ManualLogSource logger); public abstract void Load(); public abstract void UnLoad(); internal PolyScriptModBase() @@ -14,16 +15,17 @@ internal PolyScriptModBase() } public abstract class PolyScriptMod : PolyScriptModBase where TConfig : class where TExposedConfig : class { - internal override void Initialize(string name) + internal override void Initialize(string name, ManualLogSource logger) { ModName = name; Config = new Config(name, Config.ConfigTypes.PerMod); ExposedConfig = new Config(name, Config.ConfigTypes.Exposed); + Logger = logger; } public string ModName { get; private set; } protected Config Config { get; private set; } = null!; protected Config ExposedConfig { get; private set; } = null!; - protected virtual JObject DefaultConfig => new JObject(); + protected ManualLogSource Logger { get; private set; } = null!; } public abstract class PolyScriptMod : PolyScriptMod From 035d8bcbe2b6a28908c121644600c78a7085508d Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 17:28:14 +0200 Subject: [PATCH 05/26] add missing using statement --- src/Loader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Loader.cs b/src/Loader.cs index 61ed906..f497715 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -12,6 +12,7 @@ using System.Globalization; using System.IO.Compression; using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using UnityEngine; From 16210f43f3cdb909d1a5b39caa6516c4cee09f44 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 17:39:46 +0200 Subject: [PATCH 06/26] Fix 2 warnings --- src/Managers/Main.cs | 2 +- src/PolyScriptMod.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 91087c9..b7e1db7 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -16,7 +16,7 @@ public static class Main internal const int MAX_TECH_TIER = 100; internal static readonly Stopwatch stopwatch = new(); internal static bool fullyInitialized; - internal static bool dependencyCycle; + internal static bool dependencyCycle = false; // that warning wasnt really my fault internal static Dictionary embarkNames = new(); internal static Dictionary embarkOverrides = new(); internal static bool currentlyEmbarking = false; diff --git a/src/PolyScriptMod.cs b/src/PolyScriptMod.cs index b116767..08fdd80 100644 --- a/src/PolyScriptMod.cs +++ b/src/PolyScriptMod.cs @@ -22,7 +22,8 @@ internal override void Initialize(string name, ManualLogSource logger) ExposedConfig = new Config(name, Config.ConfigTypes.Exposed); Logger = logger; } - public string ModName { get; private set; } + + public string ModName { get; private set; } = null!; protected Config Config { get; private set; } = null!; protected Config ExposedConfig { get; private set; } = null!; protected ManualLogSource Logger { get; private set; } = null!; From 82fad3df047aba1d1030bde04b4ead03e938cb7a Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 19:49:19 +0200 Subject: [PATCH 07/26] Fix bug of game crashing on assembly load... --- src/Loader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Loader.cs b/src/Loader.cs index f497715..d60e1e8 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -380,6 +380,7 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file) var modInstance = (PolyScriptModBase) Activator.CreateInstance(modType)!; modInstance.Initialize(mod.id, BepInEx.Logging.Logger.CreateLogSource($"PolyMod] [{mod.id}")); modInstance.Load(); + return; } foreach (Type type in assembly.GetTypes()) { From 2345ca8ac560f705f648dcd5cd15ee833602e75e Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 19:53:52 +0200 Subject: [PATCH 08/26] other languages in localization --- resources/localization.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/localization.json b/resources/localization.json index 068d435..c74653e 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -23,14 +23,14 @@ }, "polymod_hub_footer": { "English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", - "German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon" + "Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon", + "German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon" }, "polymod_hub_header": { "English": "{0}Welcome!{1}\nHere you can see the list of all currently loaded mods:", From c5154ec21b0fe82487a17acec41dbd318e9d0bf4 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 19:58:31 +0200 Subject: [PATCH 09/26] rename polyscriptmod to polymod --- src/Managers/Config.cs | 4 ++-- src/{PolyScriptMod.cs => PolyMod.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/{PolyScriptMod.cs => PolyMod.cs} (82%) diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index e7efcbd..981388a 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -4,7 +4,7 @@ namespace PolyMod.Managers; /// -/// allows mods to save config. +/// Allows mods to save config. /// public class Config where T : class { @@ -91,7 +91,7 @@ public TResult Get(Func getter) return getter(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); } /// - /// writes the config to disk + /// Writes the config to disk /// public void SaveChanges() { diff --git a/src/PolyScriptMod.cs b/src/PolyMod.cs similarity index 82% rename from src/PolyScriptMod.cs rename to src/PolyMod.cs index 08fdd80..77c2a6c 100644 --- a/src/PolyScriptMod.cs +++ b/src/PolyMod.cs @@ -13,7 +13,7 @@ internal PolyScriptModBase() { } } -public abstract class PolyScriptMod : PolyScriptModBase where TConfig : class where TExposedConfig : class +public abstract class PolyMod : PolyScriptModBase where TConfig : class where TExposedConfig : class { internal override void Initialize(string name, ManualLogSource logger) { @@ -29,6 +29,6 @@ internal override void Initialize(string name, ManualLogSource logger) protected ManualLogSource Logger { get; private set; } = null!; } -public abstract class PolyScriptMod : PolyScriptMod +public abstract class PolyMod : PolyMod { } \ No newline at end of file From d499bc21b2f37c04fa36a8cc527100988b25c9c4 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 18:36:25 +0200 Subject: [PATCH 10/26] Add gld config --- PolyMod.csproj | 3 +- src/Managers/Config.cs | 2 +- src/Managers/GLDConfig.cs | 84 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/Managers/GLDConfig.cs diff --git a/PolyMod.csproj b/PolyMod.csproj index 072a080..921dbba 100644 --- a/PolyMod.csproj +++ b/PolyMod.csproj @@ -1,4 +1,4 @@ - + net6.0 enable @@ -20,6 +20,7 @@ + diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index 981388a..371da03 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -43,7 +43,7 @@ public Config(string modName, ConfigTypes configType) return; } var jsonText = File.ReadAllText(ExposedConfigPath); - _currentConfig = JsonNode.Parse(jsonText)![_modName]!.Deserialize(); + _currentConfig = JsonNode.Parse(jsonText)![_modName]?.Deserialize(); break; } default: diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs new file mode 100644 index 0000000..2dcdc30 --- /dev/null +++ b/src/Managers/GLDConfig.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Scriban; +using Scriban.Runtime; + +namespace PolyMod.Managers; + +public class GldConfigTemplate +{ + private static readonly string ConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); + + private readonly string _templateText; + private JsonObject _currentConfig = new(); + private string _modName; + + public GldConfigTemplate(string templateText, string modName) + { + _templateText = templateText; + _modName = modName; + Load(); + } + private void Load() + { + if (File.Exists(ConfigPath)) + { + var json = File.ReadAllText(ConfigPath); + if (JsonNode.Parse(json) is JsonObject modsConfig + && modsConfig.TryGetPropertyValue(_modName, out var modConfigNode) + && modConfigNode is JsonObject modConfig) + { + _currentConfig = modConfig; + return; + } + } + _currentConfig = new JsonObject(); + } + + public string Render() + { + var template = Template.Parse(_templateText); + var context = new TemplateContext(); + var scriptObject = new ScriptObject(); + + bool changedConfig = false; + scriptObject.Import("config", + new Func((key, defaultValue) => + { + if (_currentConfig.TryGetPropertyValue(key, out var token) && token != null) + { + return token.ToString(); + } + + changedConfig = true; + _currentConfig[key] = defaultValue; + + return defaultValue; + }) + ); + context.PushGlobal(scriptObject); + var result = template.Render(context); + if (changedConfig) + { + SaveChanges(); + } + return result; + } + + public void SaveChanges() + { + JsonObject modsConfigJson; + if (File.Exists(ConfigPath)) + { + var modsConfigText = File.ReadAllText(ConfigPath); + modsConfigJson = (JsonNode.Parse(modsConfigText) as JsonObject) ?? new JsonObject(); + } + else + { + modsConfigJson = new JsonObject(); + } + + modsConfigJson[_modName] = _currentConfig; + File.WriteAllText(ConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); + } +} \ No newline at end of file From 267e58416c70070bf06f8bda976a37033f61685c Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 18:42:38 +0200 Subject: [PATCH 11/26] Actually load config --- src/Managers/GLDConfig.cs | 1 + src/Managers/Main.cs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs index 2dcdc30..a8f287d 100644 --- a/src/Managers/GLDConfig.cs +++ b/src/Managers/GLDConfig.cs @@ -37,6 +37,7 @@ private void Load() public string Render() { + if (!_templateText.Contains("{{")) return _templateText; var template = Template.Parse(_templateText); var context = new TemplateContext(); var scriptObject = new ScriptObject(); diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index b7e1db7..b07cfd4 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -357,10 +357,12 @@ internal static void Load(JObject gameLogicdata) } if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$")) { + var patchText = new StreamReader(new MemoryStream(file.bytes)).ReadToEnd(); + var template = new GldConfigTemplate(patchText, mod.id); Loader.LoadGameLogicDataPatch( mod, gameLogicdata, - JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()) + JObject.Parse(template.Render()) ); continue; } From 9bf500733dc1706a96919b342a1cb8cf0345b8ff Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Mon, 18 Aug 2025 20:42:38 +0200 Subject: [PATCH 12/26] Revert "rename polyscriptmod to polymod" This reverts commit c5154ec21b0fe82487a17acec41dbd318e9d0bf4. --- src/Managers/Config.cs | 4 ++-- src/{PolyMod.cs => PolyScriptMod.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/{PolyMod.cs => PolyScriptMod.cs} (82%) diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index 371da03..ca4264e 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -4,7 +4,7 @@ namespace PolyMod.Managers; /// -/// Allows mods to save config. +/// allows mods to save config. /// public class Config where T : class { @@ -91,7 +91,7 @@ public TResult Get(Func getter) return getter(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); } /// - /// Writes the config to disk + /// writes the config to disk /// public void SaveChanges() { diff --git a/src/PolyMod.cs b/src/PolyScriptMod.cs similarity index 82% rename from src/PolyMod.cs rename to src/PolyScriptMod.cs index 77c2a6c..08fdd80 100644 --- a/src/PolyMod.cs +++ b/src/PolyScriptMod.cs @@ -13,7 +13,7 @@ internal PolyScriptModBase() { } } -public abstract class PolyMod : PolyScriptModBase where TConfig : class where TExposedConfig : class +public abstract class PolyScriptMod : PolyScriptModBase where TConfig : class where TExposedConfig : class { internal override void Initialize(string name, ManualLogSource logger) { @@ -29,6 +29,6 @@ internal override void Initialize(string name, ManualLogSource logger) protected ManualLogSource Logger { get; private set; } = null!; } -public abstract class PolyMod : PolyMod +public abstract class PolyScriptMod : PolyScriptMod { } \ No newline at end of file From fea14c1e52a03377655cc0c017e5474afb627ff9 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 09:14:41 +0200 Subject: [PATCH 13/26] Fix bug related to game crashing on launch --- FodyWeavers.xml | 8 ++++++++ PolyMod.csproj | 3 +++ src/Managers/GLDConfig.cs | 13 +++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 FodyWeavers.xml diff --git a/FodyWeavers.xml b/FodyWeavers.xml new file mode 100644 index 0000000..d24cbce --- /dev/null +++ b/FodyWeavers.xml @@ -0,0 +1,8 @@ + + + + + Scriban + + + \ No newline at end of file diff --git a/PolyMod.csproj b/PolyMod.csproj index 921dbba..5a2e73a 100644 --- a/PolyMod.csproj +++ b/PolyMod.csproj @@ -20,6 +20,9 @@ + + all + diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs index a8f287d..846332a 100644 --- a/src/Managers/GLDConfig.cs +++ b/src/Managers/GLDConfig.cs @@ -35,7 +35,7 @@ private void Load() _currentConfig = new JsonObject(); } - public string Render() + public string? Render() { if (!_templateText.Contains("{{")) return _templateText; var template = Template.Parse(_templateText); @@ -58,7 +58,16 @@ public string Render() }) ); context.PushGlobal(scriptObject); - var result = template.Render(context); + string? result; + try + { + result = template.Render(context); + } + catch (Exception e) + { + Plugin.logger.LogError("error during parse of gld patch template: " + e.ToString()); + result = null; + } if (changedConfig) { SaveChanges(); From cdc467b47472f1e9310341dfbf03dce824591dbf Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 09:18:33 +0200 Subject: [PATCH 14/26] On error, set mod to errrror instead of crashing --- src/Managers/Main.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index b07cfd4..b34c98d 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -359,10 +359,16 @@ internal static void Load(JObject gameLogicdata) { var patchText = new StreamReader(new MemoryStream(file.bytes)).ReadToEnd(); var template = new GldConfigTemplate(patchText, mod.id); + var text = template.Render(); + if (text is null) + { + mod.status = Mod.Status.Error; + continue; + } Loader.LoadGameLogicDataPatch( mod, gameLogicdata, - JObject.Parse(template.Render()) + JObject.Parse(text) ); continue; } From fe55cb62fe3397551fe17a41f4a8af27afce010f Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 09:59:41 +0200 Subject: [PATCH 15/26] Change naming for @jkdev --- src/Managers/Config.cs | 50 +++++++++++++++++++-------------------- src/Managers/GLDConfig.cs | 26 ++++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index ca4264e..dddb15d 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -8,32 +8,32 @@ namespace PolyMod.Managers; /// public class Config where T : class { - private T? _currentConfig; - private readonly string _modName; - private readonly ConfigTypes _configType; + private T? currentConfig; + private readonly string modName; + private readonly ConfigTypes configType; private static readonly string ExposedConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); - private readonly string _perModConfigPath; - private T? _defaultConfig; + private readonly string perModConfigPath; + private T? defaultConfig; public Config(string modName, ConfigTypes configType) { - _modName = modName; - _configType = configType; - _perModConfigPath = Path.Combine(Plugin.MODS_PATH, $"{modName}.json"); + this.modName = modName; + this.configType = configType; + perModConfigPath = Path.Combine(Plugin.MODS_PATH, $"{modName}.json"); Load(); } internal void Load() // can be called internally if config changes; gui config not implemented yet { - switch (_configType) + switch (configType) { case ConfigTypes.PerMod: { - if (!File.Exists(_perModConfigPath)) + if (!File.Exists(perModConfigPath)) { return; } - var jsonText = File.ReadAllText(_perModConfigPath); - _currentConfig = JsonSerializer.Deserialize(jsonText); + var jsonText = File.ReadAllText(perModConfigPath); + currentConfig = JsonSerializer.Deserialize(jsonText); break; } case ConfigTypes.Exposed: @@ -43,7 +43,7 @@ public Config(string modName, ConfigTypes configType) return; } var jsonText = File.ReadAllText(ExposedConfigPath); - _currentConfig = JsonNode.Parse(jsonText)![_modName]?.Deserialize(); + currentConfig = JsonNode.Parse(jsonText)![modName]?.Deserialize(); break; } default: @@ -55,9 +55,9 @@ public Config(string modName, ConfigTypes configType) /// public void SetDefaultConfig(T defaultValue) { - _defaultConfig = defaultValue; - if (_currentConfig is not null) return; - Write(_defaultConfig); + defaultConfig = defaultValue; + if (currentConfig is not null) return; + Write(defaultConfig); SaveChanges(); } @@ -66,45 +66,45 @@ public void SetDefaultConfig(T defaultValue) /// public void Write(T config) { - _currentConfig = config; + currentConfig = config; } /// /// Gets the config. Should only be called after setting a default. /// public T Get() { - return _currentConfig ?? throw new InvalidOperationException("Must set default before reading config."); + return currentConfig ?? throw new InvalidOperationException("Must set default before reading config."); } /// - /// edits the config. Should only be called after setting a default. + /// Edits the config. Should only be called after setting a default. /// /// Call SaveChanges after editing public void Edit(Action editor) { - editor(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); + editor(currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); } /// /// Gets part of the config. Should only be called after setting a default /// public TResult Get(Func getter) { - return getter(_currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); + return getter(currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); } /// /// writes the config to disk /// public void SaveChanges() { - switch (_configType) + switch (configType) { case ConfigTypes.PerMod: - var perModJson = JsonSerializer.Serialize(_currentConfig, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(_perModConfigPath, perModJson); + var perModJson = JsonSerializer.Serialize(currentConfig, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(perModConfigPath, perModJson); break; case ConfigTypes.Exposed: var modsConfigText = File.ReadAllText(ExposedConfigPath); var modsConfigJson = JsonNode.Parse(modsConfigText)!.AsObject(); - modsConfigJson[_modName] = JsonSerializer.SerializeToNode(_currentConfig!); + modsConfigJson[modName] = JsonSerializer.SerializeToNode(currentConfig!); File.WriteAllText(ExposedConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); break; default: diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs index 846332a..e341ab5 100644 --- a/src/Managers/GLDConfig.cs +++ b/src/Managers/GLDConfig.cs @@ -9,14 +9,14 @@ public class GldConfigTemplate { private static readonly string ConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); - private readonly string _templateText; - private JsonObject _currentConfig = new(); - private string _modName; + private readonly string templateText; + private JsonObject currentConfig = new(); + private string modName; public GldConfigTemplate(string templateText, string modName) { - _templateText = templateText; - _modName = modName; + this.templateText = templateText; + this.modName = modName; Load(); } private void Load() @@ -25,20 +25,20 @@ private void Load() { var json = File.ReadAllText(ConfigPath); if (JsonNode.Parse(json) is JsonObject modsConfig - && modsConfig.TryGetPropertyValue(_modName, out var modConfigNode) + && modsConfig.TryGetPropertyValue(modName, out var modConfigNode) && modConfigNode is JsonObject modConfig) { - _currentConfig = modConfig; + currentConfig = modConfig; return; } } - _currentConfig = new JsonObject(); + currentConfig = new JsonObject(); } public string? Render() { - if (!_templateText.Contains("{{")) return _templateText; - var template = Template.Parse(_templateText); + if (!templateText.Contains("{{")) return templateText; + var template = Template.Parse(templateText); var context = new TemplateContext(); var scriptObject = new ScriptObject(); @@ -46,13 +46,13 @@ private void Load() scriptObject.Import("config", new Func((key, defaultValue) => { - if (_currentConfig.TryGetPropertyValue(key, out var token) && token != null) + if (currentConfig.TryGetPropertyValue(key, out var token) && token != null) { return token.ToString(); } changedConfig = true; - _currentConfig[key] = defaultValue; + currentConfig[key] = defaultValue; return defaultValue; }) @@ -88,7 +88,7 @@ public void SaveChanges() modsConfigJson = new JsonObject(); } - modsConfigJson[_modName] = _currentConfig; + modsConfigJson[modName] = currentConfig; File.WriteAllText(ConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); } } \ No newline at end of file From 2ccae9cff8dab064b0392e0c83e57e4b7ac3c7fb Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 10:52:59 +0200 Subject: [PATCH 16/26] add doc comments and change file locations --- src/Loader.cs | 71 +++++++++++++++++++-- src/Managers/Config.cs | 4 +- src/Managers/GLDConfig.cs | 2 +- src/Managers/Visual.cs | 74 +++++++++++++++++++++- src/Mod.cs | 58 +++++++++++++++++ src/{Managers => Patches}/Audio.cs | 2 +- src/{Managers => Patches}/AutoUpdate.cs | 0 src/{Managers => Patches}/Compatibility.cs | 0 src/{Managers => Patches}/Main.cs | 71 ++------------------- src/Registry.cs | 52 +++++++++++++-- 10 files changed, 253 insertions(+), 81 deletions(-) rename src/{Managers => Patches}/Audio.cs (98%) rename src/{Managers => Patches}/AutoUpdate.cs (100%) rename src/{Managers => Patches}/Compatibility.cs (100%) rename src/{Managers => Patches}/Main.cs (87%) diff --git a/src/Loader.cs b/src/Loader.cs index d60e1e8..9c3e087 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -148,7 +148,7 @@ public static void AddPatchDataType(string typeId, Type type) typeMappings.Add(typeId, type); } - internal static void RegisterMods(Dictionary mods) + internal static bool RegisterMods(Dictionary mods) { Directory.CreateDirectory(Plugin.MODS_PATH); string[] modContainers = Directory.GetDirectories(Plugin.MODS_PATH) @@ -237,13 +237,11 @@ internal static void RegisterMods(Dictionary mods) } CheckDependencies(mods); + var dependencyCycle = !SortMods(Registry.mods); + return dependencyCycle; } - internal static void LoadMods(Dictionary mods) { - var dependencyCycle = !SortMods(Registry.mods); - if (dependencyCycle) return; - StringBuilder checksumString = new(); foreach (var (id, mod) in Registry.mods) { @@ -269,6 +267,69 @@ internal static void LoadMods(Dictionary mods) Compatibility.HashSignatures(checksumString); } + internal static void PatchGLD(JObject gameLogicdata, Dictionary mods) + { + Loc.BuildAndLoadLocalization( + JsonSerializer.Deserialize>>( + Plugin.GetResource("localization.json") + )! + ); + foreach (var (id, mod) in mods) + { + if (mod.status != Mod.Status.Success) continue; + foreach (var file in mod.files) + { + if (Path.GetFileName(file.name) == "localization.json") + { + Loader.LoadLocalizationFile(mod, file); + continue; + } + if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$")) + { + var patchText = new StreamReader(new MemoryStream(file.bytes)).ReadToEnd(); + var template = new GldConfigTemplate(patchText, mod.id); + var text = template.Render(); + if (text is null) + { + mod.status = Mod.Status.Error; + continue; + } + Loader.LoadGameLogicDataPatch( + mod, + gameLogicdata, + JObject.Parse(text) + ); + continue; + } + if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$")) + { + Loader.LoadPrefabInfoFile( + mod, + file + ); + continue; + } + + switch (Path.GetExtension(file.name)) + { + case ".png": + Loader.LoadSpriteFile(mod, file); + break; + case ".wav": + Loader.LoadAudioFile(mod, file); + break; + case ".bundle": + Loader.LoadAssetBundle(mod, file); + break; + } + } + } + TechItem.techTierFirebaseId.Clear(); + for (int i = 0; i <= Main.MAX_TECH_TIER; i++) + { + TechItem.techTierFirebaseId.Add($"tech_research_{i}"); + } + } private static void CheckDependencies(Dictionary mods) { foreach (var (id, mod) in mods) diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index dddb15d..f320264 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -4,7 +4,7 @@ namespace PolyMod.Managers; /// -/// allows mods to save config. +/// Allows mods to save config. /// public class Config where T : class { @@ -91,7 +91,7 @@ public TResult Get(Func getter) return getter(currentConfig ?? throw new InvalidOperationException("Must set default before reading config.")); } /// - /// writes the config to disk + /// Writes the config to disk /// public void SaveChanges() { diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs index e341ab5..24b395b 100644 --- a/src/Managers/GLDConfig.cs +++ b/src/Managers/GLDConfig.cs @@ -5,7 +5,7 @@ namespace PolyMod.Managers; -public class GldConfigTemplate +internal class GldConfigTemplate { private static readonly string ConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index e816f0b..031d26a 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -10,40 +10,93 @@ namespace PolyMod.Managers; +/// +/// Manages visual aspects of the game, including sprites, UI elements, and in-game object appearances. +/// public static class Visual { + /// + /// Represents a single tile in a tribe's world preview. + /// public class PreviewTile { + /// The X-coordinate of the tile. [JsonInclude] public int? x = null; + /// The Y-coordinate of the tile. [JsonInclude] public int? y = null; + /// The type of terrain on the tile. [JsonInclude] [JsonConverter(typeof(EnumCacheJson))] public Polytopia.Data.TerrainData.Type terrainType = Polytopia.Data.TerrainData.Type.Ocean; + /// The type of resource on the tile. [JsonInclude] [JsonConverter(typeof(EnumCacheJson))] public ResourceData.Type resourceType = ResourceData.Type.None; + /// The type of unit on the tile. [JsonInclude] [JsonConverter(typeof(EnumCacheJson))] public UnitData.Type unitType = UnitData.Type.None; + /// The type of improvement on the tile. [JsonInclude] [JsonConverter(typeof(EnumCacheJson))] public ImprovementData.Type improvementType = ImprovementData.Type.None; } + + /// + /// Holds information for creating a custom sprite. + /// + /// The number of pixels per unit for the sprite. + /// The pivot point of the sprite. public record SpriteInfo(float? pixelsPerUnit, Vector2? pivot); + + /// + /// Holds information about a custom skin. + /// + /// The index of the skin. + /// The unique identifier for the skin. + /// The data associated with the skin. public record SkinInfo(int idx, string id, SkinData? skinData); + + /// + /// A dictionary mapping BasicPopup instance IDs to their custom widths. + /// public static Dictionary basicPopupWidths = new(); private static bool firstTimeOpeningPreview = true; private static UnitData.Type currentUnitTypeUI = UnitData.Type.None; private static TribeData.Type attackerTribe = TribeData.Type.None; + + /// + /// Defines the types of prefabs that can be customized. + /// public enum PrefabType { + /// A unit prefab. Unit, + /// An improvement prefab. Improvement, + /// A resource prefab. Resource } + + /// + /// Holds information for a custom prefab. + /// + /// The type of the prefab. + /// The name of the prefab. + /// A list of visual parts that make up the prefab. public record PrefabInfo(PrefabType type, string name, List visualParts); + + /// + /// Represents a visual part of a custom prefab. + /// + /// The name of the GameObject for this visual part. + /// The base name for sprite lookups. + /// The rotation of the visual part. + /// The local position of the visual part. + /// The local scale of the visual part. + /// Whether the visual part can be tinted. public record VisualPartInfo( string gameObjectName, string baseName, @@ -257,7 +310,7 @@ private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, T if (tile.data.terrain is Polytopia.Data.TerrainData.Type.Forest or Polytopia.Data.TerrainData.Type.Mountain) { string propertyName = terrain.ToLower(); - terrain = "field"; + terrain = "field"; PropertyInfo? rendererProperty = tile.GetType().GetProperty(propertyName + "Renderer", BindingFlags.Public | BindingFlags.Instance); @@ -565,6 +618,11 @@ private static void PopupBase_Hide(PopupBase __instance) basicPopupWidths.Remove(__instance.GetInstanceID()); } + /// + /// Shows a BasicPopup and sets its width. + /// + /// The BasicPopup instance. + /// The desired width of the popup. public static void ShowSetWidth(this BasicPopup self, int width) { basicPopupWidths.Add(self.GetInstanceID(), width); @@ -594,6 +652,13 @@ private static void UpdateVisualPart(SkinVisualsReference.VisualPart visualPart, } } + /// + /// Creates a from raw image data. + /// + /// The byte array containing the image data. + /// The pivot point for the sprite. Defaults to the center. + /// The number of pixels per unit for the sprite. + /// A new instance. public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixelsPerUnit = 2112f) { Texture2D texture = new(1, 1, TextureFormat.RGBA32, true); @@ -609,6 +674,13 @@ public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixel return BuildSpriteWithTexture(texture, pivot, pixelsPerUnit); } + /// + /// Creates a from an existing . + /// + /// The texture to create the sprite from. + /// The pivot point for the sprite. Defaults to the center. + /// The number of pixels per unit for the sprite. + /// A new instance. public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float? pixelsPerUnit = 2112f) { return Sprite.Create( diff --git a/src/Mod.cs b/src/Mod.cs index 36e81c1..4ee9e30 100644 --- a/src/Mod.cs +++ b/src/Mod.cs @@ -1,27 +1,76 @@ namespace PolyMod; +/// +/// Represents a mod, containing its manifest information, status, and files. +/// public class Mod { + /// + /// Represents a dependency for a mod. + /// + /// The unique identifier of the dependency. + /// The minimum compatible version of the dependency. + /// The maximum compatible version of the dependency. + /// Whether the dependency is required for the mod to function. public record Dependency(string id, Version min, Version max, bool required = true); + + /// + /// Represents the manifest of a mod, containing metadata. + /// + /// The unique identifier of the mod. + /// The display name of the mod. + /// A description of the mod. + /// The version of the mod. + /// The authors of the mod. + /// The dependencies of the mod. + /// Whether the mod is client-side only. public record Manifest(string id, string? name, string? description, Version version, string[] authors, Dependency[]? dependencies, bool client = false); + + /// + /// Represents a file included in a mod. + /// + /// The name of the file. + /// The raw byte content of the file. public record File(string name, byte[] bytes); + + /// + /// Represents the loading status of a mod. + /// public enum Status { + /// The mod was loaded successfully. Success, + /// An error occurred while loading the mod. Error, + /// The mod's dependencies were not satisfied. DependenciesUnsatisfied, } + /// The unique identifier of the mod. public string id; + /// The display name of the mod. public string? name; + /// A description of the mod. public string? description; + /// The version of the mod. public Version version; + /// The authors of the mod. public string[] authors; + /// The dependencies of the mod. public Dependency[]? dependencies; + /// Whether the mod is client-side only. public bool client; + /// The loading status of the mod. public Status status; + /// The files included in the mod. public List files; + /// + /// Initializes a new instance of the class. + /// + /// The mod's manifest data. + /// The initial loading status of the mod. + /// The list of files included in the mod. public Mod(Manifest manifest, Status status, List files) { id = manifest.id; @@ -34,4 +83,13 @@ public Mod(Manifest manifest, Status status, List files) this.status = status; this.files = files; } + + /// + /// Returns a string that represents the current mod. + /// + /// A string representation of the mod. + public override string ToString() + { + return $"mod: id={id} dependencies={dependencies} status={status}"; + } } diff --git a/src/Managers/Audio.cs b/src/Patches/Audio.cs similarity index 98% rename from src/Managers/Audio.cs rename to src/Patches/Audio.cs index f93ee78..b15ef4d 100644 --- a/src/Managers/Audio.cs +++ b/src/Patches/Audio.cs @@ -5,7 +5,7 @@ namespace PolyMod.Managers; -public static class Audio +internal static class Audio { [HarmonyPostfix] [HarmonyPatch(typeof(AudioManager), nameof(AudioManager.SetupData))] diff --git a/src/Managers/AutoUpdate.cs b/src/Patches/AutoUpdate.cs similarity index 100% rename from src/Managers/AutoUpdate.cs rename to src/Patches/AutoUpdate.cs diff --git a/src/Managers/Compatibility.cs b/src/Patches/Compatibility.cs similarity index 100% rename from src/Managers/Compatibility.cs rename to src/Patches/Compatibility.cs diff --git a/src/Managers/Main.cs b/src/Patches/Main.cs similarity index 87% rename from src/Managers/Main.cs rename to src/Patches/Main.cs index b34c98d..022f6b6 100644 --- a/src/Managers/Main.cs +++ b/src/Patches/Main.cs @@ -11,12 +11,12 @@ namespace PolyMod.Managers; -public static class Main +internal static class Main { internal const int MAX_TECH_TIER = 100; internal static readonly Stopwatch stopwatch = new(); internal static bool fullyInitialized; - internal static bool dependencyCycle = false; // that warning wasnt really my fault + internal static bool dependencyCycle; internal static Dictionary embarkNames = new(); internal static Dictionary embarkOverrides = new(); internal static bool currentlyEmbarking = false; @@ -330,76 +330,15 @@ internal static void Init() Array.Empty() ); Registry.mods.Add(polytopia.id, new(polytopia, Mod.Status.Success, new())); - Loader.RegisterMods(Registry.mods); - Loader.LoadMods(Registry.mods); + dependencyCycle = Loader.RegisterMods(Registry.mods); + if (!dependencyCycle) Loader.LoadMods(Registry.mods); stopwatch.Stop(); } internal static void Load(JObject gameLogicdata) { stopwatch.Start(); - Loc.BuildAndLoadLocalization( - JsonSerializer.Deserialize>>( - Plugin.GetResource("localization.json") - )! - ); - if (dependencyCycle) return; - - foreach (var (id, mod) in Registry.mods) - { - if (mod.status != Mod.Status.Success) continue; - foreach (var file in mod.files) - { - if (Path.GetFileName(file.name) == "localization.json") - { - Loader.LoadLocalizationFile(mod, file); - continue; - } - if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$")) - { - var patchText = new StreamReader(new MemoryStream(file.bytes)).ReadToEnd(); - var template = new GldConfigTemplate(patchText, mod.id); - var text = template.Render(); - if (text is null) - { - mod.status = Mod.Status.Error; - continue; - } - Loader.LoadGameLogicDataPatch( - mod, - gameLogicdata, - JObject.Parse(text) - ); - continue; - } - if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$")) - { - Loader.LoadPrefabInfoFile( - mod, - file - ); - continue; - } - - switch (Path.GetExtension(file.name)) - { - case ".png": - Loader.LoadSpriteFile(mod, file); - break; - case ".wav": - Loader.LoadAudioFile(mod, file); - break; - case ".bundle": - Loader.LoadAssetBundle(mod, file); - break; - } - } - } - TechItem.techTierFirebaseId.Clear(); - for (int i = 0; i <= MAX_TECH_TIER; i++) - { - TechItem.techTierFirebaseId.Add($"tech_research_{i}"); - } + if (!dependencyCycle) Loader.PatchGLD(gameLogicdata, Registry.mods); stopwatch.Stop(); Plugin.logger.LogInfo($"Loaded all mods in {stopwatch.ElapsedMilliseconds}ms"); } diff --git a/src/Registry.cs b/src/Registry.cs index 207d225..c95b32a 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -6,24 +6,60 @@ namespace PolyMod; +/// +/// Provides central registries for custom content managed by PolyMod. +/// public static class Registry { - public static int autoidx = Plugin.AUTOIDX_STARTS_FROM; + internal static int autoidx = Plugin.AUTOIDX_STARTS_FROM; + + /// The registry for custom sprites. public static Dictionary sprites = new(); + + /// The registry for custom audio clips. public static Dictionary audioClips = new(); - internal static Dictionary mods = new(); + + /// The registry for loaded mods. + public static Dictionary mods = new(); + + /// The registry for custom tribe previews. public static Dictionary tribePreviews = new(); + + /// The registry for custom sprite information. public static Dictionary spriteInfos = new(); + + /// The registry for custom prefab names. public static Dictionary prefabNames = new(); + + /// The registry for custom unit prefabs. public static Dictionary unitPrefabs = new(); + + /// The registry for custom resource prefabs. public static Dictionary resourcePrefabs = new(); + + /// The registry for custom improvement prefabs. public static Dictionary improvementsPrefabs = new(); + + /// The registry for loaded asset bundles. public static Dictionary assetBundles = new(); + + /// The registry for custom tribes. public static List customTribes = new(); + + /// The registry for custom skin information. public static List skinInfo = new(); - public static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last(); - public static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length; + internal static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last(); + internal static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length; + + /// + /// Retrieves a sprite from the registry based on its name, style, and level. + /// The method searches for multiple key formats to find the best match. + /// + /// The base name of the sprite. + /// The style variant of the sprite (e.g., tribe-specific). + /// The level variant of the sprite (e.g., for a city). + /// The matching , or null if no sprite is found. public static Sprite? GetSprite(string name, string style = "", int level = 0) { Sprite? sprite = null; @@ -36,6 +72,12 @@ public static class Registry return sprite; } + /// + /// Retrieves an audio clip from the registry. + /// + /// The name of the audio clip. + /// The style of the audio clip. + /// The matching , or null if not found. public static AudioClip? GetAudioClip(string name, string style) { AudioSource? audioSource = null; @@ -45,4 +87,4 @@ public static class Registry if (audioSource == null) return null; return audioSource.clip; } -} \ No newline at end of file +} From 7dacb685a4b3844403767c21c58f8a8db5be8e17 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 14:43:14 +0200 Subject: [PATCH 17/26] Add docs --- docs/.gitignore | 2 + docs/MinorDatas.md | 126 ++++++++++++++++++++ docs/articles/getting-started.md | 1 + docs/articles/introduction.md | 1 + docs/articles/toc.yml | 4 + docs/docfx.json | 44 +++++++ docs/gld/Improvement.yml | 109 +++++++++++++++++ docs/gld/MinorDatas.md | 130 +++++++++++++++++++++ docs/gld/Technology.yml | 95 +++++++++++++++ docs/gld/Tribe.yml | 195 +++++++++++++++++++++++++++++++ docs/gld/Unit.yml | 138 ++++++++++++++++++++++ docs/gld/index.md | 5 + docs/gld/toc.yml | 12 ++ docs/index.md | 11 ++ docs/toc.yml | 6 + 15 files changed, 879 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/MinorDatas.md create mode 100644 docs/articles/getting-started.md create mode 100644 docs/articles/introduction.md create mode 100644 docs/articles/toc.yml create mode 100644 docs/docfx.json create mode 100644 docs/gld/Improvement.yml create mode 100644 docs/gld/MinorDatas.md create mode 100644 docs/gld/Technology.yml create mode 100644 docs/gld/Tribe.yml create mode 100644 docs/gld/Unit.yml create mode 100644 docs/gld/index.md create mode 100644 docs/gld/toc.yml create mode 100644 docs/index.md create mode 100644 docs/toc.yml diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..d5bcab1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_site/ +api/ diff --git a/docs/MinorDatas.md b/docs/MinorDatas.md new file mode 100644 index 0000000..892c4f5 --- /dev/null +++ b/docs/MinorDatas.md @@ -0,0 +1,126 @@ +# Minor Data Structures + +This page documents the smaller data structures found in the game's data file, including terrain, resources, tasks, skins, and diplomacy settings. + +--- + +## Terrain Data (`terrainData`) + +Defines all possible terrain types in the game. Each terrain is an object within the `terrainData` section, keyed by its name. To add a new terrain, simply add a new entry with a unique name and `idx`. + +### Syntax + +```json +"terrainData": { + "water": { + "idx": 1 + }, + "field": { + "idx": 3 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| `idx` | Integer | A unique integer index for the terrain type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Resource Data (`resourceData`) + +Defines all possible resource types that can spawn on the map. + +### Syntax + +```json +"resourceData": { + "game": { + "resourceTerrainRequirements": [ + "forest" + ], + "idx": 1 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------- | +| `resourceTerrainRequirements` | Array of Strings | An array of terrain IDs where this resource can naturally spawn. | `["forest"]` | +| `idx` | Integer | A unique integer index for the resource type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Task Data (`taskData`) + +Defines special in-game achievements or "tasks" that unlock unique monuments upon completion. + +### Syntax + +```json +"taskData": { + "pacifist": { + "improvementUnlocks": [ + "monument1" + ], + "idx": 1 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- | +| `improvementUnlocks` | Array of Strings | An array of improvement IDs (usually a monument) unlocked when this task is completed. | `["monument1"]` | +| `idx` | Integer | A unique integer index for the task. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Skin Data (`skinData`) + +Defines alternate appearances (skins) for tribes. A tribe must list the skin's key in its `skins` array to use it. + +### Syntax + +```json +"skinData": { + "swamp" : { + "color" : 6786096, + "language": "bub,ly,sq,ee,to,ad" + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------- | :------ | :---------------------------------------------------------------------------------- | :----------------------------------- | +| `color` | Integer | An integer representing the skin's primary color, overriding the tribe's default. | `"color": 6786096` | +| `language` | String | A comma-separated string of syllables, overriding the tribe's default language. | `"language": "bub,ly,sq,ee,to,ad"` | + +--- + +## Diplomacy Data (`diplomacyData`) + +Contains key-value pairs that define the global game mechanics for diplomacy, such as embassies. + +### Syntax + +```json +"diplomacyData": { + "embassyCost" : 5, + "embassyIncome" : 2, + "embassyMaxLevel" : 3, + "embassyUpgradeCost" : 20 +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------------------- | :------ | :-------------------------------------------------------- diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md new file mode 100644 index 0000000..8b3a794 --- /dev/null +++ b/docs/articles/getting-started.md @@ -0,0 +1 @@ +# Getting Started \ No newline at end of file diff --git a/docs/articles/introduction.md b/docs/articles/introduction.md new file mode 100644 index 0000000..f6ecaa6 --- /dev/null +++ b/docs/articles/introduction.md @@ -0,0 +1 @@ +# Introduction \ No newline at end of file diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml new file mode 100644 index 0000000..d3663b9 --- /dev/null +++ b/docs/articles/toc.yml @@ -0,0 +1,4 @@ +- name: Introduction + href: introduction.md +- name: Getting Started + href: getting-started.md \ No newline at end of file diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..5167ddf --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "metadata": [{ + "src": [ + { + "src": "../", + "files": [ + "**/*.csproj" + ], + } + ], + "dest": "api" + }], + "build": { + "content": [ + { + "files": [ + "**/*.{md,yml}" + ], + "exclude": [ + "_site/**" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "template": [ + "default", + "modern" + ], + "globalMetadata": { + "_appName": "polyDocs", + "_appTitle": "polyDocs", + "_enableSearch": true, + "pdf": false + } + } +} diff --git a/docs/gld/Improvement.yml b/docs/gld/Improvement.yml new file mode 100644 index 0000000..f8b3c4f --- /dev/null +++ b/docs/gld/Improvement.yml @@ -0,0 +1,109 @@ +### YamlMime:ManagedReference +items: +- uid: GameData.ImprovementData + id: ImprovementData + name: Improvement Data + fullName: Improvement Data + type: Class + summary: | + Defines the properties for a tile improvement. Each improvement is an object within the `improvementData` section, keyed by its unique name. + syntax: + content: '"improvementData": { "your_improvement_name": { ... } }' + children: + - GameData.ImprovementData.cost + - GameData.ImprovementData.rewards + - GameData.ImprovementData.terrainRequirements + - GameData.ImprovementData.adjacencyRequirements + - GameData.ImprovementData.improvementAbilities + - GameData.ImprovementData.creates + - GameData.ImprovementData.maxLevel + - GameData.ImprovementData.growthRewards + - GameData.ImprovementData.idx + +- uid: GameData.ImprovementData.cost + id: cost + parent: GameData.ImprovementData + name: cost + fullName: cost + type: Property + summary: An integer for the star cost to build the improvement. + syntax: + content: '"cost": 5' +- uid: GameData.ImprovementData.rewards + id: rewards + parent: GameData.ImprovementData + name: rewards + fullName: rewards + type: Property + summary: An array of objects defining the immediate rewards for building the improvement (e.g., population or currency). + syntax: + content: '"rewards": [{ "population": 2 }]' +- uid: GameData.ImprovementData.terrainRequirements + id: terrainRequirements + parent: GameData.ImprovementData + name: terrainRequirements + fullName: terrainRequirements + type: Property + summary: An array of objects specifying where the improvement can be built. Can require a specific `terrain` or `resource`. + syntax: + content: '"terrainRequirements": [{ "resource": "crop" }]' +- uid: GameData.ImprovementData.adjacencyRequirements + id: adjacencyRequirements + parent: GameData.ImprovementData + name: adjacencyRequirements + fullName: adjacencyRequirements + type: Property + summary: An array of objects specifying what must be adjacent for this improvement to be built (e.g., a Forge requires a Mine). + syntax: + content: '"adjacencyRequirements": [{ "improvement": "mine" }]' +- uid: GameData.ImprovementData.improvementAbilities + id: improvementAbilities + parent: GameData.ImprovementData + name: improvementAbilities + fullName: improvementAbilities + type: Property + summary: | + An array of strings for special properties. Possible values include: + - `consumed` - The improvement action can only be done once and removes the resource (e.g., `harvestfruit`). + - `limited` - Only one can be built per city. + - `unique` - Only one can be built per player. + - `patina` - The improvement levels up over time. + - `network` - Connects to other network improvements. + syntax: + content: '"improvementAbilities": ["consumed"]' +- uid: GameData.ImprovementData.creates + id: creates + parent: GameData.ImprovementData + name: creates + fullName: creates + type: Property + summary: An array of objects defining what is created after the action is complete. Can create a `terrain`, `resource`, or `unit`. + syntax: + content: '"creates": [{ "terrain": "field" }]' +- uid: GameData.ImprovementData.maxLevel + id: maxLevel + parent: GameData.ImprovementData + name: maxLevel + fullName: maxLevel + type: Property + summary: An integer for the maximum level the improvement can reach. 0 means it does not level up. + syntax: + content: '"maxLevel": 4' +- uid: GameData.ImprovementData.growthRewards + id: growthRewards + parent: GameData.ImprovementData + name: growthRewards + fullName: growthRewards + type: Property + summary: An array of objects defining the rewards gained each time the improvement levels up (e.g., population or score). + syntax: + content: '"growthRewards": [{ "score": 50, "population": 0 }]' +- uid: GameData.ImprovementData.idx + id: idx + parent: GameData.ImprovementData + name: idx + fullName: idx + type: Property + summary: A unique integer index for the improvement. Must be unique across all improvements. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates. + syntax: + content: '"idx": 5' diff --git a/docs/gld/MinorDatas.md b/docs/gld/MinorDatas.md new file mode 100644 index 0000000..df74a6a --- /dev/null +++ b/docs/gld/MinorDatas.md @@ -0,0 +1,130 @@ +# Minor Data Structures + +This page documents the smaller data structures found in the game's data file, including terrain, resources, tasks, skins, and diplomacy settings. + +--- + +## Terrain Data (`terrainData`) + +Defines all possible terrain types in the game. Each terrain is an object within the `terrainData` section, keyed by its name. To add a new terrain, simply add a new entry with a unique name and `idx`. + +### Syntax + +```json +"terrainData": { + "water": { + "idx": 1 + }, + "field": { + "idx": 3 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| `idx` | Integer | A unique integer index for the terrain type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Resource Data (`resourceData`) + +Defines all possible resource types that can spawn on the map. + +### Syntax + +```json +"resourceData": { + "game": { + "resourceTerrainRequirements": [ + "forest" + ], + "idx": 1 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------- | +| `resourceTerrainRequirements` | Array of Strings | An array of terrain IDs where this resource can naturally spawn. | `["forest"]` | +| `idx` | Integer | A unique integer index for the resource type. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Task Data (`taskData`) + +Defines special in-game achievements or "tasks" that unlock unique monuments upon completion. + +### Syntax + +```json +"taskData": { + "pacifist": { + "improvementUnlocks": [ + "monument1" + ], + "idx": 1 + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------------------- | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- | +| `improvementUnlocks` | Array of Strings | An array of improvement IDs (usually a monument) unlocked when this task is completed. | `["monument1"]` | +| `idx` | Integer | A unique integer index for the task. When adding a new entry, it is recommended to set this value to **-1** to ensure uniqueness and prevent conflicts with game updates. | `"idx": 1` | + +--- + +## Skin Data (`skinData`) + +Defines alternate appearances (skins) for tribes. A tribe must list the skin's key in its `skins` array to use it. + +### Syntax + +```json +"skinData": { + "swamp" : { + "color" : 6786096, + "language": "bub,ly,sq,ee,to,ad" + } +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------- | :------ | :---------------------------------------------------------------------------------- | :----------------------------------- | +| `color` | Integer | An integer representing the skin's primary color, overriding the tribe's default. | `"color": 6786096` | +| `language` | String | A comma-separated string of syllables, overriding the tribe's default language. | `"language": "bub,ly,sq,ee,to,ad"` | + +--- + +## Diplomacy Data (`diplomacyData`) + +Contains key-value pairs that define the global game mechanics for diplomacy, such as embassies. + +### Syntax + +```json +"diplomacyData": { + "embassyCost" : 5, + "embassyIncome" : 2, + "embassyMaxLevel" : 3, + "embassyUpgradeCost" : 20 +} +``` + +### Properties + +| Property | Type | Description | Example Value | +| :--------------------- | :------ | :---------------------------------------------------------------- | :--------------------- | +| `embassyCost` | Integer | The base star cost to build an embassy in another player's capital. | `"embassyCost": 5` | +| `embassyIncome` | Integer | The number of stars an embassy generates per turn for its owner. | `"embassyIncome": 2` | +| `embassyMaxLevel` | Integer | The maximum level an embassy can be upgraded to. | `"embassyMaxLevel": 3` | +| `embassyUpgradeCost` | Integer | The star cost to upgrade an embassy to the next level. | `"embassyUpgradeCost": 20` | diff --git a/docs/gld/Technology.yml b/docs/gld/Technology.yml new file mode 100644 index 0000000..fee1dbe --- /dev/null +++ b/docs/gld/Technology.yml @@ -0,0 +1,95 @@ +### YamlMime:ManagedReference +items: +- uid: GameData.TechData + id: TechData + name: Tech Data + fullName: Tech Data + type: Class + summary: | + Defines the properties for a technology in the tech tree. Each tech is an object within the `techData` section, keyed by its unique name. + + To add a new tech, create a new entry with a unique key and define its properties. You must also link it from another tech's `techUnlocks` property to make it accessible. + syntax: + content: '"techData": { "your_tech_name": { ... } }' + children: + - GameData.TechData.techUnlocks + - GameData.TechData.unitUnlocks + - GameData.TechData.improvementUnlocks + - GameData.TechData.abilityUnlocks + - GameData.TechData.movementUnlocks + - GameData.TechData.defenceBonusUnlocks + - GameData.TechData.cost + - GameData.TechData.idx + +- uid: GameData.TechData.techUnlocks + id: techUnlocks + parent: GameData.TechData + name: techUnlocks + fullName: techUnlocks + type: Property + summary: An array of strings listing the tech IDs that become available after researching this one. This defines the tech tree's structure. + syntax: + content: '"techUnlocks": ["roads", "freespirit"]' +- uid: GameData.TechData.unitUnlocks + id: unitUnlocks + parent: GameData.TechData + name: unitUnlocks + fullName: unitUnlocks + type: Property + summary: An array of strings listing the unit IDs that can be trained after researching this tech. + syntax: + content: '"unitUnlocks": ["rider"]' +- uid: GameData.TechData.improvementUnlocks + id: improvementUnlocks + parent: GameData.TechData + name: improvementUnlocks + fullName: improvementUnlocks + type: Property + summary: An array of strings listing the improvement IDs that can be built after researching this tech. + syntax: + content: '"improvementUnlocks": ["road", "bridge"]' +- uid: GameData.TechData.abilityUnlocks + id: abilityUnlocks + parent: GameData.TechData + name: abilityUnlocks + fullName: abilityUnlocks + type: Property + summary: An array of strings listing new player or unit actions unlocked by this tech (e.g., "disband", "destroy"). + syntax: + content: '"abilityUnlocks": ["destroy"]' +- uid: GameData.TechData.movementUnlocks + id: movementUnlocks + parent: GameData.TechData + name: movementUnlocks + fullName: movementUnlocks + type: Property + summary: An object where keys are terrain IDs and values are the new movement cost for that terrain. + syntax: + content: '"movementUnlocks": { "mountain": 2 }' +- uid: GameData.TechData.defenceBonusUnlocks + id: defenceBonusUnlocks + parent: GameData.TechData + name: defenceBonusUnlocks + fullName: defenceBonusUnlocks + type: Property + summary: An object where keys are terrain IDs and values are the defense bonus percentage (e.g., 15 for +15%) for units on that terrain. + syntax: + content: '"defenceBonusUnlocks": { "mountain": 15 }' +- uid: GameData.TechData.cost + id: cost + parent: GameData.TechData + name: cost + fullName: cost + type: Property + summary: An integer representing the base star cost to research this technology. + syntax: + content: '"cost": 1' +- uid: GameData.TechData.idx + id: idx + parent: GameData.TechData + name: idx + fullName: idx + type: Property + summary: A unique integer index for the tech. Must be unique across all technologies. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates. + syntax: + content: '"idx": 1' diff --git a/docs/gld/Tribe.yml b/docs/gld/Tribe.yml new file mode 100644 index 0000000..d585ad0 --- /dev/null +++ b/docs/gld/Tribe.yml @@ -0,0 +1,195 @@ +### YamlMime:ManagedReference +items: +- uid: GameData.TribeData + id: TribeData + name: Tribe Data + fullName: Tribe Data + type: Class + summary: | + Defines the properties for a tribe. Each tribe is an object within the `tribeData` section of the JSON file, keyed by its unique name (e.g., "aimo", "bardur"). + + To add a new tribe, create a new entry with a unique key and define its properties as described below. + syntax: + content: '"tribeData": { "your_tribe_name": { ... } }' + children: + - GameData.TribeData.color + - GameData.TribeData.style + - GameData.TribeData.climate + - GameData.TribeData.language + - GameData.TribeData.startingTech + - GameData.TribeData.startingResource + - GameData.TribeData.startingUnit + - GameData.TribeData.priceTier + - GameData.TribeData.category + - GameData.TribeData.bonus + - GameData.TribeData.terrainModifier + - GameData.TribeData.resourceModifier + - GameData.TribeData.unitOverrides + - GameData.TribeData.techOverrides + - GameData.TribeData.improvementOverrides + - GameData.TribeData.tribeAbilities + - GameData.TribeData.skins + - GameData.TribeData.idx + +- uid: GameData.TribeData.color + id: color + parent: GameData.TribeData + name: color + fullName: color + type: Property + summary: An integer representing the tribe's primary color. This is often a decimal representation of a hex color code. + syntax: + content: '"color": 3596970' +- uid: GameData.TribeData.style + id: style + parent: GameData.TribeData + name: style + fullName: style + type: Property + summary: An integer ID that determines the visual style of the tribe's cities and units. + syntax: + content: '"style": 10' +- uid: GameData.TribeData.climate + id: climate + parent: GameData.TribeData + name: climate + fullName: climate + type: Property + summary: An integer ID that determines the environmental look and feel, including terrain colors and vegetation. + syntax: + content: '"climate": 10' +- uid: GameData.TribeData.language + id: language + parent: GameData.TribeData + name: language + fullName: language + type: Property + summary: A comma-separated string of syllables used to generate names and sounds for the tribe. + syntax: + content: '"language": "gu,rø,lak,bu,gru,tof,fla,ork,lin,ark,ur"' +- uid: GameData.TribeData.startingTech + id: startingTech + parent: GameData.TribeData + name: startingTech + fullName: startingTech + type: Property + summary: An array of strings listing the tech IDs the tribe begins the game with. "basic" is required for all tribes. + syntax: + content: '"startingTech": ["basic", "meditation"]' +- uid: GameData.TribeData.startingResource + id: startingResource + parent: GameData.TribeData + name: startingResource + fullName: startingResource + type: Property + summary: An array of strings listing the resource IDs guaranteed to spawn near the tribe's starting city. + syntax: + content: '"startingResource": ["game"]' +- uid: GameData.TribeData.startingUnit + id: startingUnit + parent: GameData.TribeData + name: startingUnit + fullName: startingUnit + type: Property + summary: The string ID of the unit the tribe starts with (e.g., "warrior", "rider"). + syntax: + content: '"startingUnit": "warrior"' +- uid: GameData.TribeData.priceTier + id: priceTier + parent: GameData.TribeData + name: priceTier + fullName: priceTier + type: Property + summary: An integer (0-3) determining the tribe's cost in the selection screen. 0 is free, 1-3 correspond to increasing costs. + syntax: + content: '"priceTier": 1' +- uid: GameData.TribeData.category + id: category + parent: GameData.TribeData + name: category + fullName: category + type: Property + summary: An integer ID for grouping tribes. (e.g., 1 for Normal, 2 for Special). + syntax: + content: '"category": 1' +- uid: GameData.TribeData.bonus + id: bonus + parent: GameData.TribeData + name: bonus + fullName: bonus + type: Property + summary: An integer that determines the tribe's starting star bonus. 0 means no bonus, 1 gives a larger capital and more starting stars (e.g., Luxidoor). + syntax: + content: '"bonus": 0' +- uid: GameData.TribeData.terrainModifier + id: terrainModifier + parent: GameData.TribeData + name: terrainModifier + fullName: terrainModifier + type: Property + summary: An object where keys are terrain IDs (e.g., "mountain") and values are numeric multipliers for their spawn rate. >1 increases spawn rate, <1 decreases it. + syntax: + content: '"terrainModifier": { "mountain": 1.5 }' +- uid: GameData.TribeData.resourceModifier + id: resourceModifier + parent: GameData.TribeData + name: resourceModifier + fullName: resourceModifier + type: Property + summary: An object where keys are resource IDs (e.g., "crop") and values are numeric multipliers for their spawn rate. >1 increases spawn rate, <1 decreases it. A value of 0 prevents the resource from spawning. + syntax: + content: '"resourceModifier": { "crop": 0.1 }' +- uid: GameData.TribeData.unitOverrides + id: unitOverrides + parent: GameData.TribeData + name: unitOverrides + fullName: unitOverrides + type: Property + summary: An object that replaces standard units with tribe-specific ones. The key is the base unit ID (e.g., "giant"), and the value is the replacement unit ID (e.g., "crab"). + syntax: + content: '"unitOverrides": { "giant": "crab" }' +- uid: GameData.TribeData.techOverrides + id: techOverrides + parent: GameData.TribeData + name: techOverrides + fullName: techOverrides + type: Property + summary: An object that replaces standard technologies with tribe-specific ones. The key is the base tech ID (e.g., "sailing"), and the value is the replacement tech ID (e.g., "marinelife"). + syntax: + content: '"techOverrides": { "sailing": "marinelife" }' +- uid: GameData.TribeData.improvementOverrides + id: improvementOverrides + parent: GameData.TribeData + name: improvementOverrides + fullName: improvementOverrides + type: Property + summary: An object that replaces standard improvements with tribe-specific ones. The key is the base improvement ID (e.g., "burnforest"), and the value is the replacement ID or "none" to disable it. + syntax: + content: '"improvementOverrides": { "burnforest": "none" }' +- uid: GameData.TribeData.tribeAbilities + id: tribeAbilities + parent: GameData.TribeData + name: tribeAbilities + fullName: tribeAbilities + type: Property + summary: An array of strings representing special abilities unique to the tribe, such as "poisonresist" or "freeze". + syntax: + content: '"tribeAbilities": ["poisonresist"]' +- uid: GameData.TribeData.skins + id: skins + parent: GameData.TribeData + name: skins + fullName: skins + type: Property + summary: An array of string IDs referencing skins from `skinData` that are available for this tribe. + syntax: + content: '"skins":["Aibo"]' +- uid: GameData.TribeData.idx + id: idx + parent: GameData.TribeData + name: idx + fullName: idx + type: Property + summary: A unique integer index for the tribe. Must be unique across all tribes. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates. + syntax: + content: '"idx": 2' diff --git a/docs/gld/Unit.yml b/docs/gld/Unit.yml new file mode 100644 index 0000000..1d4e117 --- /dev/null +++ b/docs/gld/Unit.yml @@ -0,0 +1,138 @@ +### YamlMime:ManagedReference +items: +- uid: GameData.UnitData + id: UnitData + name: Unit Data + fullName: Unit Data + type: Class + summary: | + Defines the properties for a unit. Each unit is an object within the `unitData` section of the JSON file, keyed by its unique name (e.g., "warrior", "knight"). + + To add a new unit, create a new entry with a unique key and define its properties as described below. + syntax: + content: '"unitData": { "your_unit_name": { ... } }' + children: + - GameData.UnitData.health + - GameData.UnitData.defence + - GameData.UnitData.attack + - GameData.UnitData.movement + - GameData.UnitData.range + - GameData.UnitData.cost + - GameData.UnitData.unitAbilities + - GameData.UnitData.hidden + - GameData.UnitData.upgradesFrom + - GameData.UnitData.promotionLimit + - GameData.UnitData.idx + +- uid: GameData.UnitData.health + id: health + parent: GameData.UnitData + name: health + fullName: health + type: Property + summary: An integer representing the unit's maximum health points. + syntax: + content: '"health": 100' +- uid: GameData.UnitData.defence + id: defence + parent: GameData.UnitData + name: defence + fullName: defence + type: Property + summary: An integer representing the unit's defense stat. + syntax: + content: '"defence": 20' +- uid: GameData.UnitData.attack + id: attack + parent: GameData.UnitData + name: attack + fullName: attack + type: Property + summary: An integer representing the unit's attack stat. + syntax: + content: '"attack": 20' +- uid: GameData.UnitData.movement + id: movement + parent: GameData.UnitData + name: movement + fullName: movement + type: Property + summary: An integer for the number of tiles the unit can move per turn. + syntax: + content: '"movement": 1' +- uid: GameData.UnitData.range + id: range + parent: GameData.UnitData + name: range + fullName: range + type: Property + summary: An integer for the unit's attack range in tiles. A value of 1 means melee. + syntax: + content: '"range": 1' +- uid: GameData.UnitData.cost + id: cost + parent: GameData.UnitData + name: cost + fullName: cost + type: Property + summary: The number of stars required to train this unit. A cost of 0 often means the unit cannot be trained directly. + syntax: + content: '"cost": 2' +- uid: GameData.UnitData.unitAbilities + id: unitAbilities + parent: GameData.UnitData + name: unitAbilities + fullName: unitAbilities + type: Property + summary: | + An array of strings listing the special abilities of the unit. Possible values include: + - `dash` - Can attack after moving. + - `fortify` - Can gain a defensive bonus by not moving. + - `escape` - Can move after attacking. + - `persist` - Can attack multiple enemies in one turn if the first is destroyed. + - `fly` - Can move over any terrain. + - `swim` - Can move on water tiles. + - `amphibious` - Can move on both land and water tiles. + - `skate` - Can move on ice tiles. + - `creep` - Can ignore enemy zone of control. + - `convert` - Can convert enemy units. + - `heal` - Can heal nearby friendly units. + - `poison` - Applies a damage-over-time effect on attack. + syntax: + content: '"unitAbilities": ["dash", "fortify", "land"]' +- uid: GameData.UnitData.hidden + id: hidden + parent: GameData.UnitData + name: hidden + fullName: hidden + type: Property + summary: A boolean (`true`/`false`) indicating if the unit should be hidden from standard training menus. Often used for summoned units or Giants. + syntax: + content: '"hidden": true' +- uid: GameData.UnitData.upgradesFrom + id: upgradesFrom + parent: GameData.UnitData + name: upgradesFrom + fullName: upgradesFrom + type: Property + summary: A string ID of the unit that this unit upgrades from (e.g., "firedragon" upgrades from "babydragon"). + syntax: + content: '"upgradesFrom": "babydragon"' +- uid: GameData.UnitData.promotionLimit + id: promotionLimit + parent: GameData.UnitData + name: promotionLimit + fullName: promotionLimit + type: Property + summary: An integer for the maximum number of promotions this unit can receive. 0 means it cannot be promoted. + syntax: + content: '"promotionLimit": 3' +- uid: GameData.UnitData.idx + id: idx + parent: GameData.UnitData + name: idx + fullName: idx + type: Property + summary: A unique integer index for the unit. Must be unique across all units. When adding a new entry, it is recommended to set this value to -1 to ensure uniqueness and prevent conflicts with game updates. + syntax: + content: '"idx": 2' diff --git a/docs/gld/index.md b/docs/gld/index.md new file mode 100644 index 0000000..1460a84 --- /dev/null +++ b/docs/gld/index.md @@ -0,0 +1,5 @@ +# Game Data Reference + +Welcome to the game data reference documentation. This section provides a detailed breakdown of the structure of the main JSON data file. Use this reference to understand how to modify existing game elements or create your own custom content, such as new tribes, units, or technologies. + +Each page documents the schema for a top-level section of the JSON file (e.g., `tribeData`, `unitData`). It explains the purpose of each property and the types of values it expects. diff --git a/docs/gld/toc.yml b/docs/gld/toc.yml new file mode 100644 index 0000000..ff104d1 --- /dev/null +++ b/docs/gld/toc.yml @@ -0,0 +1,12 @@ +- name: Game Data Reference + href: index.md +- name: Tribe Data + href: Tribe.yml +- name: Unit Data + href: Unit.yml +- name: Technology Data + href: Technology.yml +- name: Improvement Data + href: Improvement.yml +- name: Minor Data Structures + href: MinorDatas.md diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..6023cdd --- /dev/null +++ b/docs/index.md @@ -0,0 +1,11 @@ +--- +_layout: landing +--- + +# This is the **HOMEPAGE**. + +Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. + +## Quick Start Notes: + +1. Add images to the *images* folder if the file is referencing an image. \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..d0885f7 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,6 @@ +- name: Docs + href: articles/ +- name: API + href: api/ +- name: GLD + href: gld/ From ced36efca13a4ec3c349c08751c2a3e74d8b8f45 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Tue, 19 Aug 2025 16:13:40 +0200 Subject: [PATCH 18/26] add gh actions workflow to publish to pages --- .github/workflows/pages.yml | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..ea55660 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,43 @@ +on: + push: + branches: + - main + - documentation-tm # for testing + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + actions: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + publish-docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Dotnet Setup + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.x + + - run: dotnet tool update -g docfx + - run: docfx docs/docfx.json + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'docs/_site' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From b58638b0b225459a1444c01052617b99785be7ec Mon Sep 17 00:00:00 2001 From: joukepouke <73423163+joukepouke@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:14:57 +0200 Subject: [PATCH 19/26] Create pages.yml --- .github/workflows/pages.yml | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..ea55660 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,43 @@ +on: + push: + branches: + - main + - documentation-tm # for testing + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + actions: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + publish-docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Dotnet Setup + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.x + + - run: dotnet tool update -g docfx + - run: docfx docs/docfx.json + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'docs/_site' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From c710b8411a9056404d833f186250ef3d06f14933 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Wed, 20 Aug 2025 14:23:10 +0200 Subject: [PATCH 20/26] add docs-tm --- docs/articles/getting-started.md | 1 - docs/articles/introduction.md | 8 +- docs/articles/modding/gld.md | 189 ++++++++++++++++++++++++ docs/articles/modding/index.md | 0 docs/articles/modding/localization.md | 24 +++ docs/articles/modding/polyscript.md | 1 + docs/articles/modding/sprites.md | 86 +++++++++++ docs/articles/modding/toc.yml | 11 ++ docs/articles/modding/your-first-mod.md | 47 ++++++ docs/articles/toc.yml | 6 +- docs/articles/using/adding-mods.md | 1 + docs/articles/using/configure.md | 1 + docs/articles/using/installing.md | 12 ++ docs/articles/using/toc.yml | 6 + src/Loader.cs | 36 ++--- src/Managers/General.cs | 31 ++++ src/{Managers => Patches}/Hub.cs | 0 src/{Managers => Patches}/Loc.cs | 2 +- src/{Managers => Patches}/Visual.cs | 2 +- src/Registry.cs | 18 +-- 20 files changed, 443 insertions(+), 39 deletions(-) delete mode 100644 docs/articles/getting-started.md create mode 100644 docs/articles/modding/gld.md create mode 100644 docs/articles/modding/index.md create mode 100644 docs/articles/modding/localization.md create mode 100644 docs/articles/modding/polyscript.md create mode 100644 docs/articles/modding/sprites.md create mode 100644 docs/articles/modding/toc.yml create mode 100644 docs/articles/modding/your-first-mod.md create mode 100644 docs/articles/using/adding-mods.md create mode 100644 docs/articles/using/configure.md create mode 100644 docs/articles/using/installing.md create mode 100644 docs/articles/using/toc.yml create mode 100644 src/Managers/General.cs rename src/{Managers => Patches}/Hub.cs (100%) rename src/{Managers => Patches}/Loc.cs (99%) rename src/{Managers => Patches}/Visual.cs (99%) diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md deleted file mode 100644 index 8b3a794..0000000 --- a/docs/articles/getting-started.md +++ /dev/null @@ -1 +0,0 @@ -# Getting Started \ No newline at end of file diff --git a/docs/articles/introduction.md b/docs/articles/introduction.md index f6ecaa6..9aabc00 100644 --- a/docs/articles/introduction.md +++ b/docs/articles/introduction.md @@ -1 +1,7 @@ -# Introduction \ No newline at end of file +# Introduction + +Polymod is the modding framework for The Battle Of Polytopia. + +If you would like information about how to **develop** mods, go to [modding](modding/your-first-mod.md). + +If you would like information about how to use and configure PolyMod and its mods, go to [using](using/installing.md) \ No newline at end of file diff --git a/docs/articles/modding/gld.md b/docs/articles/modding/gld.md new file mode 100644 index 0000000..4a16d88 --- /dev/null +++ b/docs/articles/modding/gld.md @@ -0,0 +1,189 @@ +# Gld Patching + +## Rules +The rules that PolyMod uses to merge patches into the gld are: +- If an array exists in both the patch and the gld, the array will be **completely replaced**, it will not be merged. This can be useful to remove items from an array. +- If a value is `null` in the patch, it will be removed from the gld. + +## The basics +The `patch.json` file is essentially a mini GLD that includes only the values you want to change and nothing else. If you want to make a mod that just changes a `warrior`'s attack, for example… +```json +{ + "unitData": { + "warrior": { + "attack": 1000 + } + } +} +``` + +As you can see, this uses the exact same keys as the GLD file, but excludes everything that's unnecessary for the mod. + +## Adding new content +Well, adding new content is as simple as editing existing content! You just have to pretend that something exists, and then it will exist: you need to set its `idx` to `-1` for it to work. This will make PolyMod automatically assign an `idx` when the game starts (autoindex). +> [!WARNING] +> Manually setting `idx` to something other than `-1` breaks mod compatibility! + +Additionally, make sure that all the internal names you use are in lowercase. PolyMod will crash otherwise. +Here's part of an example gld patch which adds new tribe: +```json +{ + "tribeData": { + "testingtribe": { + "color":15713321, + "language":"az,bar,bryn,dûm,girt,hall,kar,khâz,kol,kruk,lok,rûdh,ruf,und,vorn,zak", + "startingTech":[ + "basic", + "mining" + ], + "startingResource":[ + "metal" + ], + "skins":[ + "Testingskin" + ], + "priceTier":0, + "category":2, + "bonus":0, + "startingUnit":"warrior", + "idx":-1, + "preview": [ + { + "x": 0, + "y": 0, + "terrainType": "mountain", + "unitType": "swordsman", + "improvementType ": "mine" + } + ] + } + } +} +``` + +## Custom Tribe Preview +As you could have noticed, our testingtribe has a field `preview` which does not exist in GLD. This field was added in order to modify tribe previews if needed. +```json +{ + "preview": [ + { + "x": 0, + "y": 0, + "terrainType": "mountain", + "unitType": "swordsman", + "improvementType ": "mine" + } + ] +} +``` +Each tile of preview consists of: +* `x` X coordinate of the tile in the preview +* `y` Y coordinate of the tile in the preview +* `terrainType` terrain of the original tile will be replaced with the one you choose here +* `resourceType` resource of the original tile will be replaced with the one you choose here +* `unitType` unit of the original tile will be replaced with the one you choose here +* `improvementType` improvement of the original tile will be replaced with the one you choose here + +Based on that, our chosen preview tile will have `mountain`, `swordsman` and `mine`. +You can see all tiles and their coordinates in Tribe Preview by enabling PolyMod debug mode in `PolyMod.json` + +## Custom Skins +Also, our tribe has a non-existing skin. By writing such, PolyMod will create a skin automatically: +```json +{ + "skins": [ + "Testingskin" + ] +} +``` + +## Prefabs +Let's look at this patch which adds new unit: +```json +{ + "unitData": { + "dashbender": { + "health": 100, + "defence": 10, + "movement": 1, + "range": 1, + "attack": 0, + "cost": 15, + "unitAbilities": [ + "dash", + "convert", + "stiff", + "land" + ], + "weapon": 4, + "promotionLimit": 3, + "idx": -1, + "prefab": "mindbender" + } + } +} +``` + +By default, when creating a new unit, improvement or resource PolyMod will set basic sprites for them, such as: + +* New **Unit** have explorer's sprites by default +* New **Improvement**s have custom house's sprites by default +* New **Resource**s have animal's sprites by default + +If you want to change it to another already existing type, you can do just what we did for `dashbender`: +```json +{ + "prefab": "mindbender" +} +``` +That sets `mindbender`'s sprites as our base sprites for `dashbender`. + +## Config +Say that, in your mod, you created a new unit, but you aren't a balancing expert and thus you want the user to be able to configure how expensive it is. Before, you would need polyscript to do this. However, in polymod 1.2 there is a new feature that allows you to have configurable options in gld patches! +Say that, for example, you wanted to change the warrior's cost to whatever the user wants. +you can use `{{ config key defaultValue}}`. +``` +{ + "unitData" : { + "warrior" : { + "cost" : {{ config "warriorCost" 5 }} + } + } +} +``` +but what if you want to disable or enable a unit based on config? For that, you need to do more advanced templating. Here is an example that gives ai-mo a dashbender if dashbenders are enabled, otherwise a mindbender. In reality you will also need to modify tech etc. +``` +{ + "unitData": { + {% if config "dashbenders" true %} + "dashbender": { + "health": 100, + "defence": 10, + "movement": 1, + "range": 1, + "attack": 0, + "cost": 15, + "unitAbilities": [ + "dash", + "convert", + "stiff", + "land" + ], + "weapon": 4, + "promotionLimit": 3, + "idx": -1, + "prefab": "mindbender" + } + } + {% aimo-starting = "dashbender" %} + {% else %} + {% aimo-starting = "mindbender" %} + {% end %} + "tribeData":{ + "ai-mo":{ + "startingUnit": "{{ aimo-starting }}" + } + } +} +``` +For a full list of templates, see [Scriban docs](https://github.com/scriban/scriban/blob/master/doc/language.md). \ No newline at end of file diff --git a/docs/articles/modding/index.md b/docs/articles/modding/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/articles/modding/localization.md b/docs/articles/modding/localization.md new file mode 100644 index 0000000..d54c09a --- /dev/null +++ b/docs/articles/modding/localization.md @@ -0,0 +1,24 @@ +# Localization +Localization is a json file in a mod that declares mod's localization strings. Mod localization is declared in `localization.json` file. + +If you've ever used the custom language system before it was removed, you'll notice that the keys follow the same format as custom langs, but **with all the periods replaced with underscores**. +If you want to translate your strings to other languages, you can simply use a comma and enter another language's name with its corresponding string. + +### Languages +* `English` +* `Portuguese (Brazil)` +* `Russian` +* `Italian (Italy)` +* `French (France)` +* `Spanish (Mexico)` +* `German (Germany)` +* `Japanese` +* `Korean` +* `Hindi` +* `Indonesian` +* `Malay` +* `Polish` +* `Thai` +* `Turkish` +* `Vietnamese` +* `Elyrion` diff --git a/docs/articles/modding/polyscript.md b/docs/articles/modding/polyscript.md new file mode 100644 index 0000000..30404ce --- /dev/null +++ b/docs/articles/modding/polyscript.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/docs/articles/modding/sprites.md b/docs/articles/modding/sprites.md new file mode 100644 index 0000000..6ca9c5d --- /dev/null +++ b/docs/articles/modding/sprites.md @@ -0,0 +1,86 @@ +# Texture +A texture is an image file in png format, which is being loaded by PolyMod in order to be used ingame. + +PolyMod decides how to load the texture based on filename. Texture's file name consists of `name`, `style` and `level`. +* `name` id of the texture +* `style` tribe or skin id, for which this texture should be set +* `level` level of the texture + +### Format +Here is all possible combinations of how you can name the texture file: +* `name__.png` will replace the texture of chosen target for all tribes, skins and possible levels +* `name_style_.png` will replace the texture of chosen target for chosen tribe or skin and for all possible levels +* `name__level.png` will replace the texture of chosen target for all tribes and skins, but for chosen level +* `name_style_level.png` will replace the texture of chosen target for chosen tribe or skin and for chosen level + +### Example +You want to replace all lumberhut textures for all tribes. +* We want to replace it for **all** tribes and skins, so we dont specify the style. +* Lumber hut has only one level, which means we dont want to specify it. + In such case, you should name it as `lumberhut__.png` + +# Sprites +Sprites file is a json file which declares advanced settings for how each texture should be transformed into the sprite. Mod sprites is declared in `sprites.json` file. + +### Format +* `pixelsPerUnit` _(optional, default `2112`)_ in Unity, a **sprite PPU** is a property that determines how many pixels from a sprite texture correspond to one unit in the Unity game world. + +* `pivot` _(optional, default `[0.5, 0.5]`)_ in Unity, a **sprite pivot** is the reference point that determines how a sprite is positioned within the Unity game world. It acts as the point relative to which happen all movements, rotation and scaling of the sprite. + +> [!TIP] +> You can find more info in [Unity Documentation](https://docs.unity.com/). + +### Example +```json +{ + "lumberhut__": { + "pixelsPerUnit": 256, + "pivot": [0.1, 0.5] + } +} +``` + +# Prefab +A prefab is a json file in a mod that describes a unit prefab. Prefabs are declared in `prefab_name.json` files (name is your prefab name). + +### Format +* `type` int value of the type of a prefab. Here is all current prefab types: +``` +Unit, +Improvement, +Resource +``` +* `name` name of your prefab +* `visualParts` array of prfab's visual parts + * `gameObjectName` name of the GameObject of the visual part + * `baseName` sprite name of the visual part + * `coordinates` position of the visual part + * `rotation` _(optional, default `0`)_ rotation of the visual part + * `scale` scale of the visual part + * `tintable` _(optional, default `false`)_ is visual part a tint + +### Example +```json +{ + "type": 0, + "name": "Yuukwi", + "visualParts": [ + { + "gameObjectName": "Body", + "baseName": "body", + "coordinates": [0, 0.2], + "rotation": 180, + "scale": [1, 1], + "tintable": false + }, + { + "gameObjectName": "Body_Tint", + "baseName": "body_tint", + "coordinates": [0, 0.2], + "rotation": 90, + "scale": [1, 1], + "tintable": true + } + ] +} +``` \ No newline at end of file diff --git a/docs/articles/modding/toc.yml b/docs/articles/modding/toc.yml new file mode 100644 index 0000000..c61c3a1 --- /dev/null +++ b/docs/articles/modding/toc.yml @@ -0,0 +1,11 @@ +- name: Your first mod + href: your-first-mod.md +- name: Gld Patching + href: gld.md +- name: Sprites + href: sprites.md +- name: Localization + href: localization.md +- name: Polyscript + href: polyscript.md + \ No newline at end of file diff --git a/docs/articles/modding/your-first-mod.md b/docs/articles/modding/your-first-mod.md new file mode 100644 index 0000000..5c3f9e2 --- /dev/null +++ b/docs/articles/modding/your-first-mod.md @@ -0,0 +1,47 @@ +# Creating your first mod + +## Step 1: Setting up your environment +Before you start writing mods, you should ensure you have the following: +- A text editor. Notepad or Text Editor will be enough. +> [!TIP] +> Using a text editor meant for writing code, like [vscode](https://code.visualstudio.com/) or Kate(preinstalled on most linux distros) is a lot easier when writing and editing code. However, this isn't required. + +[Install PolyMod.](../using/installing.md) +Inside your mods folder, create a folder, e.g. `example`. If you have an IDE, open that folder with your IDE. Else, open the folder with file manager(or finder, or dolphin etc.) + +## Step 2: Writing the manifest +Create `manifest.json` and paste the following into it: +```json +{ + "id" : "my_first_mod", + "name": "My first mod", + "version": "1.0.0", + "authors": ["your-name"], + "dependencies": [ + { + "id": "polytopia" + } + ] +} +``` + +## Step 3: Creating a patch +Create `patch.json` and paste the following into it: +``` +{ + "unitData" : { + "warrior" : { + "attack" : 100 + } + } +} +``` +This will make the warrior have a large attack value. For more information about GLD patching, see (gld.md) + +## Step 4: loading the mod +Start the game. Thats it! You should see your mod listed under the polymod hub. + +## Step 5: publishing your mod +Once you have finished your mod, you may want to bundle it into a single file for easier sharing. Here's how to do that: +1. zip the folder of the mod +2. rename the zipfile to `example.polymod` \ No newline at end of file diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index d3663b9..8cedef0 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -1,4 +1,6 @@ - name: Introduction href: introduction.md -- name: Getting Started - href: getting-started.md \ No newline at end of file +- name: Modding + href: modding/toc.yml +- name: Using + href: using/toc.yml diff --git a/docs/articles/using/adding-mods.md b/docs/articles/using/adding-mods.md new file mode 100644 index 0000000..9936734 --- /dev/null +++ b/docs/articles/using/adding-mods.md @@ -0,0 +1 @@ +go to steam path and go into mods folder and add mod there \ No newline at end of file diff --git a/docs/articles/using/configure.md b/docs/articles/using/configure.md new file mode 100644 index 0000000..78f9a75 --- /dev/null +++ b/docs/articles/using/configure.md @@ -0,0 +1 @@ +edit mods.json in ur polytopia folder \ No newline at end of file diff --git a/docs/articles/using/installing.md b/docs/articles/using/installing.md new file mode 100644 index 0000000..82cd82c --- /dev/null +++ b/docs/articles/using/installing.md @@ -0,0 +1,12 @@ +# Installing PolyMod + +Choose your OS: // TODO + +## Windows +Download the installer and launch it. Locate your steam directory and click install. + +## Linux +TODO + +## MacOS +// idk \ No newline at end of file diff --git a/docs/articles/using/toc.yml b/docs/articles/using/toc.yml new file mode 100644 index 0000000..dc7a19a --- /dev/null +++ b/docs/articles/using/toc.yml @@ -0,0 +1,6 @@ +- name: Installing + href: installing.md +- name: Adding Mods + href: adding-mods.md +- name: Configure + href: configure.md diff --git a/src/Loader.cs b/src/Loader.cs index 9c3e087..eae5102 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -133,20 +133,8 @@ public static class Loader }; public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite); - - public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite) - { - EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx); - EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx); - gamemodes.Add(new GameModeButtonsInformation(Registry.gameModesAutoidx, action, null, sprite)); - Registry.gameModesAutoidx++; - } - - public static void AddPatchDataType(string typeId, Type type) - { - if (!typeMappings.ContainsKey(typeId)) - typeMappings.Add(typeId, type); - } + + internal static bool RegisterMods(Dictionary mods) { @@ -428,7 +416,7 @@ private static bool SortMods(Dictionary mods) return true; } - public static void LoadAssemblyFile(Mod mod, Mod.File file) + internal static void LoadAssemblyFile(Mod mod, Mod.File file) { try { @@ -472,7 +460,7 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file) } } - public static void LoadLocalizationFile(Mod mod, Mod.File file) + internal static void LoadLocalizationFile(Mod mod, Mod.File file) { try { @@ -486,7 +474,7 @@ public static void LoadLocalizationFile(Mod mod, Mod.File file) } } - public static void LoadSpriteFile(Mod mod, Mod.File file) + internal static void LoadSpriteFile(Mod mod, Mod.File file) { string name = Path.GetFileNameWithoutExtension(file.name); Vector2 pivot = name.Split("_")[0] switch @@ -508,7 +496,7 @@ public static void LoadSpriteFile(Mod mod, Mod.File file) Registry.sprites.Add(name, sprite); } - public static void UpdateSprite(string name) + internal static void UpdateSprite(string name) { if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name)) { @@ -523,7 +511,7 @@ public static void UpdateSprite(string name) } } - public static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file) + internal static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file) { try { @@ -553,7 +541,7 @@ public static void UpdateSprite(string name) } } - public static void LoadAudioFile(Mod mod, Mod.File file) + internal static void LoadAudioFile(Mod mod, Mod.File file) { // AudioSource audioSource = new GameObject().AddComponent(); // GameObject.DontDestroyOnLoad(audioSource); @@ -562,7 +550,7 @@ public static void LoadAudioFile(Mod mod, Mod.File file) // TODO: issue #71 } - public static void LoadPrefabInfoFile(Mod mod, Mod.File file) + internal static void LoadPrefabInfoFile(Mod mod, Mod.File file) { try { @@ -675,7 +663,7 @@ private static SkinVisualsReference.VisualPart CreateVisualPart( return visualPart; } - public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) + internal static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) { try { @@ -734,7 +722,7 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) } } - public static void LoadAssetBundle(Mod mod, Mod.File file) + internal static void LoadAssetBundle(Mod mod, Mod.File file) { Registry.assetBundles.Add( Path.GetFileNameWithoutExtension(file.name), @@ -742,7 +730,7 @@ public static void LoadAssetBundle(Mod mod, Mod.File file) ); } - public static void HandleSkins(JObject gld, JObject patch) + internal static void HandleSkins(JObject gld, JObject patch) { foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray()) { diff --git a/src/Managers/General.cs b/src/Managers/General.cs new file mode 100644 index 0000000..23c959c --- /dev/null +++ b/src/Managers/General.cs @@ -0,0 +1,31 @@ +using PolytopiaBackendBase.Game; +using UnityEngine; + +namespace PolyMod.Managers; + +public static class General +{ + /// + /// Adds a button to the "select game mode" screen + /// + /// The text to display on the button + /// The action to take when the button is pressed + /// The image on the button. + public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite) + { + EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx); + EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx); + Loader.gamemodes.Add(new Loader.GameModeButtonsInformation(Registry.gameModesAutoidx, action, null, sprite)); + Registry.gameModesAutoidx++; + } + /// + /// Adds a custom type to the gld. + /// + /// + /// + public static void AddPatchDataType(string typeId, Type type) + { + if (!Loader.typeMappings.ContainsKey(typeId)) + Loader.typeMappings.Add(typeId, type); + } +} diff --git a/src/Managers/Hub.cs b/src/Patches/Hub.cs similarity index 100% rename from src/Managers/Hub.cs rename to src/Patches/Hub.cs diff --git a/src/Managers/Loc.cs b/src/Patches/Loc.cs similarity index 99% rename from src/Managers/Loc.cs rename to src/Patches/Loc.cs index 5b1c70f..2287bd8 100644 --- a/src/Managers/Loc.cs +++ b/src/Patches/Loc.cs @@ -7,7 +7,7 @@ namespace PolyMod.Managers; -public static class Loc +internal static class Loc { [HarmonyPostfix] [HarmonyPatch(typeof(SelectTribePopup), nameof(SelectTribePopup.SetDescription))] diff --git a/src/Managers/Visual.cs b/src/Patches/Visual.cs similarity index 99% rename from src/Managers/Visual.cs rename to src/Patches/Visual.cs index 031d26a..d2a9318 100644 --- a/src/Managers/Visual.cs +++ b/src/Patches/Visual.cs @@ -13,7 +13,7 @@ namespace PolyMod.Managers; /// /// Manages visual aspects of the game, including sprites, UI elements, and in-game object appearances. /// -public static class Visual +internal static class Visual { /// /// Represents a single tile in a tribe's world preview. diff --git a/src/Registry.cs b/src/Registry.cs index c95b32a..91cdcb6 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -14,31 +14,31 @@ public static class Registry internal static int autoidx = Plugin.AUTOIDX_STARTS_FROM; /// The registry for custom sprites. - public static Dictionary sprites = new(); + internal static Dictionary sprites = new(); /// The registry for custom audio clips. - public static Dictionary audioClips = new(); + internal static Dictionary audioClips = new(); /// The registry for loaded mods. public static Dictionary mods = new(); /// The registry for custom tribe previews. - public static Dictionary tribePreviews = new(); + internal static Dictionary tribePreviews = new(); /// The registry for custom sprite information. - public static Dictionary spriteInfos = new(); + internal static Dictionary spriteInfos = new(); /// The registry for custom prefab names. - public static Dictionary prefabNames = new(); + internal static Dictionary prefabNames = new(); /// The registry for custom unit prefabs. - public static Dictionary unitPrefabs = new(); + internal static Dictionary unitPrefabs = new(); /// The registry for custom resource prefabs. - public static Dictionary resourcePrefabs = new(); + internal static Dictionary resourcePrefabs = new(); /// The registry for custom improvement prefabs. - public static Dictionary improvementsPrefabs = new(); + internal static Dictionary improvementsPrefabs = new(); /// The registry for loaded asset bundles. public static Dictionary assetBundles = new(); @@ -47,7 +47,7 @@ public static class Registry public static List customTribes = new(); /// The registry for custom skin information. - public static List skinInfo = new(); + internal static List skinInfo = new(); internal static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last(); internal static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length; From 3eeb148490c8e6a10b18297d299bff81a89b80f3 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Wed, 20 Aug 2025 14:38:55 +0200 Subject: [PATCH 21/26] Add some more comments --- src/Constants.cs | 35 +++++++++++++++++++++++++++++++++++ src/Loader.cs | 10 +++++----- src/Managers/Config.cs | 4 ++-- src/Managers/GLDConfig.cs | 2 +- src/Patches/AutoUpdate.cs | 14 +++++++------- src/Patches/Compatibility.cs | 8 ++++---- src/Patches/Hub.cs | 18 +++++++++--------- src/Patches/Loc.cs | 4 ++-- src/Patches/Main.cs | 6 +++--- src/Plugin.cs | 25 +++---------------------- src/Registry.cs | 2 +- 11 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/Constants.cs diff --git a/src/Constants.cs b/src/Constants.cs new file mode 100644 index 0000000..173e3f6 --- /dev/null +++ b/src/Constants.cs @@ -0,0 +1,35 @@ +namespace PolyMod; + +/// +/// useful constant values. +/// +public partial class Constants +{ + /// + /// path of the mods folder + /// + public static readonly string MODS_PATH = Path.Combine(BASE_PATH, "Mods"); + internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json"); + internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy"; + internal const int AUTOIDX_STARTS_FROM = 1000; + internal static readonly List LOG_MESSAGES_IGNORE = new() + { + "Failed to find atlas", + "Could not find sprite", + "Couldn't find prefab for type", + "MARKET: id:", + "Missing name for value", + }; + /// + /// Path of the polytopia folder + /// + public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, ".."); + /// + /// Filename of the dumped data + /// + public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData"); + internal static readonly string CHECKSUM_PATH + = Path.Combine(BASE_PATH, "CHECKSUM"); + internal const string INCOMPATIBILITY_WARNING_LAST_VERSION_KEY + = "INCOMPATIBILITY_WARNING_LAST_VERSION"; +} diff --git a/src/Loader.cs b/src/Loader.cs index eae5102..87b8ce4 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -19,7 +19,7 @@ namespace PolyMod; -public static class Loader +internal static class Loader { internal static Dictionary typeMappings = new() { @@ -138,10 +138,10 @@ public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonA internal static bool RegisterMods(Dictionary mods) { - Directory.CreateDirectory(Plugin.MODS_PATH); - string[] modContainers = Directory.GetDirectories(Plugin.MODS_PATH) - .Union(Directory.GetFiles(Plugin.MODS_PATH, "*.polymod")) - .Union(Directory.GetFiles(Plugin.MODS_PATH, "*.zip")) + Directory.CreateDirectory(Constants.MODS_PATH); + string[] modContainers = Directory.GetDirectories(Constants.MODS_PATH) + .Union(Directory.GetFiles(Constants.MODS_PATH, "*.polymod")) + .Union(Directory.GetFiles(Constants.MODS_PATH, "*.zip")) .ToArray(); foreach (var modContainer in modContainers) { diff --git a/src/Managers/Config.cs b/src/Managers/Config.cs index f320264..4b91e55 100644 --- a/src/Managers/Config.cs +++ b/src/Managers/Config.cs @@ -11,14 +11,14 @@ public class Config where T : class private T? currentConfig; private readonly string modName; private readonly ConfigTypes configType; - private static readonly string ExposedConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); + private static readonly string ExposedConfigPath = Path.Combine(Constants.BASE_PATH, "mods.json"); private readonly string perModConfigPath; private T? defaultConfig; public Config(string modName, ConfigTypes configType) { this.modName = modName; this.configType = configType; - perModConfigPath = Path.Combine(Plugin.MODS_PATH, $"{modName}.json"); + perModConfigPath = Path.Combine(Constants.MODS_PATH, $"{modName}.json"); Load(); } diff --git a/src/Managers/GLDConfig.cs b/src/Managers/GLDConfig.cs index 24b395b..86d29cb 100644 --- a/src/Managers/GLDConfig.cs +++ b/src/Managers/GLDConfig.cs @@ -7,7 +7,7 @@ namespace PolyMod.Managers; internal class GldConfigTemplate { - private static readonly string ConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json"); + private static readonly string ConfigPath = Path.Combine(Constants.BASE_PATH, "mods.json"); private readonly string templateText; private JsonObject currentConfig = new(); diff --git a/src/Patches/AutoUpdate.cs b/src/Patches/AutoUpdate.cs index ef7a043..cd90cbd 100644 --- a/src/Patches/AutoUpdate.cs +++ b/src/Patches/AutoUpdate.cs @@ -56,20 +56,20 @@ void Update() { Time.timeScale = 0; File.WriteAllBytes( - Path.Combine(Plugin.BASE_PATH, "PolyMod.new.dll"), + Path.Combine(Constants.BASE_PATH, "PolyMod.new.dll"), client.GetAsync(latest?.GetProperty("assets")[0].GetProperty("browser_download_url").GetString()!).UnwrapAsync() .Content.ReadAsByteArrayAsync().UnwrapAsync() ); using ZipArchive bepinex = new(client.GetAsync(bepinex_url).UnwrapAsync().Content.ReadAsStream()); - bepinex.ExtractToDirectory(Path.Combine(Plugin.BASE_PATH, "New"), overwriteFiles: true); + bepinex.ExtractToDirectory(Path.Combine(Constants.BASE_PATH, "New"), overwriteFiles: true); ProcessStartInfo info = new() { - WorkingDirectory = Path.Combine(Plugin.BASE_PATH), + WorkingDirectory = Path.Combine(Constants.BASE_PATH), CreateNoWindow = true, }; if (Application.platform == RuntimePlatform.WindowsPlayer) { - string batchPath = Path.Combine(Plugin.BASE_PATH, "update.bat"); + string batchPath = Path.Combine(Constants.BASE_PATH, "update.bat"); File.WriteAllText(batchPath, $@" @echo off echo Waiting for Polytopia.exe to exit... @@ -93,13 +93,13 @@ echo Launching game... "); info.FileName = "cmd.exe"; info.Arguments = $"/C start \"\" \"{batchPath}\""; - info.WorkingDirectory = Plugin.BASE_PATH; + info.WorkingDirectory = Constants.BASE_PATH; info.CreateNoWindow = true; info.UseShellExecute = false; } if (Application.platform == RuntimePlatform.LinuxPlayer || Application.platform == RuntimePlatform.OSXPlayer) { - string bashPath = Path.Combine(Plugin.BASE_PATH, "update.sh"); + string bashPath = Path.Combine(Constants.BASE_PATH, "update.sh"); File.WriteAllText(bashPath, $@" #!/bin/bash @@ -130,7 +130,7 @@ exit 0 info.FileName = "/bin/bash"; info.Arguments = $"\"{bashPath}\""; - info.WorkingDirectory = Plugin.BASE_PATH; + info.WorkingDirectory = Constants.BASE_PATH; info.CreateNoWindow = true; info.UseShellExecute = false; } diff --git a/src/Patches/Compatibility.cs b/src/Patches/Compatibility.cs index c0b2321..b67ae8f 100644 --- a/src/Patches/Compatibility.cs +++ b/src/Patches/Compatibility.cs @@ -68,12 +68,12 @@ private static void StartScreen_Start() string lastChecksum = checksum; try { - lastChecksum = new(File.ReadAllText(Plugin.CHECKSUM_PATH)); + lastChecksum = new(File.ReadAllText(Constants.CHECKSUM_PATH)); } catch (FileNotFoundException) { } File.WriteAllText( - Plugin.CHECKSUM_PATH, + Constants.CHECKSUM_PATH, checksum ); if (lastChecksum != checksum) @@ -82,13 +82,13 @@ private static void StartScreen_Start() } Version incompatibilityWarningLastVersion = new(PlayerPrefs.GetString( - Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, + Constants.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, Plugin.POLYTOPIA_VERSION.CutRevision().ToString() )); if (VersionManager.SemanticVersion.Cast().CutRevision() > incompatibilityWarningLastVersion) { PlayerPrefs.SetString( - Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, + Constants.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, VersionManager.SemanticVersion.Cast().CutRevision().ToString() ); PlayerPrefs.Save(); diff --git a/src/Patches/Hub.cs b/src/Patches/Hub.cs index 43b186f..beae3f4 100644 --- a/src/Patches/Hub.cs +++ b/src/Patches/Hub.cs @@ -143,7 +143,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) new( "polymod.hub.discord", callback: (UIButtonBase.ButtonAction)((_, _) => - NativeHelpers.OpenURL(Plugin.DISCORD_LINK, false)) + NativeHelpers.OpenURL(Constants.DISCORD_LINK, false)) ), new( "polymod.hub.config", @@ -159,18 +159,18 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) "polymod.hub.dump", callback: (UIButtonBase.ButtonAction)((_, _) => { - Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH); + Directory.CreateDirectory(Constants.DUMPED_DATA_PATH); File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, "gameLogicData.json"), + Path.Combine(Constants.DUMPED_DATA_PATH, "gameLogicData.json"), PolytopiaDataManager.provider.LoadGameLogicData(VersionManager.GameLogicDataVersion) ); File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, "avatarData.json"), + Path.Combine(Constants.DUMPED_DATA_PATH, "avatarData.json"), PolytopiaDataManager.provider.LoadAvatarData(1337) ); foreach (var category in LocalizationManager.Sources[0].GetCategories()) File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, $"localization_{category}.csv"), + Path.Combine(Constants.DUMPED_DATA_PATH, $"localization_{category}.csv"), LocalizationManager.Sources[0].Export_CSV(category) ); foreach (KeyValuePair entry in Registry.mods) @@ -179,7 +179,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) { if (Path.GetFileName(file.name) == "sprites.json") { - File.WriteAllBytes(Path.Combine(Plugin.DUMPED_DATA_PATH, $"sprites_{entry.Key}.json"), file.bytes); + File.WriteAllBytes(Path.Combine(Constants.DUMPED_DATA_PATH, $"sprites_{entry.Key}.json"), file.bytes); } } } @@ -208,7 +208,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) } } File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, $"preview_{type}.json"), + Path.Combine(Constants.DUMPED_DATA_PATH, $"preview_{type}.json"), JsonSerializer.Serialize(previewTiles, new JsonSerializerOptions { WriteIndented = true }) ); } @@ -262,9 +262,9 @@ private static void GameManager_Update() internal static void UpdateSpriteInfos() { string message = string.Empty; - Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH); + Directory.CreateDirectory(Constants.DUMPED_DATA_PATH); - foreach (var file in Directory.GetFiles(Plugin.DUMPED_DATA_PATH)) + foreach (var file in Directory.GetFiles(Constants.DUMPED_DATA_PATH)) { string? name = Path.GetFileNameWithoutExtension(file); List subnames = new(); diff --git a/src/Patches/Loc.cs b/src/Patches/Loc.cs index 2287bd8..4697599 100644 --- a/src/Patches/Loc.cs +++ b/src/Patches/Loc.cs @@ -13,7 +13,7 @@ internal static class Loc [HarmonyPatch(typeof(SelectTribePopup), nameof(SelectTribePopup.SetDescription))] private static void SetDescription(SelectTribePopup __instance) { - if ((int)__instance.SkinType >= Plugin.AUTOIDX_STARTS_FROM) + if ((int)__instance.SkinType >= Constants.AUTOIDX_STARTS_FROM) { string description = Localization.Get(__instance.SkinType.GetLocalizationDescriptionKey()); if (description == __instance.SkinType.GetLocalizationDescriptionKey()) @@ -42,7 +42,7 @@ private static bool Localization_Get(ref string key, Il2CppReferenceArray= Plugin.AUTOIDX_STARTS_FROM) + if (parsedIdx >= Constants.AUTOIDX_STARTS_FROM) { idx = parsedIdx; } diff --git a/src/Patches/Main.cs b/src/Patches/Main.cs index 022f6b6..6e3e7e4 100644 --- a/src/Patches/Main.cs +++ b/src/Patches/Main.cs @@ -110,7 +110,7 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb [HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsSkinUnlockedInternal))] private static bool PurchaseManager_IsSkinUnlockedInternal(ref bool __result, SkinType skinType) { - __result = (int)skinType >= Plugin.AUTOIDX_STARTS_FROM && skinType != SkinType.Test; + __result = (int)skinType >= Constants.AUTOIDX_STARTS_FROM && skinType != SkinType.Test; return !__result; } @@ -118,7 +118,7 @@ private static bool PurchaseManager_IsSkinUnlockedInternal(ref bool __result, Sk [HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsTribeUnlocked))] private static void PurchaseManager_IsTribeUnlocked(ref bool __result, TribeData.Type type) { - __result = (int)type >= Plugin.AUTOIDX_STARTS_FROM || __result; + __result = (int)type >= Constants.AUTOIDX_STARTS_FROM || __result; } [HarmonyPostfix] @@ -135,7 +135,7 @@ private static void PurchaseManager_GetUnlockedTribes( [HarmonyPatch(typeof(IL2CPPUnityLogSource), nameof(IL2CPPUnityLogSource.UnityLogCallback))] private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string exception, LogType type) { - foreach (string stringToIgnore in Plugin.LOG_MESSAGES_IGNORE) + foreach (string stringToIgnore in Constants.LOG_MESSAGES_IGNORE) { if (logLine.Contains(stringToIgnore)) return false; diff --git a/src/Plugin.cs b/src/Plugin.cs index 025258c..7251c58 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -9,7 +9,7 @@ namespace PolyMod; [BepInPlugin("com.polymod", "PolyMod", VERSION)] -public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin +internal partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin { internal record PolyConfig( bool debug = false, @@ -17,25 +17,6 @@ internal record PolyConfig( bool updatePrerelease = false ); - internal const int AUTOIDX_STARTS_FROM = 1000; - internal const string INCOMPATIBILITY_WARNING_LAST_VERSION_KEY - = "INCOMPATIBILITY_WARNING_LAST_VERSION"; - public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, ".."); - public static readonly string MODS_PATH = Path.Combine(BASE_PATH, "Mods"); - public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData"); - internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json"); - internal static readonly string CHECKSUM_PATH - = Path.Combine(BASE_PATH, "CHECKSUM"); - internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy"; - internal static readonly List LOG_MESSAGES_IGNORE = new() - { - "Failed to find atlas", - "Could not find sprite", - "Couldn't find prefab for type", - "MARKET: id:", - "Missing name for value", - }; - #pragma warning disable CS8618 internal static PolyConfig config; @@ -46,7 +27,7 @@ public override void Load() { try { - config = JsonSerializer.Deserialize(File.ReadAllText(CONFIG_PATH))!; + config = JsonSerializer.Deserialize(File.ReadAllText(Constants.CONFIG_PATH))!; } catch { @@ -77,7 +58,7 @@ internal static Stream GetResource(string id) internal static void WriteConfig() { - File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config)); + File.WriteAllText(Constants.CONFIG_PATH, JsonSerializer.Serialize(config)); } internal static void UpdateConsole() diff --git a/src/Registry.cs b/src/Registry.cs index 91cdcb6..a9134f4 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -11,7 +11,7 @@ namespace PolyMod; /// public static class Registry { - internal static int autoidx = Plugin.AUTOIDX_STARTS_FROM; + internal static int autoidx = Constants.AUTOIDX_STARTS_FROM; /// The registry for custom sprites. internal static Dictionary sprites = new(); From 7566a172ad0cf7af432b9e5ee7b26c56b8a14607 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Wed, 20 Aug 2025 14:48:21 +0200 Subject: [PATCH 22/26] Fix error --- src/Constants.cs | 2 +- src/Patches/AutoUpdate.cs | 2 +- src/Patches/Compatibility.cs | 2 +- src/Patches/Hub.cs | 2 +- src/Plugin.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Constants.cs b/src/Constants.cs index 173e3f6..91579e1 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -25,7 +25,7 @@ public partial class Constants /// public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, ".."); /// - /// Filename of the dumped data + /// kFilename of the dumped data /// public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData"); internal static readonly string CHECKSUM_PATH diff --git a/src/Patches/AutoUpdate.cs b/src/Patches/AutoUpdate.cs index cd90cbd..4ea6670 100644 --- a/src/Patches/AutoUpdate.cs +++ b/src/Patches/AutoUpdate.cs @@ -35,7 +35,7 @@ private static void StartScreen_Start() break; } string newVersion = latest?.GetProperty("tag_name").GetString()!.TrimStart('v')!; - if (newVersion.IsVersionOlderOrEqual(Plugin.VERSION)) return; + if (newVersion.IsVersionOlderOrEqual(Constants.POLYMOD_VERSION)) return; string os = Application.platform switch { RuntimePlatform.WindowsPlayer => "win", diff --git a/src/Patches/Compatibility.cs b/src/Patches/Compatibility.cs index b67ae8f..401c4b4 100644 --- a/src/Patches/Compatibility.cs +++ b/src/Patches/Compatibility.cs @@ -83,7 +83,7 @@ private static void StartScreen_Start() Version incompatibilityWarningLastVersion = new(PlayerPrefs.GetString( Constants.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, - Plugin.POLYTOPIA_VERSION.CutRevision().ToString() + Constants.POLYTOPIA_VERSION.CutRevision().ToString() )); if (VersionManager.SemanticVersion.Cast().CutRevision() > incompatibilityWarningLastVersion) { diff --git a/src/Patches/Hub.cs b/src/Patches/Hub.cs index beae3f4..8399350 100644 --- a/src/Patches/Hub.cs +++ b/src/Patches/Hub.cs @@ -91,7 +91,7 @@ private static void StartScreen_Start() textComponent.fontSize = 18; textComponent.alignment = TextAlignmentOptions.BottomLeft; - text.GetComponent().Text = $"PolyMod {Plugin.VERSION}"; + text.GetComponent().Text = $"PolyMod {Constants.POLYMOD_VERSION}"; text.AddComponent().ignoreLayout = true; } else if (parentName == "NewsButton") diff --git a/src/Plugin.cs b/src/Plugin.cs index 7251c58..61a757e 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -8,7 +8,7 @@ namespace PolyMod; -[BepInPlugin("com.polymod", "PolyMod", VERSION)] +[BepInPlugin("com.polymod", "PolyMod", Constants.POLYMOD_VERSION)] internal partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin { internal record PolyConfig( From 9959f1585dcb78e404f9a4155ba70c61d6d406aa Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Wed, 20 Aug 2025 14:51:19 +0200 Subject: [PATCH 23/26] PolyMod.csproj is spelt wrong --- PolyMod.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PolyMod.csproj b/PolyMod.csproj index 5a2e73a..1530dcb 100644 --- a/PolyMod.csproj +++ b/PolyMod.csproj @@ -33,9 +33,9 @@ Date: Wed, 20 Aug 2025 14:57:01 +0200 Subject: [PATCH 24/26] fix warning --- src/Constants.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Constants.cs b/src/Constants.cs index 91579e1..3a74747 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -5,6 +5,10 @@ namespace PolyMod; /// public partial class Constants { + /// + /// Path of the polytopia folder + /// + public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, ".."); /// /// path of the mods folder /// @@ -21,10 +25,6 @@ public partial class Constants "Missing name for value", }; /// - /// Path of the polytopia folder - /// - public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, ".."); - /// /// kFilename of the dumped data /// public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData"); From 9ec94e50d3d169d648e8dfaca7e0ae2edfc9dd24 Mon Sep 17 00:00:00 2001 From: joukepouke <73423163+joukepouke@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:32:07 +0200 Subject: [PATCH 25/26] Update pages.yml --- .github/workflows/pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index ea55660..2119bb7 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -3,6 +3,7 @@ on: branches: - main - documentation-tm # for testing + workflow-dispatch # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: From a131e7536a1344661e6495ce420d7930297d2ddc Mon Sep 17 00:00:00 2001 From: joukepouke <73423163+joukepouke@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:34:11 +0200 Subject: [PATCH 26/26] Update pages.yml --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 2119bb7..ffd4dca 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -3,7 +3,7 @@ on: branches: - main - documentation-tm # for testing - workflow-dispatch + workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: