From 2d8111e2526bf466261fea09c524b53ec8e624d6 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Sat, 5 Apr 2025 22:17:54 +0300 Subject: [PATCH 01/23] Add `AssetBundle` loader and registry (closes #63) --- src/Loader.cs | 10 +++++++++- src/Managers/Main.cs | 34 ++++++++++++++++++++++------------ src/Registry.cs | 1 + 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index 7a6edf1..3e9f401 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -305,7 +305,7 @@ public static void LoadAudioFile(Mod mod, Mod.File file) { AudioSource audioSource = new GameObject().AddComponent(); GameObject.DontDestroyOnLoad(audioSource); - audioSource.clip = Managers.Audio.BuildAudioClip(file.bytes); + audioSource.clip = Audio.BuildAudioClip(file.bytes); Registry.audioClips.Add(Path.GetFileNameWithoutExtension(file.name), audioSource); } @@ -407,6 +407,14 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) } } + public static void LoadAssetBundle(Mod mod, Mod.File file) + { + Registry.assetBundles.Add( + Path.GetFileNameWithoutExtension(file.name), + AssetBundle.LoadFromMemory(file.bytes) + ); + } + public static void HandleSkins(JObject gld, JObject patch) { foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray()) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index fa49b10..3a8456e 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -131,21 +131,31 @@ internal static void Load(JObject gameLogicdata) if (mod.status != Mod.Status.Success) continue; foreach (var file in mod.files) { - if (Path.GetFileName(file.name) == "patch.json") + switch (Path.GetFileName(file.name)) { - Loader.LoadGameLogicDataPatch(mod, gameLogicdata, JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd())); + case "patch.json": + Loader.LoadGameLogicDataPatch( + mod, + gameLogicdata, + JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()) + ); + break; + case "localization.json": + Loader.LoadLocalizationFile(mod, file); + break; } - if (Path.GetFileName(file.name) == "localization.json") - { - Loader.LoadLocalizationFile(mod, file); - } - if (Path.GetExtension(file.name) == ".png") - { - Loader.LoadSpriteFile(mod, file); - } - if (Path.GetExtension(file.name) == ".wav") + + switch (Path.GetExtension(file.name)) { - Loader.LoadAudioFile(mod, file); + case ".png": + Loader.LoadSpriteFile(mod, file); + break; + case ".wav": + Loader.LoadAudioFile(mod, file); + break; + case ".bundle": + Loader.LoadAssetBundle(mod, file); + break; } } } diff --git a/src/Registry.cs b/src/Registry.cs index 2c7a612..1336c26 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -12,6 +12,7 @@ public static class Registry internal static Dictionary mods = new(); public static Dictionary tribePreviews = new(); public static Dictionary spriteInfos = new(); + public static Dictionary assetBundles = new(); public static List customTribes = new(); public static List skinInfo = new(); public static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last(); From 716c164686ac9956ee92c410dbe70ed60eb980b5 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 6 Apr 2025 15:46:51 +0200 Subject: [PATCH 02/23] Added game mode button creation api --- PolyMod.csproj | 2 +- src/Loader.cs | 7 ++++++ src/Managers/Main.cs | 57 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/PolyMod.csproj b/PolyMod.csproj index ef778e6..dcf6645 100644 --- a/PolyMod.csproj +++ b/PolyMod.csproj @@ -10,7 +10,7 @@ https://polymod.dev/nuget/v3/index.json; IL2CPP - 1.1.4 + 1.2.0 2.12.1.13909 PolyModdingTeam The Battle of Polytopia's mod loader. diff --git a/src/Loader.cs b/src/Loader.cs index 3e9f401..e8a7a2b 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -32,6 +32,13 @@ public static class Loader { "improvementAbility", typeof(ImprovementAbility.Type) }, { "playerAbility", typeof(PlayerAbility.Type) } }; + internal static List gamemodes = new(); + public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite); + + public static void AddGameModeButton(int index, UIButtonBase.ButtonAction action, Sprite? sprite) + { + gamemodes.Add(new GameModeButtonsInformation(index, action, null, sprite)); + } public static void AddPatchDataType(string typeId, Type type) { diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 3a8456e..6260be8 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -2,6 +2,7 @@ using HarmonyLib; using Newtonsoft.Json.Linq; using Polytopia.Data; +using PolytopiaBackendBase.Game; using System.Diagnostics; using System.Text; using System.Text.Json; @@ -70,6 +71,62 @@ private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string return true; } + [HarmonyPrefix] + [HarmonyPatch(typeof(GameModeScreen), nameof(GameModeScreen.Init))] + private static void GameModeScreen_Init(GameModeScreen __instance) // TODO: refactor + { + List list = __instance.buttons.ToList(); + for (int i = 0; i < Loader.gamemodes.Count; i++) + { + Loader.GameModeButtonsInformation item = Loader.gamemodes[i]; + GamemodeButton prefab = __instance.buttons[2]; + GamemodeButton button = UnityEngine.GameObject.Instantiate(prefab); + int buttonIndex = __instance.buttons.Length; + list.Add(button); + Loader.gamemodes[i] = new Loader.GameModeButtonsInformation(item.gameModeIndex, item.action, buttonIndex, item.sprite); + }; + Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray newArray = list.ToArray(); + for (int i = 0; i < __instance.buttons.Length; i++) + { + if(newArray[i] != null) + { + newArray[i].OnClicked = __instance.buttons[i].OnClicked; + } + } + + for (int i = 0; i < Loader.gamemodes.Count; i++) + { + Loader.GameModeButtonsInformation item = Loader.gamemodes[i]; + if(item.buttonIndex != null) + { + newArray[item.buttonIndex.Value].OnClicked = item.action; + } + } + __instance.buttons = newArray; + + for (int i = 0; i < __instance.buttons.Length; i++) + { + GamemodeButton item = __instance.buttons[i]; + List newData = item.gamemodeData.ToList(); + for (int j = 0; j < Loader.gamemodes.Count; j++) + { + Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; + string id = EnumCache.GetName((GameMode)info.gameModeIndex).ToLower(); + newData.Add(new GamemodeButton.GamemodeButtonData() { gameMode = (GameMode)info.gameModeIndex, id = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id), descriptionKey = "gamemode." + id + ".description.button", headerKey = "gamemode." + id + ".caps", icon = info.sprite}); + } + item.gamemodeData = newData.ToArray(); + for (int j = 0; j < Loader.gamemodes.Count; j++) + { + Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; + + if(info.buttonIndex == i) + { + item.SetGamemode(info.buttonIndex.Value); + } + } + } + } + internal static void Init() { stopwatch.Start(); From 5f0938b936fa425628edad89c99ad15d4b5d46a1 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Sun, 6 Apr 2025 17:24:07 +0300 Subject: [PATCH 03/23] Refactor game mode button API --- src/Loader.cs | 2 +- src/Managers/Main.cs | 69 ++++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index e8a7a2b..9d9d001 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -417,7 +417,7 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) public static void LoadAssetBundle(Mod mod, Mod.File file) { Registry.assetBundles.Add( - Path.GetFileNameWithoutExtension(file.name), + Path.GetFileNameWithoutExtension(file.name), AssetBundle.LoadFromMemory(file.bytes) ); } diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 6260be8..7df2f95 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -71,61 +71,56 @@ private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string return true; } - [HarmonyPrefix] - [HarmonyPatch(typeof(GameModeScreen), nameof(GameModeScreen.Init))] - private static void GameModeScreen_Init(GameModeScreen __instance) // TODO: refactor - { + [HarmonyPrefix] + [HarmonyPatch(typeof(GameModeScreen), nameof(GameModeScreen.Init))] + private static void GameModeScreen_Init(GameModeScreen __instance) + { List list = __instance.buttons.ToList(); for (int i = 0; i < Loader.gamemodes.Count; i++) { - Loader.GameModeButtonsInformation item = Loader.gamemodes[i]; - GamemodeButton prefab = __instance.buttons[2]; - GamemodeButton button = UnityEngine.GameObject.Instantiate(prefab); - int buttonIndex = __instance.buttons.Length; + var item = Loader.gamemodes[i]; + var button = GameObject.Instantiate(__instance.buttons[2]); list.Add(button); - Loader.gamemodes[i] = new Loader.GameModeButtonsInformation(item.gameModeIndex, item.action, buttonIndex, item.sprite); - }; - Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray newArray = list.ToArray(); + Loader.gamemodes[i] = new Loader.GameModeButtonsInformation(item.gameModeIndex, item.action, __instance.buttons.Length, item.sprite); + } + + var newArray = list.ToArray(); for (int i = 0; i < __instance.buttons.Length; i++) { - if(newArray[i] != null) - { - newArray[i].OnClicked = __instance.buttons[i].OnClicked; - } + if (newArray[i] != null) newArray[i].OnClicked = __instance.buttons[i].OnClicked; } for (int i = 0; i < Loader.gamemodes.Count; i++) { - Loader.GameModeButtonsInformation item = Loader.gamemodes[i]; - if(item.buttonIndex != null) - { - newArray[item.buttonIndex.Value].OnClicked = item.action; - } + if (Loader.gamemodes[i].buttonIndex != null) + newArray[Loader.gamemodes[i].buttonIndex!.Value].OnClicked = Loader.gamemodes[i].action; } + __instance.buttons = newArray; - for (int i = 0; i < __instance.buttons.Length; i++) + foreach (var button in __instance.buttons) { - GamemodeButton item = __instance.buttons[i]; - List newData = item.gamemodeData.ToList(); - for (int j = 0; j < Loader.gamemodes.Count; j++) + var newData = button.gamemodeData.ToList(); + foreach (var info in Loader.gamemodes) { - Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; string id = EnumCache.GetName((GameMode)info.gameModeIndex).ToLower(); - newData.Add(new GamemodeButton.GamemodeButtonData() { gameMode = (GameMode)info.gameModeIndex, id = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id), descriptionKey = "gamemode." + id + ".description.button", headerKey = "gamemode." + id + ".caps", icon = info.sprite}); + newData.Add(new GamemodeButton.GamemodeButtonData() + { + gameMode = (GameMode)info.gameModeIndex, + id = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id), + descriptionKey = "gamemode." + id + ".description.button", + headerKey = "gamemode." + id + ".caps", icon = info.sprite + }); } - item.gamemodeData = newData.ToArray(); - for (int j = 0; j < Loader.gamemodes.Count; j++) - { - Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; + button.gamemodeData = newData.ToArray(); - if(info.buttonIndex == i) - { - item.SetGamemode(info.buttonIndex.Value); - } + foreach (var info in Loader.gamemodes) + { + if (info.buttonIndex == Array.IndexOf(__instance.buttons, button)) + button.SetGamemode(info.buttonIndex.Value); } } - } + } internal static void Init() { @@ -192,8 +187,8 @@ internal static void Load(JObject gameLogicdata) { case "patch.json": Loader.LoadGameLogicDataPatch( - mod, - gameLogicdata, + mod, + gameLogicdata, JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()) ); break; From ec953a529ecf2531b7cbd50f6426875c7d6d6b6f Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Sun, 6 Apr 2025 17:28:29 +0300 Subject: [PATCH 04/23] Empty (closes #61) From d92059b6571a5d358236ab064d4e56b0226bdccf Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Fri, 11 Apr 2025 21:18:55 +0300 Subject: [PATCH 05/23] Add `description` field to `manifest.json` (closes #69) --- resources/localization.json | 4 ++-- src/Managers/Hub.cs | 5 +++-- src/Managers/Main.cs | 1 + src/Mod.cs | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/resources/localization.json b/resources/localization.json index 8a39199..c4e939c 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -44,8 +44,8 @@ "German (Germany)": "{0} Willkommen! {1}\nDiese Mods sind gerade eben geladen:" }, "polymod_hub_mod": { - "English": "Name: {0}\nStatus: {1}\nAuthors: {2}\nVersion: {3}", - "Russian": "Имя: {0}\nСтатус: {1}\nАвторы: {2}\nВерсия: {3}", + "English": "Name: {0}\nStatus: {1}\nAuthors: {2}\nVersion: {3}\nDescription: {4}", + "Russian": "Имя: {0}\nСтатус: {1}\nАвторы: {2}\nВерсия: {3}\nОписание: {4}", "Turkish": "İsim: {0}\nDurum: {1}\nYaratıcılar: {2}\nSürüm: {3}", "Spanish (Mexico)": "Titulo: {0}\nEstado: {1}\nPublicador: {2}\nVersion: {3}", "French (France)": "Titre: {0}\nEtat: {1}\nAuteurs: {2}\nVersion: {3}", diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index ae5cbd2..49a79dc 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -90,7 +90,8 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) Localization.Get("polymod.hub.mod.status." + Enum.GetName(typeof(Mod.Status), mod.status)!.ToLower()), string.Join(", ", mod.authors), - mod.version.ToString() + mod.version.ToString(), + mod.description ?? "" }); popup.Description += "\n\n"; } @@ -103,7 +104,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) new("buttons.back"), new( "polymod.hub.discord", - callback: (UIButtonBase.ButtonAction)((int _, BaseEventData _) => + callback: (UIButtonBase.ButtonAction)((_, _) => NativeHelpers.OpenURL(Plugin.DISCORD_LINK, false)) ) }; diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index bc9ad7e..9f8819b 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -129,6 +129,7 @@ internal static void Init() Mod.Manifest polytopia = new( "polytopia", "The Battle of Polytopia", + null, new(Application.version.ToString()), new string[] { "Midjiwan AB" }, Array.Empty() diff --git a/src/Mod.cs b/src/Mod.cs index c8c179f..1e68eef 100644 --- a/src/Mod.cs +++ b/src/Mod.cs @@ -2,7 +2,7 @@ namespace PolyMod; public class Mod { public record Dependency(string id, Version min, Version max, bool required = true); - public record Manifest(string id, string? name, Version version, string[] authors, Dependency[]? dependencies, bool client = false); + public record Manifest(string id, string? name, string? description, Version version, string[] authors, Dependency[]? dependencies, bool client = false); public record File(string name, byte[] bytes); public enum Status { @@ -13,6 +13,7 @@ public enum Status public string id; public string? name; + public string? description; public Version version; public string[] authors; public Dependency[]? dependencies; @@ -24,6 +25,7 @@ public Mod(Manifest manifest, Status status, List files) { id = manifest.id; name = manifest.name ?? manifest.id; + description = manifest.description; version = manifest.version; authors = manifest.authors; dependencies = manifest.dependencies; From 5dbf9cf6d749b1f21c80c0951c0629d95a0b0bb2 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Fri, 11 Apr 2025 21:41:40 +0300 Subject: [PATCH 06/23] Dump localization when clicking dump data on debug (closes #68) --- src/Managers/Hub.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index 49a79dc..feeac79 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -1,5 +1,6 @@ using Cpp2IL.Core.Extensions; using HarmonyLib; +using I2.Loc; using TMPro; using UnityEngine; using UnityEngine.EventSystems; @@ -111,17 +112,22 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) if (Plugin.config.debug) popupButtons.Add(new( "polymod.hub.dump", - callback: (UIButtonBase.ButtonAction)((int _, BaseEventData _) => + callback: (UIButtonBase.ButtonAction)((_, _) => { Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH); File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, $"gameLogicData.json"), + Path.Combine(Plugin.DUMPED_DATA_PATH, "gameLogicData.json"), PolytopiaDataManager.provider.LoadGameLogicData(VersionManager.GameLogicDataVersion) ); File.WriteAllTextAsync( - Path.Combine(Plugin.DUMPED_DATA_PATH, $"avatarData.json"), + Path.Combine(Plugin.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"), + LocalizationManager.Sources[0].Export_CSV(category) + ); NotificationManager.Notify(Localization.Get("polymod.hub.dumped")); }), closesPopup: false From c8f2dadb0a439ef3f7ba6b3fedcddd98435b7003 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Fri, 11 Apr 2025 21:59:55 +0300 Subject: [PATCH 07/23] Fix formatting --- src/Managers/Loc.cs | 2 +- src/Managers/Main.cs | 11 ++++++----- src/Managers/Visual.cs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Managers/Loc.cs b/src/Managers/Loc.cs index 3c5c7ba..d342e43 100644 --- a/src/Managers/Loc.cs +++ b/src/Managers/Loc.cs @@ -33,7 +33,7 @@ private static bool Localization_Get(ref string key, Il2CppReferenceArray= Plugin.AUTOIDX_STARTS_FROM) + if (parsedIdx >= Plugin.AUTOIDX_STARTS_FROM) { idx = parsedIdx; } diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 9f8819b..0eccfec 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -104,12 +104,13 @@ private static void GameModeScreen_Init(GameModeScreen __instance) foreach (var info in Loader.gamemodes) { string id = EnumCache.GetName((GameMode)info.gameModeIndex).ToLower(); - newData.Add(new GamemodeButton.GamemodeButtonData() + newData.Add(new GamemodeButton.GamemodeButtonData() { - gameMode = (GameMode)info.gameModeIndex, - id = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id), - descriptionKey = "gamemode." + id + ".description.button", - headerKey = "gamemode." + id + ".caps", icon = info.sprite + gameMode = (GameMode)info.gameModeIndex, + id = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id), + descriptionKey = "gamemode." + id + ".description.button", + headerKey = "gamemode." + id + ".caps", + icon = info.sprite }); } button.gamemodeData = newData.ToArray(); diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index e938b6c..116aac5 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -65,7 +65,7 @@ void GetAtlas(SpriteAtlas spriteAtlas) if (spriteAtlas != null) { Sprite foundSprite = __instance.GetSpriteFromAtlas(spriteAtlas, sprite); - if(foundSprite != null) + if (foundSprite != null) { completion?.Invoke(atlas, sprite, __instance.GetSpriteFromAtlas(spriteAtlas, sprite)); found = true; From f843c61cdc0f3e8ddba320cb81f7b046ebc3c445 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 12 Apr 2025 17:15:35 +0200 Subject: [PATCH 08/23] Fixed custom game mods loc and added enum mapping --- src/Loader.cs | 8 ++++++-- src/Managers/Main.cs | 11 ++++++++--- src/Registry.cs | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index fe25edf..5d9f917 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -6,6 +6,7 @@ using PolyMod.Json; using PolyMod.Managers; using Polytopia.Data; +using PolytopiaBackendBase.Game; using System.Data; using System.Diagnostics; using System.Globalization; @@ -35,9 +36,12 @@ public static class Loader internal static List gamemodes = new(); public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite); - public static void AddGameModeButton(int index, UIButtonBase.ButtonAction action, Sprite? sprite) + public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite) { - gamemodes.Add(new GameModeButtonsInformation(index, action, null, 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) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 0eccfec..9762e49 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -98,8 +98,9 @@ private static void GameModeScreen_Init(GameModeScreen __instance) __instance.buttons = newArray; - foreach (var button in __instance.buttons) + for (int i = 0; i < __instance.buttons.Length; i++) { + GamemodeButton button = __instance.buttons[i]; var newData = button.gamemodeData.ToList(); foreach (var info in Loader.gamemodes) { @@ -115,10 +116,14 @@ private static void GameModeScreen_Init(GameModeScreen __instance) } button.gamemodeData = newData.ToArray(); - foreach (var info in Loader.gamemodes) + for (int j = 0; j < Loader.gamemodes.Count; j++) { - if (info.buttonIndex == Array.IndexOf(__instance.buttons, button)) + Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; + + if(info.buttonIndex == i) + { button.SetGamemode(info.buttonIndex.Value); + } } } } diff --git a/src/Registry.cs b/src/Registry.cs index 1336c26..8b74429 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -1,6 +1,7 @@ using LibCpp2IL; using PolyMod.Managers; using Polytopia.Data; +using PolytopiaBackendBase.Game; using UnityEngine; namespace PolyMod; @@ -16,6 +17,7 @@ public static class Registry public static List customTribes = new(); 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; public static Sprite? GetSprite(string name, string style = "", int level = 0) { From c19d08b0c9ff07f19a36189f4d7b646f0f839e9d Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Fri, 18 Apr 2025 19:49:25 +0300 Subject: [PATCH 09/23] Add more detailed issue description on invalid manifest (closes #67) --- src/Loader.cs | 52 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index 5d9f917..f112f61 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -99,30 +99,42 @@ internal static void LoadMods(Dictionary mods) } } - if (manifest != null - && manifest.id != null - && Regex.IsMatch(manifest.id, @"^(?!polytopia$)[a-z_]+$") - && manifest.version != null - && manifest.authors != null - && manifest.authors.Length != 0 - ) + if (manifest == null) { - if (mods.ContainsKey(manifest.id)) - { - Plugin.logger.LogError($"Mod {manifest.id} already exists"); - continue; - } - mods.Add(manifest.id, new( - manifest, - Mod.Status.Success, - files - )); - Plugin.logger.LogInfo($"Registered mod {manifest.id}"); + Plugin.logger.LogError($"Mod manifest not found in {modContainer}"); + continue; } - else + if (manifest.id == null) + { + Plugin.logger.LogError($"Mod id not found in {modContainer}"); + continue; + } + if (!Regex.IsMatch(manifest.id, @"^(?!polytopia$)[a-z_]+$")) + { + Plugin.logger.LogError($"Mod id {manifest.id} is invalid in {modContainer}"); + continue; + } + if (manifest.version == null) + { + Plugin.logger.LogError($"Mod version not found in {modContainer}"); + continue; + } + if (manifest.authors == null || manifest.authors.Length == 0) + { + Plugin.logger.LogError($"Mod authors not found in {modContainer}"); + continue; + } + if (mods.ContainsKey(manifest.id)) { - Plugin.logger.LogError("An invalid mod manifest was found or not found at all"); + Plugin.logger.LogError($"Mod {manifest.id} already exists"); + continue; } + mods.Add(manifest.id, new( + manifest, + Mod.Status.Success, + files + )); + Plugin.logger.LogInfo($"Registered mod {manifest.id}"); } foreach (var (id, mod) in mods) From 23bc9c648f8025038ae4f698f4a83248c82ba9cf Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Wed, 30 Apr 2025 19:29:04 +0300 Subject: [PATCH 10/23] Modular patch.json (closes #75) --- src/Managers/Main.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 9762e49..ec3b5d5 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; using UnityEngine; namespace PolyMod.Managers; @@ -120,7 +121,7 @@ private static void GameModeScreen_Init(GameModeScreen __instance) { Loader.GameModeButtonsInformation info = Loader.gamemodes[j]; - if(info.buttonIndex == i) + if (info.buttonIndex == i) { button.SetGamemode(info.buttonIndex.Value); } @@ -190,18 +191,19 @@ internal static void Load(JObject gameLogicdata) if (mod.status != Mod.Status.Success) continue; foreach (var file in mod.files) { - switch (Path.GetFileName(file.name)) + if (Path.GetFileName(file.name) == "localization.json") { - case "patch.json": - Loader.LoadGameLogicDataPatch( - mod, - gameLogicdata, - JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()) - ); - break; - case "localization.json": - Loader.LoadLocalizationFile(mod, file); - break; + Loader.LoadLocalizationFile(mod, file); + continue; + } + if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$")) + { + Loader.LoadGameLogicDataPatch( + mod, + gameLogicdata, + JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()) + ); + continue; } switch (Path.GetExtension(file.name)) From 60c8df361c83c2b9760510b17c59d05e249dd8e4 Mon Sep 17 00:00:00 2001 From: HighFlyer222 <66063913+HighFlyer-222@users.noreply.github.com> Date: Thu, 1 May 2025 15:55:37 +0200 Subject: [PATCH 11/23] Dynamic tech branch angle (#83) * Dynamic tech branch angle * Tech branch fixes --- src/Managers/Main.cs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index ec3b5d5..ae86f5b 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -129,6 +129,40 @@ private static void GameModeScreen_Init(GameModeScreen __instance) } } + [HarmonyPrefix] + [HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))] + public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) { + float baseAngle = 360 / GameManager.GameState.GameLogicData.GetTechData(TechData.Type.Basic).techUnlocks.Count; + float childAngle = 0f; + if (parentItem != null) + { + childAngle = angle + baseAngle * (data.techUnlocks.Count - 1) / 2f; + } + GameLogicData gameLogicData = GameManager.GameState.GameLogicData; + TribeData tribeData = gameLogicData.GetTribeData(GameManager.LocalPlayer.tribe); + foreach (TechData techData in data.techUnlocks) + { + if (gameLogicData.TryGetData(techData.type, out TechData techData2)) + { + TechData @override = GameManager.GameState.GameLogicData.GetOverride(techData, tribeData); + TechItem techItem = __instance.CreateTechItem(@override, parentItem, childAngle); + __instance.currTechIdx++; + if (@override.techUnlocks != null && @override.techUnlocks.Count > 0) + { + __instance.CreateNode(@override, techItem, childAngle); + } + childAngle -= baseAngle; + } + } + Il2CppSystem.Action onItemsRefreshed = __instance.OnItemsRefreshed; + if (onItemsRefreshed == null) + { + return false; + } + onItemsRefreshed.Invoke(__instance); + return false; + } + internal static void Init() { stopwatch.Start(); From 341e971283e02ab319542ec96d3e81fa20a59bae Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 May 2025 15:41:23 +0200 Subject: [PATCH 12/23] Added implementation for custom flood sprites for aquarion skins. Closes #51 --- src/Managers/Visual.cs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index 8a0ac9f..ff1cd84 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -189,6 +189,27 @@ private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, T string flood = ""; if (tile.data.effects.Contains(TileData.EffectType.Flooded)) { + Il2CppSystem.Collections.Generic.List newStack = new Il2CppSystem.Collections.Generic.List(); + foreach (CommandBase command in GameManager.GameState.CommandStack) + { + newStack.Add(command); + } + newStack.Reverse(); + foreach (CommandBase command in GameManager.GameState.CommandStack) + { + if (command.GetCommandType() == CommandType.Flood) + { + FloodCommand floodCommand = command.Cast(); + if (floodCommand.Coordinates == tile.Coordinates) + { + if (GameManager.GameState.TryGetPlayer(floodCommand.PlayerId, out PlayerState playerState)) + { + skinType = playerState.skinType; + } + break; + } + } + } flood = "_flooded"; } if (tile.data.terrain is Polytopia.Data.TerrainData.Type.Forest or Polytopia.Data.TerrainData.Type.Mountain) @@ -221,6 +242,24 @@ private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, T } } + [HarmonyPostfix] + [HarmonyPatch(typeof(TileData), nameof(TileData.Flood))] + private static void TileData_Flood(TileData __instance, PlayerState playerState) + { + if (GameManager.Instance.isLevelLoaded) + { + GameManager.Client.ActionManager.ExecuteCommand(new FloodCommand(playerState.Id, __instance.coordinates), out string error); + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(FloodCommand), nameof(FloodCommand.IsValid))] + private static bool FloodCommand_IsValid(ref bool __result, FloodCommand __instance, GameState state, ref string validationError) + { + __result = true; + return false; + } + [HarmonyPostfix] [HarmonyPatch(typeof(PolytopiaSpriteRenderer), nameof(PolytopiaSpriteRenderer.ForceUpdateMesh))] private static void PolytopiaSpriteRenderer_ForceUpdateMesh(PolytopiaSpriteRenderer __instance) From c0514798606bbdaf8f5275788ccbbca22340d09a Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 3 May 2025 19:37:53 +0200 Subject: [PATCH 13/23] Added dinamic sprite info update --- PolyMod.csproj | 2 +- resources/localization.json | 9 ++++ src/Loader.cs | 42 +++++++++++---- src/Managers/Hub.cs | 100 ++++++++++++++++++++++++++++++++++++ src/Managers/Visual.cs | 5 ++ 5 files changed, 146 insertions(+), 12 deletions(-) diff --git a/PolyMod.csproj b/PolyMod.csproj index 114fc96..3816483 100644 --- a/PolyMod.csproj +++ b/PolyMod.csproj @@ -10,7 +10,7 @@ https://polymod.dev/nuget/v3/index.json; IL2CPP - 1.2.0-pre + 1.2.0-pre.1 2.13.0.14200 PolyModdingTeam The Battle of Polytopia's mod loader. diff --git a/resources/localization.json b/resources/localization.json index c4e939c..52acd98 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -160,5 +160,14 @@ "Portuguese (Brazil)": "Essa versão do PolyMod não foi desenvolvida para a versão atual do aplicativo e pode não funcionar corretamente!", "Elyrion": "πȱ∫ỹmȱδ ƒƒƒƒƒƒƒ ŋȱŧ ȱrrȱ #₺rr∑ŋŧ ƒƒƒƒƒƒƒ ỹ maỹ ŋȱŧ ~ȱr§ #ȱrr∑#ŧ∫ỹ!", "German (Germany)": "Diese Version von PolyMod ist nicht für die aktuelle Version der Anwendung ausgelegt und könnte nicht funktionieren!" + }, + "polymod_hub_updatespriteinfos": { + "English": "UPDATE SPRITES" + }, + "polymod.spriteinfo.updated": { + "English": "Sprite info for mod {0} updated." + }, + "polymod.spriteinfo.notupdated": { + "English": "No sprite infos were found." } } diff --git a/src/Loader.cs b/src/Loader.cs index 7446e6a..48a7cc3 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -303,24 +303,44 @@ public static void LoadSpriteFile(Mod mod, Mod.File file) Registry.sprites.Add(name, sprite); } - public static void LoadSpriteInfoFile(Mod mod, Mod.File file) + public static void UpdateSprite(string name) + { + if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name)) + { + Visual.SpriteInfo spriteData = Registry.spriteInfos[name]; + Sprite sprite = Visual.BuildSpriteWithTexture(Registry.sprites[name].texture, spriteData.pivot, (float)spriteData.pixelsPerUnit); + GameManager.GetSpriteAtlasManager().cachedSprites["Heads"][name] = sprite; + Registry.sprites[name] = sprite; + } + } + + public static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file) { try { - Registry.spriteInfos = Registry.spriteInfos - .Concat(JsonSerializer.Deserialize>( - file.bytes, - new JsonSerializerOptions() - { - Converters = { new Vector2Json() }, - } - )!) - .ToDictionary(e => e.Key, e => e.Value); - Plugin.logger.LogInfo($"Registried sprite data from {mod.id} mod"); + var deserialized = JsonSerializer.Deserialize>( + file.bytes, + new JsonSerializerOptions() + { + Converters = { new Vector2Json() }, + } + ); + + if (deserialized != null) + { + foreach (var kvp in deserialized) + { + Registry.spriteInfos[kvp.Key] = kvp.Value; + } + } + + Plugin.logger.LogInfo($"Registered sprite data from {mod.id} mod"); + return deserialized; } catch (Exception e) { Plugin.logger.LogError($"Error on loading sprite data from {mod.id} mod: {e.Message}"); + return null; } } diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index 44582b1..2bc9e50 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -6,12 +6,14 @@ using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; +using static PopupBase; namespace PolyMod.Managers; internal static class Hub { private const string HEADER_PREFIX = ""; private const string HEADER_POSTFIX = ""; + public static bool isLevelPopupActive = false; [HarmonyPrefix] [HarmonyPatch(typeof(SplashController), nameof(SplashController.LoadAndPlayClip))] @@ -141,6 +143,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) ) }; if (Plugin.config.debug) + { popupButtons.Add(new( "polymod.hub.dump", callback: (UIButtonBase.ButtonAction)((_, _) => @@ -159,10 +162,29 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) Path.Combine(Plugin.DUMPED_DATA_PATH, $"localization_{category}.csv"), LocalizationManager.Sources[0].Export_CSV(category) ); + foreach (KeyValuePair entry in Registry.mods) + { + foreach (Mod.File file in entry.Value.files) + { + if (Path.GetFileName(file.name) == "sprites.json") + { + File.WriteAllBytes(Path.Combine(Plugin.DUMPED_DATA_PATH, $"sprites_{entry.Key}.json"), file.bytes); + } + } + } NotificationManager.Notify(Localization.Get("polymod.hub.dumped")); }), closesPopup: false )); + popupButtons.Add(new( + "polymod.hub.updatespriteinfos", + callback: (UIButtonBase.ButtonAction)((_, _) => + { + UpdateSpriteInfos(); + }), + closesPopup: false + )); + } popup.buttonData = popupButtons.ToArray(); popup.ShowSetWidth(1000); } @@ -187,6 +209,84 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) } } + [HarmonyPostfix] + [HarmonyPatch(typeof(GameManager), nameof(GameManager.Update))] + private static void GameManager_Update() + { + if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Tab) && !isLevelPopupActive) + { + ShowLevelPopup(); + } + } + + internal static void UpdateSpriteInfos() + { + string message = string.Empty; + Directory.CreateDirectory(Plugin.DUMPED_DATA_PATH); + + foreach (var file in Directory.GetFiles(Plugin.DUMPED_DATA_PATH)) + { + string? name = Path.GetFileNameWithoutExtension(file); + List subnames = new(); + if (name.Contains("sprites_")) + { + subnames = name.Split('_').ToList(); + Mod.File spriteInfo = new(Path.GetFileNameWithoutExtension(file), File.ReadAllBytes(file)); + Dictionary? deserialized = Loader.LoadSpriteInfoFile(Registry.mods[subnames[1]], spriteInfo); + if (deserialized != null) + { + foreach (var kvp in deserialized) + { + Loader.UpdateSprite(kvp.Key); + } + message += Localization.Get("polymod.spriteinfo.updated", new Il2CppSystem.Object[] {subnames[1]}); + } + } + } + if (message == string.Empty) + { + message = Localization.Get("polymod.spriteinfo.notupdated"); + } + NotificationManager.Notify(message); + } + + internal static void ShowLevelPopup() + { + BasicPopup polymodPopup = PopupManager.GetBasicPopup(); + + polymodPopup.Header = "POLYMOD"; + polymodPopup.Description = ""; + + polymodPopup.buttonData = CreatePopupButtonData(); + polymodPopup.Show(); + } + + internal static PopupButtonData[] CreatePopupButtonData() + { + List popupButtons = new() + { + new(Localization.Get("buttons.back"), PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnBackButtonClicked, -1, true, null) + }; + + if (GameManager.Instance.isLevelLoaded) + { + popupButtons.Add(new PopupButtonData("UPDATE SPRITES", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSprites, -1, true, null)); + } + + return popupButtons.ToArray(); + + void OnUpdateSprites(int buttonId, BaseEventData eventData) + { + UpdateSpriteInfos(); + isLevelPopupActive = false; + } + + void OnBackButtonClicked(int buttonId, BaseEventData eventData) + { + isLevelPopupActive = false; + } + } + internal static void Init() { Harmony.CreateAndPatchAll(typeof(Hub)); diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index ff1cd84..620c9c6 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -538,6 +538,11 @@ public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixel texture.SetPixels(pixels); texture.filterMode = FilterMode.Trilinear; texture.Apply(); + return BuildSpriteWithTexture(texture, pivot, pixelsPerUnit); + } + + public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float pixelsPerUnit = 2112f) + { return Sprite.Create( texture, new(0, 0, texture.width, texture.height), From 41817ee7b43047f7e832df79162d48ef395bbfb0 Mon Sep 17 00:00:00 2001 From: mxkeljii <109823841+johnklipi@users.noreply.github.com> Date: Wed, 7 May 2025 07:38:20 +0200 Subject: [PATCH 14/23] #76 + #66 (#84) Added config settings to polymod hub and added preliminary settings reset on mods change --- resources/localization.json | 19 +++++++++- src/Managers/Compatibility.cs | 71 ++++++++++++++++++++++------------- src/Managers/Hub.cs | 56 ++++++++++++++++++++++++--- src/Managers/Main.cs | 13 +++---- src/Plugin.cs | 3 ++ 5 files changed, 119 insertions(+), 43 deletions(-) diff --git a/resources/localization.json b/resources/localization.json index 52acd98..c7d8f75 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -161,13 +161,28 @@ "Elyrion": "πȱ∫ỹmȱδ ƒƒƒƒƒƒƒ ŋȱŧ ȱrrȱ #₺rr∑ŋŧ ƒƒƒƒƒƒƒ ỹ maỹ ŋȱŧ ~ȱr§ #ȱrr∑#ŧ∫ỹ!", "German (Germany)": "Diese Version von PolyMod ist nicht für die aktuelle Version der Anwendung ausgelegt und könnte nicht funktionieren!" }, + "polymod_hub_config": { + "English": "SETTINGS" + }, + "polymod_hub_debugenable": { + "English": "ENABLE DEBUG" + }, + "polymod_hub_debugdisable": { + "English": "DISABLE DEBUG" + }, + "polymod_hub_debugswitch": { + "English": "Debug set to {0}." + }, "polymod_hub_updatespriteinfos": { "English": "UPDATE SPRITES" }, - "polymod.spriteinfo.updated": { + "polymod_spriteinfo_updated": { "English": "Sprite info for mod {0} updated." }, - "polymod.spriteinfo.notupdated": { + "polymod_spriteinfo_notupdated": { "English": "No sprite infos were found." + }, + "polymod_popup_header": { + "English": "POLYMOD" } } diff --git a/src/Managers/Compatibility.cs b/src/Managers/Compatibility.cs index d270cb4..b153249 100644 --- a/src/Managers/Compatibility.cs +++ b/src/Managers/Compatibility.cs @@ -6,14 +6,13 @@ namespace PolyMod.Managers; internal static class Compatibility { - internal static string signature = string.Empty; - internal static string looseSignature = string.Empty; + internal static string checksum = string.Empty; + internal static bool shouldResetSettings = false; private static bool sawSignatureWarning; - public static void HashSignatures(StringBuilder looseSignatureString, StringBuilder signatureString) + public static void HashSignatures(StringBuilder checksumString) { - looseSignature = Util.Hash(looseSignatureString); - signature = Util.Hash(signatureString); + checksum = Util.Hash(checksumString); } private static bool CheckSignatures(Action action, int id, BaseEventData eventData, Il2CppSystem.Guid gameId) @@ -24,15 +23,15 @@ private static bool CheckSignatures(Action action, int id, B return true; } - string[] signatures = { string.Empty, string.Empty }; + string signature = string.Empty; try { - signatures = File.ReadAllLines(Path.Combine(Application.persistentDataPath, $"{gameId}.signatures")); + signature = File.ReadAllText(Path.Combine(Application.persistentDataPath, $"{gameId}.signatures")); } catch { } - if (signatures[0] == string.Empty && signatures[1] == string.Empty) return true; + if (signature == string.Empty) return true; if (Plugin.config.debug) return true; - if (looseSignature != signatures[0]) + if (checksum != signature) { PopupManager.GetBasicPopup(new( Localization.Get("polymod.signature.mismatch"), @@ -43,23 +42,6 @@ private static bool CheckSignatures(Action action, int id, B )).Show(); return false; } - if (signature != signatures[1]) - { - PopupManager.GetBasicPopup(new( - Localization.Get("polymod.signature.mismatch"), - Localization.Get("polymod.signature.maybe.incompatible"), - new(new PopupBase.PopupButtonData[] { - new( - "OK", - callback: (UIButtonBase.ButtonAction)((int _, BaseEventData _) => { - sawSignatureWarning = true; - action(id, eventData); - }) - ) - }) - )).Show(); - return false; - } return true; } @@ -67,6 +49,22 @@ private static bool CheckSignatures(Action action, int id, B [HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))] private static void StartScreen_Start() { + string lastChecksum = checksum; + try + { + lastChecksum = new(File.ReadAllText(Plugin.CHECKSUM_PATH)); + } + catch (FileNotFoundException){} + + File.WriteAllText( + Plugin.CHECKSUM_PATH, + checksum + ); + if (lastChecksum != checksum) + { + shouldResetSettings = true; + } + Version incompatibilityWarningLastVersion = Plugin.POLYTOPIA_VERSION.CutRevision(); try { @@ -139,10 +137,29 @@ private static void ClientBase_CreateSession(GameSettings settings, Il2CppSystem { File.WriteAllLinesAsync( Path.Combine(Application.persistentDataPath, $"{gameId}.signatures"), - new string[] { looseSignature, signature } + new string[] { checksum } ); } + [HarmonyPrefix] + [HarmonyPatch(typeof(TribeSelectorScreen), nameof(TribeSelectorScreen.Show))] + private static bool TribeSelectorScreen_Show(bool instant = false) + { + if (shouldResetSettings) + { + RestorePreliminaryGameSettings(); + shouldResetSettings = false; + } + return true; + } + + internal static void RestorePreliminaryGameSettings() + { + GameManager.PreliminaryGameSettings.disabledTribes.Clear(); + GameManager.PreliminaryGameSettings.selectedSkins.Clear(); + GameManager.PreliminaryGameSettings.SaveToDisk(); + } + internal static void Init() { Harmony.CreateAndPatchAll(typeof(Compatibility)); diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index 2bc9e50..4135d48 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Cpp2IL.Core.Extensions; using HarmonyLib; using I2.Loc; @@ -13,6 +14,7 @@ internal static class Hub { private const string HEADER_PREFIX = ""; private const string HEADER_POSTFIX = ""; + private const int POPUP_WIDTH = 1400; public static bool isLevelPopupActive = false; [HarmonyPrefix] @@ -140,6 +142,13 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) "polymod.hub.discord", callback: (UIButtonBase.ButtonAction)((_, _) => NativeHelpers.OpenURL(Plugin.DISCORD_LINK, false)) + ), + new( + "polymod.hub.config", + callback: (UIButtonBase.ButtonAction)((_, _) => + { + ShowLevelPopup(); + }) ) }; if (Plugin.config.debug) @@ -186,7 +195,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) )); } popup.buttonData = popupButtons.ToArray(); - popup.ShowSetWidth(1000); + popup.ShowSetWidth(POPUP_WIDTH); } if (Main.dependencyCycle) @@ -254,8 +263,7 @@ internal static void ShowLevelPopup() { BasicPopup polymodPopup = PopupManager.GetBasicPopup(); - polymodPopup.Header = "POLYMOD"; - polymodPopup.Description = ""; + polymodPopup.Header = Localization.Get("polymod.popup.header"); polymodPopup.buttonData = CreatePopupButtonData(); polymodPopup.Show(); @@ -270,12 +278,48 @@ internal static PopupButtonData[] CreatePopupButtonData() if (GameManager.Instance.isLevelLoaded) { - popupButtons.Add(new PopupButtonData("UPDATE SPRITES", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSprites, -1, true, null)); + popupButtons.Add(new PopupButtonData(Localization.Get("polymod.hub.spriteinfo"), PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSpritesButtonClicked, -1, true, null)); + } + else + { + string debugButtonName = Localization.Get("polymod.hub.debugenable"); + if (Plugin.config.debug) + { + debugButtonName = Localization.Get("polymod.hub.debugdisable"); + } + popupButtons.Add(new PopupButtonData(debugButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnDebugButtonClicked, -1, true, null)); + //popupButtons.Add(new PopupButtonData("", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null)); + //popupButtons.Add(new PopupButtonData("", PopupButtonData.States.Disabled, (UIButtonBase.ButtonAction)OnIncludeAlphasButtonClicked, -1, true, null)); } - return popupButtons.ToArray(); - void OnUpdateSprites(int buttonId, BaseEventData eventData) + void OnDebugButtonClicked(int buttonId, BaseEventData eventData) + { + Plugin.config = new(debug: !Plugin.config.debug); + File.WriteAllText(Plugin.CONFIG_PATH, JsonSerializer.Serialize(Plugin.config)); + NotificationManager.Notify(Localization.Get("polymod.hub.debugswitch", new Il2CppSystem.Object[] { Plugin.config.debug })); + if (Plugin.config.debug) + { + BepInEx.ConsoleManager.CreateConsole(); + } + else + { + BepInEx.ConsoleManager.DetachConsole(); + } + isLevelPopupActive = false; + } + + void OnAutoUpdateButtonClicked(int buttonId, BaseEventData eventData) + { + isLevelPopupActive = false; + } + + void OnIncludeAlphasButtonClicked(int buttonId, BaseEventData eventData) + { + isLevelPopupActive = false; + } + + void OnUpdateSpritesButtonClicked(int buttonId, BaseEventData eventData) { UpdateSpriteInfos(); isLevelPopupActive = false; diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index ae86f5b..f4893fb 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -180,13 +180,13 @@ internal static void Init() dependencyCycle = !Loader.SortMods(Registry.mods); if (dependencyCycle) return; - StringBuilder looseSignatureString = new(); - StringBuilder signatureString = new(); + 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); @@ -198,14 +198,11 @@ internal static void Init() } if (!mod.client && id != "polytopia") { - looseSignatureString.Append(id); - looseSignatureString.Append(mod.version.Major); - - signatureString.Append(id); - signatureString.Append(mod.version.ToString()); + checksumString.Append(id); + checksumString.Append(mod.version.ToString()); } } - Compatibility.HashSignatures(looseSignatureString, signatureString); + Compatibility.HashSignatures(checksumString); stopwatch.Stop(); } diff --git a/src/Plugin.cs b/src/Plugin.cs index 86a49ea..05f7fb0 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -4,6 +4,7 @@ using BepInEx.Configuration; using BepInEx.Logging; using PolyMod.Managers; +using UnityEngine; namespace PolyMod; [BepInPlugin("com.polymod", "PolyMod", VERSION)] @@ -20,6 +21,8 @@ internal record PolyConfig( internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json"); internal static readonly string INCOMPATIBILITY_WARNING_LAST_VERSION_PATH = Path.Combine(BASE_PATH, "IncompatibilityWarningLastVersion"); + 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() { From 319b08d67416957f88400c78c0a34fa19fbbb2d5 Mon Sep 17 00:00:00 2001 From: mxkeljii <109823841+johnklipi@users.noreply.github.com> Date: Fri, 9 May 2025 10:50:43 +0200 Subject: [PATCH 15/23] Rewrote MRB's custom embark gld setting (#87) --- src/Loader.cs | 10 +++++++++ src/Managers/Main.cs | 50 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Loader.cs b/src/Loader.cs index 48a7cc3..dc49ae6 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -441,6 +441,16 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) Registry.tribePreviews[Util.GetJTokenName(token)] = preview; } } + foreach (JToken jtoken in patch.SelectTokens("$.unitData.*").ToArray()) + { + JObject token = jtoken.Cast(); + if (token["embarksTo"] != null) + { + string unitId = Util.GetJTokenName(token); + string embarkUnitId = token["embarksTo"].ToString(); + Main.embarkNames[unitId] = embarkUnitId; + } + } gld.Merge(patch, new() { MergeArrayHandling = MergeArrayHandling.Replace, MergeNullValueHandling = MergeNullValueHandling.Merge }); Plugin.logger.LogInfo($"Registried patch from {mod.id} mod"); } diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index f4893fb..761d17a 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -16,6 +16,9 @@ public static class Main internal static readonly Stopwatch stopwatch = new(); internal static bool fullyInitialized; internal static bool dependencyCycle; + internal static Dictionary embarkNames = new(); + internal static Dictionary embarkOverrides = new(); + internal static bool currentlyEmbarking = false; [HarmonyPrefix] @@ -30,6 +33,20 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb if (skin.skinData != null) __instance.skinData[(SkinType)skin.idx] = skin.skinData; } + foreach (KeyValuePair entry in embarkNames) + { + try + { + UnitData.Type unit = EnumCache.GetType(entry.Key); + UnitData.Type newUnit = EnumCache.GetType(entry.Value); + embarkOverrides[unit] = newUnit; + Plugin.logger.LogInfo($"Embark unit type for {entry.Key} is now {entry.Value}"); + } + catch + { + Plugin.logger.LogError($"Embark unit type for {entry.Key} is not valid: {entry.Value}"); + } + } fullyInitialized = true; } } @@ -131,7 +148,7 @@ private static void GameModeScreen_Init(GameModeScreen __instance) [HarmonyPrefix] [HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))] - public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) { + private static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) { float baseAngle = 360 / GameManager.GameState.GameLogicData.GetTechData(TechData.Type.Basic).techUnlocks.Count; float childAngle = 0f; if (parentItem != null) @@ -163,6 +180,37 @@ public static bool TechView_CreateNode(TechView __instance, TechData data, TechI return false; } + [HarmonyPrefix] + [HarmonyPatch(typeof(EmbarkAction), nameof(EmbarkAction.Execute))] + private static bool EmbarkAction_Execute_Prefix(EmbarkAction __instance, GameState gameState) + { + currentlyEmbarking = true; + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ActionUtils), nameof(ActionUtils.TrainUnit))] + private static bool ActionUtils_TrainUnit(ref UnitState __result, GameState gameState, PlayerState playerState, TileData tile, ref UnitData unitData) + { + if (tile == null) + { + return true; + } + if (tile.unit == null) + { + return true; + } + if (currentlyEmbarking) + { + if (embarkOverrides.TryGetValue(tile.unit.type, out UnitData.Type newType)) + { + gameState.GameLogicData.TryGetData(newType, out unitData); + } + currentlyEmbarking = false; + } + return true; + } + internal static void Init() { stopwatch.Start(); From 7ac84ff219adba543d8201093c1f5d8099dadb76 Mon Sep 17 00:00:00 2001 From: MartinRB45534 <55586336+MartinRB45534@users.noreply.github.com> Date: Tue, 20 May 2025 08:10:20 +0200 Subject: [PATCH 16/23] Attract resource type and terrain type support in gld (#88) * Can now choose which resource to attract and on which terrain. * Remove _postfix --- src/Loader.cs | 20 +++++++++-- src/Managers/Main.cs | 79 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index dc49ae6..80e6b63 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -273,7 +273,7 @@ public static void LoadLocalizationFile(Mod mod, Mod.File file) { Loc.BuildAndLoadLocalization(JsonSerializer .Deserialize>>(file.bytes)!); - Plugin.logger.LogInfo($"Registried localization from {mod.id} mod"); + Plugin.logger.LogInfo($"Registered localization from {mod.id} mod"); } catch (Exception e) { @@ -451,8 +451,24 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) Main.embarkNames[unitId] = embarkUnitId; } } + foreach (JToken jtoken in patch.SelectTokens("$.improvementData.*").ToArray()) + { + JObject token = jtoken.Cast(); + if (token["attractsResource"] != null) + { + string improvementId = Util.GetJTokenName(token); + string attractsId = token["attractsResource"].ToString(); + Main.attractsResourceNames[improvementId] = attractsId; + } + if (token["attractsToTerrain"] != null) + { + string improvementId = Util.GetJTokenName(token); + string attractsId = token["attractsToTerrain"].ToString(); + Main.attractsTerrainNames[improvementId] = attractsId; + } + } gld.Merge(patch, new() { MergeArrayHandling = MergeArrayHandling.Replace, MergeNullValueHandling = MergeNullValueHandling.Merge }); - Plugin.logger.LogInfo($"Registried patch from {mod.id} mod"); + Plugin.logger.LogInfo($"Registered patch from {mod.id} mod"); } catch (Exception e) { diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 761d17a..cd01ced 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -19,7 +19,10 @@ public static class Main internal static Dictionary embarkNames = new(); internal static Dictionary embarkOverrides = new(); internal static bool currentlyEmbarking = false; - + internal static Dictionary attractsResourceNames = new(); + internal static Dictionary attractsTerrainNames = new(); + internal static Dictionary attractsResourceOverrides = new(); + internal static Dictionary attractsTerrainOverrides = new(); [HarmonyPrefix] [HarmonyPatch(typeof(GameLogicData), nameof(GameLogicData.AddGameLogicPlaceholders))] @@ -47,6 +50,34 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb Plugin.logger.LogError($"Embark unit type for {entry.Key} is not valid: {entry.Value}"); } } + foreach (KeyValuePair entry in attractsResourceNames) + { + try + { + ImprovementData.Type improvement = EnumCache.GetType(entry.Key); + ResourceData.Type resource = EnumCache.GetType(entry.Value); + attractsResourceOverrides[improvement] = resource; + Plugin.logger.LogInfo($"Improvement {entry.Key} now attracts {entry.Value}"); + } + catch + { + Plugin.logger.LogError($"Improvement {entry.Key} resource type is not valid: {entry.Value}"); + } + } + foreach (KeyValuePair entry in attractsTerrainNames) + { + try + { + ImprovementData.Type improvement = EnumCache.GetType(entry.Key); + Polytopia.Data.TerrainData.Type terrain = EnumCache.GetType(entry.Value); + attractsTerrainOverrides[improvement] = terrain; + Plugin.logger.LogInfo($"Improvement {entry.Key} now attracts on {entry.Value}"); + } + catch + { + Plugin.logger.LogError($"Improvement {entry.Key} terrain type is not valid: {entry.Value}"); + } + } fullyInitialized = true; } } @@ -211,6 +242,52 @@ private static bool ActionUtils_TrainUnit(ref UnitState __result, GameState game return true; } + [HarmonyPostfix] + [HarmonyPatch(typeof(StartTurnAction), nameof(StartTurnAction.Execute))] + private static void StartTurnAction_Execute(StartTurnAction __instance, GameState state) + { + for (int i = state.ActionStack.Count - 1; i >= 0; i--) + { + if (state.ActionStack[i].GetActionType() == ActionType.CreateResource) + { + state.ActionStack.RemoveAt(i); + } + } + for (int i = 0; i < state.Map.Tiles.Length; i++) + { + TileData tileData = state.Map.Tiles[i]; + if (tileData.owner == __instance.PlayerId && tileData.improvement != null && state.CurrentTurn > 0U) + { + ImprovementData improvementData; + state.GameLogicData.TryGetData(tileData.improvement.type, out improvementData); + if (improvementData != null) + { + if (improvementData.HasAbility(ImprovementAbility.Type.Attract) && tileData.improvement.GetAge(state) % improvementData.growthRate == 0) + { + ResourceData.Type resourceType = ResourceData.Type.Game; + if (attractsResourceOverrides.TryGetValue(tileData.improvement.type, out ResourceData.Type newType)) + { + resourceType = newType; + } + Polytopia.Data.TerrainData.Type targetTerrain = Polytopia.Data.TerrainData.Type.Forest; + if (attractsTerrainOverrides.TryGetValue(tileData.improvement.type, out Polytopia.Data.TerrainData.Type newTerrain)) + { + targetTerrain = newTerrain; + } + foreach (TileData tileData2 in state.Map.GetArea(tileData.coordinates, 1, true, false)) + { + if (tileData2.owner == __instance.PlayerId && tileData2.improvement == null && tileData2.resource == null && tileData2.terrain == targetTerrain) + { + state.ActionStack.Add(new CreateResourceAction(__instance.PlayerId, resourceType, tileData2.coordinates, CreateResourceAction.CreateReason.Attract)); + break; + } + } + } + } + } + } + } + internal static void Init() { stopwatch.Start(); From c41e216c647e03b0f15fb800bda70f1847b0fb13 Mon Sep 17 00:00:00 2001 From: HighFlyer222 <66063913+HighFlyer-222@users.noreply.github.com> Date: Tue, 20 May 2025 14:46:47 +0200 Subject: [PATCH 17/23] Fix dynamic tech branches rotating the vanilla tech tree (#85) * Dynamic tech branch angle * Tech branch fixes * Fixed the tech tree being rotated somehow * Address review --------- Co-authored-by: Lubyanoy Ivan --- src/Managers/Main.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index cd01ced..87ebdba 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -179,26 +179,23 @@ private static void GameModeScreen_Init(GameModeScreen __instance) [HarmonyPrefix] [HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))] - private static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) { - float baseAngle = 360 / GameManager.GameState.GameLogicData.GetTechData(TechData.Type.Basic).techUnlocks.Count; + public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) + { + GameLogicData gameLogicData = GameManager.GameState.GameLogicData; + TribeData tribeData = gameLogicData.GetTribeData(GameManager.LocalPlayer.tribe); + float baseAngle = 360 / gameLogicData.GetOverride(gameLogicData.GetTechData(TechData.Type.Basic), tribeData).techUnlocks.Count; float childAngle = 0f; if (parentItem != null) - { childAngle = angle + baseAngle * (data.techUnlocks.Count - 1) / 2f; - } - GameLogicData gameLogicData = GameManager.GameState.GameLogicData; - TribeData tribeData = gameLogicData.GetTribeData(GameManager.LocalPlayer.tribe); - foreach (TechData techData in data.techUnlocks) + foreach (var techData in data.techUnlocks) { if (gameLogicData.TryGetData(techData.type, out TechData techData2)) { - TechData @override = GameManager.GameState.GameLogicData.GetOverride(techData, tribeData); + TechData @override = gameLogicData.GetOverride(techData, tribeData); TechItem techItem = __instance.CreateTechItem(@override, parentItem, childAngle); __instance.currTechIdx++; if (@override.techUnlocks != null && @override.techUnlocks.Count > 0) - { __instance.CreateNode(@override, techItem, childAngle); - } childAngle -= baseAngle; } } From cfb74e6a228d35bf2028cc5303245a8aea409be5 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Thu, 12 Jun 2025 09:40:36 +0300 Subject: [PATCH 18/23] Refactor localization keys and improve JSON converter classes; update popup management and configuration handling --- resources/localization.json | 32 +++++++++++-------- src/Json/EnumCacheJson.cs | 1 + src/Json/Vector2Json.cs | 1 + src/Json/VersionJson.cs | 1 + src/Loader.cs | 7 +++- src/Managers/Audio.cs | 1 + src/Managers/Compatibility.cs | 17 +++++----- src/Managers/Hub.cs | 60 +++++++++++++++++++---------------- src/Managers/Loc.cs | 1 + src/Managers/Main.cs | 5 +-- src/Managers/Visual.cs | 7 ++-- src/Mod.cs | 1 + src/Plugin.cs | 26 ++++++++++++--- src/Registry.cs | 1 + src/Util.cs | 1 + 15 files changed, 101 insertions(+), 61 deletions(-) diff --git a/resources/localization.json b/resources/localization.json index c7d8f75..dfc7f03 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -162,27 +162,31 @@ "German (Germany)": "Diese Version von PolyMod ist nicht für die aktuelle Version der Anwendung ausgelegt und könnte nicht funktionieren!" }, "polymod_hub_config": { - "English": "SETTINGS" + "English": "CONFIG", + "Russian": "КОНФИГ" }, - "polymod_hub_debugenable": { - "English": "ENABLE DEBUG" + "polymod_hub_config_enable": { + "English": "ENABLE {0}", + "Russian": "ВКЛЮЧИТЬ {0}" }, - "polymod_hub_debugdisable": { - "English": "DISABLE DEBUG" + "polymod_hub_config_disable": { + "English": "DISABLE {0}", + "Russian": "ВЫЛЮЧИТЬ {0}" }, - "polymod_hub_debugswitch": { - "English": "Debug set to {0}." + "polymod_config_setto": { + "English": "{0} is set to {1}!", + "Russian": "{0} задан на {1}!" }, - "polymod_hub_updatespriteinfos": { - "English": "UPDATE SPRITES" + "polymod_hub_spriteinfo_update": { + "English": "UPDATE SPRITES", + "Russian": "ОБНОВИТЬ СПРАЙТЫ" }, "polymod_spriteinfo_updated": { - "English": "Sprite info for mod {0} updated." + "English": "Sprite info for mod {0} updated!", + "Russian": "Данные спрайтов обновлены для мода {0}!" }, "polymod_spriteinfo_notupdated": { - "English": "No sprite infos were found." - }, - "polymod_popup_header": { - "English": "POLYMOD" + "English": "No sprite infos were found!", + "Russian": "Данные спрайтов не найдены!" } } diff --git a/src/Json/EnumCacheJson.cs b/src/Json/EnumCacheJson.cs index 8ee87be..9b98432 100644 --- a/src/Json/EnumCacheJson.cs +++ b/src/Json/EnumCacheJson.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; namespace PolyMod.Json; + internal class EnumCacheJson : JsonConverter where T : struct, Enum { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Json/Vector2Json.cs b/src/Json/Vector2Json.cs index 628c94f..847cc62 100644 --- a/src/Json/Vector2Json.cs +++ b/src/Json/Vector2Json.cs @@ -3,6 +3,7 @@ using UnityEngine; namespace PolyMod.Json; + internal class Vector2Json : JsonConverter { public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Json/VersionJson.cs b/src/Json/VersionJson.cs index 38c08ec..5ccf7ec 100644 --- a/src/Json/VersionJson.cs +++ b/src/Json/VersionJson.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; namespace PolyMod.Json; + internal class VersionJson : JsonConverter { public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Loader.cs b/src/Loader.cs index 80e6b63..7f49ec0 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -17,6 +17,7 @@ using UnityEngine; namespace PolyMod; + public static class Loader { internal static Dictionary typeMappings = new() @@ -308,7 +309,11 @@ public static void UpdateSprite(string name) if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name)) { Visual.SpriteInfo spriteData = Registry.spriteInfos[name]; - Sprite sprite = Visual.BuildSpriteWithTexture(Registry.sprites[name].texture, spriteData.pivot, (float)spriteData.pixelsPerUnit); + Sprite sprite = Visual.BuildSpriteWithTexture( + Registry.sprites[name].texture, + spriteData.pivot, + spriteData.pixelsPerUnit + ); GameManager.GetSpriteAtlasManager().cachedSprites["Heads"][name] = sprite; Registry.sprites[name] = sprite; } diff --git a/src/Managers/Audio.cs b/src/Managers/Audio.cs index 7eecf93..f93ee78 100644 --- a/src/Managers/Audio.cs +++ b/src/Managers/Audio.cs @@ -4,6 +4,7 @@ using UnityEngine.Networking; namespace PolyMod.Managers; + public static class Audio { [HarmonyPostfix] diff --git a/src/Managers/Compatibility.cs b/src/Managers/Compatibility.cs index b153249..89afd24 100644 --- a/src/Managers/Compatibility.cs +++ b/src/Managers/Compatibility.cs @@ -4,6 +4,7 @@ using UnityEngine.EventSystems; namespace PolyMod.Managers; + internal static class Compatibility { internal static string checksum = string.Empty; @@ -54,7 +55,7 @@ private static void StartScreen_Start() { lastChecksum = new(File.ReadAllText(Plugin.CHECKSUM_PATH)); } - catch (FileNotFoundException){} + catch (FileNotFoundException) { } File.WriteAllText( Plugin.CHECKSUM_PATH, @@ -65,16 +66,14 @@ private static void StartScreen_Start() shouldResetSettings = true; } - Version incompatibilityWarningLastVersion = Plugin.POLYTOPIA_VERSION.CutRevision(); - try - { - incompatibilityWarningLastVersion = new(File.ReadAllText(Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_PATH)); - } - catch (FileNotFoundException) { } + Version incompatibilityWarningLastVersion = new(PlayerPrefs.GetString( + Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, + Plugin.POLYTOPIA_VERSION.CutRevision().ToString() + )); if (VersionManager.SemanticVersion.Cast().CutRevision() > incompatibilityWarningLastVersion) { - File.WriteAllText( - Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_PATH, + PlayerPrefs.SetString( + Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, VersionManager.SemanticVersion.Cast().CutRevision().ToString() ); PopupManager.GetBasicPopup(new( diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index 4135d48..0b7c562 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -10,12 +10,13 @@ using static PopupBase; namespace PolyMod.Managers; + internal static class Hub { private const string HEADER_PREFIX = ""; private const string HEADER_POSTFIX = ""; private const int POPUP_WIDTH = 1400; - public static bool isLevelPopupActive = false; + public static bool isConfigPopupActive = false; [HarmonyPrefix] [HarmonyPatch(typeof(SplashController), nameof(SplashController.LoadAndPlayClip))] @@ -147,7 +148,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) "polymod.hub.config", callback: (UIButtonBase.ButtonAction)((_, _) => { - ShowLevelPopup(); + ShowConfigPopup(); }) ) }; @@ -186,7 +187,7 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) closesPopup: false )); popupButtons.Add(new( - "polymod.hub.updatespriteinfos", + "polymod.hub.spriteinfo.update", callback: (UIButtonBase.ButtonAction)((_, _) => { UpdateSpriteInfos(); @@ -222,9 +223,9 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) [HarmonyPatch(typeof(GameManager), nameof(GameManager.Update))] private static void GameManager_Update() { - if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Tab) && !isLevelPopupActive) + if (Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.Tab) && !isConfigPopupActive) { - ShowLevelPopup(); + ShowConfigPopup(); } } @@ -248,7 +249,7 @@ internal static void UpdateSpriteInfos() { Loader.UpdateSprite(kvp.Key); } - message += Localization.Get("polymod.spriteinfo.updated", new Il2CppSystem.Object[] {subnames[1]}); + message += Localization.Get("polymod.spriteinfo.updated", new Il2CppSystem.Object[] { subnames[1] }); } } } @@ -259,17 +260,18 @@ internal static void UpdateSpriteInfos() NotificationManager.Notify(message); } - internal static void ShowLevelPopup() + internal static void ShowConfigPopup() { BasicPopup polymodPopup = PopupManager.GetBasicPopup(); - polymodPopup.Header = Localization.Get("polymod.popup.header"); + polymodPopup.Header = Localization.Get("polymod.hub.config"); + polymodPopup.Description = ""; - polymodPopup.buttonData = CreatePopupButtonData(); + polymodPopup.buttonData = CreateConfigPopupButtonData(); polymodPopup.Show(); } - internal static PopupButtonData[] CreatePopupButtonData() + internal static PopupButtonData[] CreateConfigPopupButtonData() { List popupButtons = new() { @@ -278,14 +280,20 @@ internal static PopupButtonData[] CreatePopupButtonData() if (GameManager.Instance.isLevelLoaded) { - popupButtons.Add(new PopupButtonData(Localization.Get("polymod.hub.spriteinfo"), PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSpritesButtonClicked, -1, true, null)); + popupButtons.Add(new PopupButtonData(Localization.Get("polymod.hub.spriteinfo.update"), PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSpritesButtonClicked, -1, true, null)); } else { - string debugButtonName = Localization.Get("polymod.hub.debugenable"); + string debugButtonName = Localization.Get( + "polymod.hub.config.enable", + new Il2CppSystem.Object[] { "DEBUG" } + ); if (Plugin.config.debug) { - debugButtonName = Localization.Get("polymod.hub.debugdisable"); + debugButtonName = Localization.Get( + "polymod.hub.config.disable", + new Il2CppSystem.Object[] { "DEBUG" } + ); } popupButtons.Add(new PopupButtonData(debugButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnDebugButtonClicked, -1, true, null)); //popupButtons.Add(new PopupButtonData("", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null)); @@ -296,38 +304,34 @@ internal static PopupButtonData[] CreatePopupButtonData() void OnDebugButtonClicked(int buttonId, BaseEventData eventData) { Plugin.config = new(debug: !Plugin.config.debug); - File.WriteAllText(Plugin.CONFIG_PATH, JsonSerializer.Serialize(Plugin.config)); - NotificationManager.Notify(Localization.Get("polymod.hub.debugswitch", new Il2CppSystem.Object[] { Plugin.config.debug })); - if (Plugin.config.debug) - { - BepInEx.ConsoleManager.CreateConsole(); - } - else - { - BepInEx.ConsoleManager.DetachConsole(); - } - isLevelPopupActive = false; + Plugin.WriteConfig(); + Plugin.UpdateConsole(); + NotificationManager.Notify(Localization.Get( + "polymod.config.setto", + new Il2CppSystem.Object[] { "Debug", Plugin.config.debug } + )); + isConfigPopupActive = false; } void OnAutoUpdateButtonClicked(int buttonId, BaseEventData eventData) { - isLevelPopupActive = false; + isConfigPopupActive = false; } void OnIncludeAlphasButtonClicked(int buttonId, BaseEventData eventData) { - isLevelPopupActive = false; + isConfigPopupActive = false; } void OnUpdateSpritesButtonClicked(int buttonId, BaseEventData eventData) { UpdateSpriteInfos(); - isLevelPopupActive = false; + isConfigPopupActive = false; } void OnBackButtonClicked(int buttonId, BaseEventData eventData) { - isLevelPopupActive = false; + isConfigPopupActive = false; } } diff --git a/src/Managers/Loc.cs b/src/Managers/Loc.cs index 7df0b66..5b1c70f 100644 --- a/src/Managers/Loc.cs +++ b/src/Managers/Loc.cs @@ -6,6 +6,7 @@ using Polytopia.Data; namespace PolyMod.Managers; + public static class Loc { [HarmonyPostfix] diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index 87ebdba..db9ab38 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -10,6 +10,7 @@ using UnityEngine; namespace PolyMod.Managers; + public static class Main { internal const int MAX_TECH_TIER = 100; @@ -179,8 +180,8 @@ private static void GameModeScreen_Init(GameModeScreen __instance) [HarmonyPrefix] [HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))] - public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) - { + public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle) + { GameLogicData gameLogicData = GameManager.GameState.GameLogicData; TribeData tribeData = gameLogicData.GetTribeData(GameManager.LocalPlayer.tribe); float baseAngle = 360 / gameLogicData.GetOverride(gameLogicData.GetTechData(TechData.Type.Basic), tribeData).techUnlocks.Count; diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index 620c9c6..1b4bfba 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -9,6 +9,7 @@ using System.Text.Json.Serialization; namespace PolyMod.Managers; + public static class Visual { public class PreviewTile @@ -69,7 +70,7 @@ void GetAtlas(SpriteAtlas spriteAtlas) string style = ""; foreach (string item in names) { - string upperitem = char.ToUpper(item[0]) + item.Substring(1); + string upperitem = char.ToUpper(item[0]) + item[1..]; if (EnumCache.TryGetType(item, out TribeData.Type tribe) || EnumCache.TryGetType(item, out SkinType skin) || EnumCache.TryGetType(upperitem, out TribeData.Type tribeUpper) || EnumCache.TryGetType(upperitem, out SkinType skinUpper)) { @@ -541,13 +542,13 @@ public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixel return BuildSpriteWithTexture(texture, pivot, pixelsPerUnit); } - public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float pixelsPerUnit = 2112f) + public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float? pixelsPerUnit = 2112f) { return Sprite.Create( texture, new(0, 0, texture.width, texture.height), pivot ?? new(0.5f, 0.5f), - pixelsPerUnit + pixelsPerUnit ?? 2112f ); } diff --git a/src/Mod.cs b/src/Mod.cs index 1e68eef..36e81c1 100644 --- a/src/Mod.cs +++ b/src/Mod.cs @@ -1,4 +1,5 @@ namespace PolyMod; + public class Mod { public record Dependency(string id, Version min, Version max, bool required = true); diff --git a/src/Plugin.cs b/src/Plugin.cs index 05f7fb0..c09811d 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -7,6 +7,7 @@ using UnityEngine; namespace PolyMod; + [BepInPlugin("com.polymod", "PolyMod", VERSION)] public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin { @@ -15,12 +16,12 @@ internal record PolyConfig( ); 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 INCOMPATIBILITY_WARNING_LAST_VERSION_PATH - = Path.Combine(BASE_PATH, "IncompatibilityWarningLastVersion"); internal static readonly string CHECKSUM_PATH = Path.Combine(BASE_PATH, "CHECKSUM"); internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy"; @@ -48,9 +49,9 @@ public override void Load() catch { config = new(); - File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config)); + WriteConfig(); } - if (!config.debug) ConsoleManager.DetachConsole(); + UpdateConsole(); logger = Log; ConfigFile.CoreConfig[new("Logging.Disk", "WriteUnityLog")].BoxedValue = true; @@ -70,4 +71,21 @@ internal static Stream GetResource(string id) $"{typeof(Plugin).Namespace}.resources.{id}" )!; } + + internal static void WriteConfig() + { + File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config)); + } + + internal static void UpdateConsole() + { + if (config.debug) + { + ConsoleManager.CreateConsole(); + } + else + { + ConsoleManager.DetachConsole(); + } + } } diff --git a/src/Registry.cs b/src/Registry.cs index 8b74429..3861034 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -5,6 +5,7 @@ using UnityEngine; namespace PolyMod; + public static class Registry { public static int autoidx = Plugin.AUTOIDX_STARTS_FROM; diff --git a/src/Util.cs b/src/Util.cs index d62e51b..fad25af 100644 --- a/src/Util.cs +++ b/src/Util.cs @@ -6,6 +6,7 @@ using Polytopia.Data; namespace PolyMod; + internal static class Util { internal static Il2CppSystem.Type WrapType() where T : class From f9816617bd24cb990ce47e0cc58a1084a5935978 Mon Sep 17 00:00:00 2001 From: IExploitableMan Date: Thu, 12 Jun 2025 09:46:09 +0300 Subject: [PATCH 19/23] Save/flush PlayerPrefs after updating the incompatibility warning version --- src/Managers/Compatibility.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Managers/Compatibility.cs b/src/Managers/Compatibility.cs index 89afd24..12f41fb 100644 --- a/src/Managers/Compatibility.cs +++ b/src/Managers/Compatibility.cs @@ -76,6 +76,7 @@ private static void StartScreen_Start() Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_KEY, VersionManager.SemanticVersion.Cast().CutRevision().ToString() ); + PlayerPrefs.Save(); PopupManager.GetBasicPopup(new( Localization.Get("polymod.version.mismatch"), Localization.Get("polymod.version.mismatch.description"), From 0321d6c1acd8736a32ecfe067643964a3be46ee4 Mon Sep 17 00:00:00 2001 From: mxkeljii <109823841+johnklipi@users.noreply.github.com> Date: Sun, 22 Jun 2025 10:58:50 +0200 Subject: [PATCH 20/23] Loader patch loading refactor + Custom weapons and sprites for them (#94) --- src/Loader.cs | 205 ++++++++++++++++++++++++----------------- src/Managers/Visual.cs | 29 +++++- 2 files changed, 148 insertions(+), 86 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index 7f49ec0..f68f8f4 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -32,9 +32,109 @@ public static class Loader { "tribeAbility", typeof(TribeAbility.Type) }, { "unitAbility", typeof(UnitAbility.Type) }, { "improvementAbility", typeof(ImprovementAbility.Type) }, - { "playerAbility", typeof(PlayerAbility.Type) } + { "playerAbility", typeof(PlayerAbility.Type) }, + { "weaponData", typeof(UnitData.WeaponEnum) } }; internal static List gamemodes = new(); + private static readonly Dictionary> typeHandlers = new() + { + [typeof(TribeData.Type)] = new((token, duringEnumCacheCreation) => + { + if (duringEnumCacheCreation) + { + Registry.customTribes.Add((TribeData.Type)Registry.autoidx); + token["style"] = Registry.climateAutoidx; + token["climate"] = Registry.climateAutoidx; + Registry.climateAutoidx++; + } + else + { + if (token["preview"] != null) + { + Visual.PreviewTile[] preview = JsonSerializer.Deserialize(token["preview"].ToString())!; + Registry.tribePreviews[Util.GetJTokenName(token)] = preview; + } + } + }), + + [typeof(UnitData.Type)] = new((token, duringEnumCacheCreation) => + { + if (duringEnumCacheCreation) + { + UnitData.Type unitPrefabType = UnitData.Type.Scout; + if (token["prefab"] != null) + { + string prefabId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString()); + if (Enum.TryParse(prefabId, out UnitData.Type parsedType)) + unitPrefabType = parsedType; + } + PrefabManager.units.TryAdd((int)(UnitData.Type)Registry.autoidx, PrefabManager.units[(int)unitPrefabType]); + } + else + { + if (token["embarksTo"] != null) + { + string unitId = Util.GetJTokenName(token); + string embarkUnitId = token["embarksTo"].ToString(); + Main.embarkNames[unitId] = embarkUnitId; + } + if (token["weapon"] != null) + { + string weaponString = token["weapon"].ToString(); + if (EnumCache.TryGetType(weaponString, out UnitData.WeaponEnum type)) + { + token["weapon"] = (int)type; + } + } + } + }), + + [typeof(ImprovementData.Type)] = new((token, duringEnumCacheCreation) => + { + if (duringEnumCacheCreation) + { + ImprovementData.Type improvementPrefabType = ImprovementData.Type.CustomsHouse; + if (token["prefab"] != null) + { + string prefabId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString()); + if (Enum.TryParse(prefabId, out ImprovementData.Type parsedType)) + improvementPrefabType = parsedType; + } + PrefabManager.improvements.TryAdd((ImprovementData.Type)Registry.autoidx, PrefabManager.improvements[improvementPrefabType]); + } + else + { + if (token["attractsResource"] != null) + { + string improvementId = Util.GetJTokenName(token); + string attractsId = token["attractsResource"].ToString(); + Main.attractsResourceNames[improvementId] = attractsId; + } + if (token["attractsToTerrain"] != null) + { + string improvementId = Util.GetJTokenName(token); + string attractsId = token["attractsToTerrain"].ToString(); + Main.attractsTerrainNames[improvementId] = attractsId; + } + } + }), + + [typeof(ResourceData.Type)] = new((token, duringEnumCacheCreation) => + { + if (duringEnumCacheCreation) + { + ResourceData.Type resourcePrefabType = ResourceData.Type.Game; + if (token["prefab"] != null) + { + string prefabId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString()); + if (Enum.TryParse(prefabId, out ResourceData.Type parsedType)) + resourcePrefabType = parsedType; + } + PrefabManager.resources.TryAdd((ResourceData.Type)Registry.autoidx, PrefabManager.resources[resourcePrefabType]); + } + }) + }; + public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite); public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite) @@ -368,66 +468,22 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) JObject? token = jtoken.TryCast(); if (token != null) { - if (token["idx"] != null && (int)token["idx"] == -1) + string dataType = Util.GetJTokenName(token, 2); + if (typeMappings.TryGetValue(dataType, out Type? targetType)) { - string id = Util.GetJTokenName(token); - string dataType = Util.GetJTokenName(token, 2); - token["idx"] = Registry.autoidx; - if (typeMappings.TryGetValue(dataType, out Type? targetType)) + if (token["idx"] != null && (int)token["idx"] == -1) { + string id = Util.GetJTokenName(token); + token["idx"] = Registry.autoidx; MethodInfo? methodInfo = typeof(EnumCache<>).MakeGenericType(targetType).GetMethod("AddMapping"); if (methodInfo != null) { methodInfo.Invoke(null, new object[] { id, Registry.autoidx }); methodInfo.Invoke(null, new object[] { id, Registry.autoidx }); - if (targetType == typeof(TribeData.Type)) - { - Registry.customTribes.Add((TribeData.Type)Registry.autoidx); - token["style"] = Registry.climateAutoidx; - token["climate"] = Registry.climateAutoidx; - Registry.climateAutoidx++; - } - else if (targetType == typeof(UnitData.Type)) - { - UnitData.Type unitPrefabType = UnitData.Type.Scout; - if (token["prefab"] != null) - { - TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo; - string prefabId = textInfo.ToTitleCase(token["prefab"].ToString()); - if (Enum.TryParse(prefabId, out UnitData.Type parsedType)) - { - unitPrefabType = parsedType; - } - } - PrefabManager.units.TryAdd((int)(UnitData.Type)Registry.autoidx, PrefabManager.units[(int)unitPrefabType]); - } - else if (targetType == typeof(ImprovementData.Type)) - { - ImprovementData.Type improvementPrefabType = ImprovementData.Type.CustomsHouse; - if (token["prefab"] != null) - { - TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo; - string prefabId = textInfo.ToTitleCase(token["prefab"].ToString()); - if (Enum.TryParse(prefabId, out ImprovementData.Type parsedType)) - { - improvementPrefabType = parsedType; - } - } - PrefabManager.improvements.TryAdd((ImprovementData.Type)Registry.autoidx, PrefabManager.improvements[improvementPrefabType]); - } - else if (targetType == typeof(ResourceData.Type)) + + if (typeHandlers.TryGetValue(targetType, out var handler)) { - ResourceData.Type resourcePrefabType = ResourceData.Type.Game; - if (token["prefab"] != null) - { - TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo; - string prefabId = textInfo.ToTitleCase(token["prefab"].ToString()); - if (Enum.TryParse(prefabId, out ResourceData.Type parsedType)) - { - resourcePrefabType = parsedType; - } - } - PrefabManager.resources.TryAdd((ResourceData.Type)Registry.autoidx, PrefabManager.resources[resourcePrefabType]); + handler(token, true); } Plugin.logger.LogInfo("Created mapping for " + targetType.ToString() + " with id " + id + " and index " + Registry.autoidx); Registry.autoidx++; @@ -436,40 +492,19 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) } } } - foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray()) - { - JObject token = jtoken.Cast(); - - if (token["preview"] != null) - { - Visual.PreviewTile[] preview = JsonSerializer.Deserialize(token["preview"].ToString())!; - Registry.tribePreviews[Util.GetJTokenName(token)] = preview; - } - } - foreach (JToken jtoken in patch.SelectTokens("$.unitData.*").ToArray()) - { - JObject token = jtoken.Cast(); - if (token["embarksTo"] != null) - { - string unitId = Util.GetJTokenName(token); - string embarkUnitId = token["embarksTo"].ToString(); - Main.embarkNames[unitId] = embarkUnitId; - } - } - foreach (JToken jtoken in patch.SelectTokens("$.improvementData.*").ToArray()) + foreach (JToken jtoken in patch.SelectTokens("$.*.*").ToArray()) { - JObject token = jtoken.Cast(); - if (token["attractsResource"] != null) - { - string improvementId = Util.GetJTokenName(token); - string attractsId = token["attractsResource"].ToString(); - Main.attractsResourceNames[improvementId] = attractsId; - } - if (token["attractsToTerrain"] != null) + JObject? token = jtoken.TryCast(); + if (token != null) { - string improvementId = Util.GetJTokenName(token); - string attractsId = token["attractsToTerrain"].ToString(); - Main.attractsTerrainNames[improvementId] = attractsId; + string dataType = Util.GetJTokenName(token, 2); + if (typeMappings.TryGetValue(dataType, out Type? targetType)) + { + if (typeHandlers.TryGetValue(targetType, out var handler)) + { + handler(token, false); + } + } } } gld.Merge(patch, new() { MergeArrayHandling = MergeArrayHandling.Replace, MergeNullValueHandling = MergeNullValueHandling.Merge }); diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index 1b4bfba..96b3350 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -36,6 +36,7 @@ public record SkinInfo(int idx, string id, SkinData? skinData); 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; #region General @@ -487,10 +488,36 @@ private static void PlayerInfoIcon_SetData(PlayerInfoIcon __instance, TribeData. private static void BasicPopup_Update(BasicPopup __instance) { int id = __instance.GetInstanceID(); - if (Visual.basicPopupWidths.ContainsKey(id)) + if (basicPopupWidths.ContainsKey(id)) __instance.rectTransform.SetWidth(basicPopupWidths[id]); } + [HarmonyPrefix] + [HarmonyPatch(typeof(Unit), nameof(Unit.Attack))] + private static bool Unit_Attack(Unit __instance, WorldCoordinates target, bool moveToTarget, Il2CppSystem.Action onComplete) + { + if (__instance.Owner != null) + { + attackerTribe = __instance.Owner.tribe; + } + return true; + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(WeaponGFX), nameof(WeaponGFX.SetSkin))] + private static void WeaponGFX_SetSkin(WeaponGFX __instance, SkinType skinType) + { + if (attackerTribe != TribeData.Type.None) + { + Sprite? sprite = Registry.GetSprite(__instance.defaultSprite.name, Util.GetStyle(attackerTribe, skinType)); + if (sprite != null) + { + __instance.spriteRenderer.sprite = sprite; + } + attackerTribe = TribeData.Type.None; + } + } + [HarmonyPostfix] [HarmonyPatch(typeof(PopupBase), nameof(PopupBase.Hide))] private static void PopupBase_Hide(PopupBase __instance) From c249d9735ee01dc04648f1fa106726354cb7fbfd Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 22 Jun 2025 11:52:28 +0200 Subject: [PATCH 21/23] Added tribe preview dump, Closes #90 --- src/Managers/Hub.cs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index 0b7c562..ff8abc1 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -3,6 +3,7 @@ using HarmonyLib; using I2.Loc; using Il2CppInterop.Runtime; +using Polytopia.Data; using TMPro; using UnityEngine; using UnityEngine.EventSystems; @@ -182,6 +183,35 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData) } } } + foreach (TribeData.Type type in Enum.GetValues(typeof(TribeData.Type))) + { + List previewTiles = new(); + SelectTribePopup popup = PopupManager.GetSelectTribePopup(); + for (int x = -3; x <= 3; x++) + { + for (int y = -7; y <= 7; y++) + { + Vector2Int pos = new Vector2Int(x, y); + if (popup.UIWorldPreview.worldPreviewData.TryGetData(pos, type, out UITileData tileData)) + { + Visual.PreviewTile previewTile = new Visual.PreviewTile + { + x = tileData.Position.x, + y = tileData.Position.y, + terrainType = tileData.terrainType, + resourceType = tileData.resourceType, + unitType = tileData.unitType, + improvementType = tileData.improvementType + }; + previewTiles.Add(previewTile); + } + } + } + File.WriteAllTextAsync( + Path.Combine(Plugin.DUMPED_DATA_PATH, $"preview_{type}.json"), + JsonSerializer.Serialize(previewTiles, new JsonSerializerOptions { WriteIndented = true }) + ); + } NotificationManager.Notify(Localization.Get("polymod.hub.dumped")); }), closesPopup: false From 1f9cd490021e8322d8cba9ae90d5a539fa37447b Mon Sep 17 00:00:00 2001 From: mxkeljii <109823841+johnklipi@users.noreply.github.com> Date: Sun, 29 Jun 2025 16:00:19 +0200 Subject: [PATCH 22/23] Prefabs (#95) --- src/Loader.cs | 119 +++++++++++++++++++++++++++++++++++++++-- src/Managers/Main.cs | 39 ++++++++++++++ src/Managers/Visual.cs | 40 ++++++++++++++ src/Registry.cs | 4 ++ 4 files changed, 197 insertions(+), 5 deletions(-) diff --git a/src/Loader.cs b/src/Loader.cs index f68f8f4..bdf79a2 100644 --- a/src/Loader.cs +++ b/src/Loader.cs @@ -61,14 +61,10 @@ public static class Loader { if (duringEnumCacheCreation) { - UnitData.Type unitPrefabType = UnitData.Type.Scout; if (token["prefab"] != null) { - string prefabId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString()); - if (Enum.TryParse(prefabId, out UnitData.Type parsedType)) - unitPrefabType = parsedType; + Registry.prefabNames.Add((int)(UnitData.Type)Registry.autoidx, CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString())); } - PrefabManager.units.TryAdd((int)(UnitData.Type)Registry.autoidx, PrefabManager.units[(int)unitPrefabType]); } else { @@ -458,6 +454,119 @@ public static void LoadAudioFile(Mod mod, Mod.File file) // TODO: issue #71 } + public static void LoadPrefabInfoFile(Mod mod, Mod.File file) + { + try + { + var prefab = JsonSerializer.Deserialize(file.bytes, new JsonSerializerOptions + { + Converters = { new Vector2Json() }, + PropertyNameCaseInsensitive = true, + }); + if (prefab == null || prefab.type != Visual.PrefabType.Unit) + return; + + var baseUnit = PrefabManager.GetPrefab(UnitData.Type.Warrior, TribeData.Type.Imperius, SkinType.Default); + if (baseUnit == null) + return; + + var unitInstance = GameObject.Instantiate(baseUnit); + if (unitInstance == null) + return; + + var spriteContainer = unitInstance.transform.GetChild(0); + var material = ClearExistingPartsAndExtractMaterial(spriteContainer); + + var visualParts = ApplyVisualParts(prefab.visualParts, spriteContainer, material); + + var svr = unitInstance.GetComponent(); + svr.visualParts = visualParts.ToArray(); + + GameObject.DontDestroyOnLoad(unitInstance.gameObject); + Registry.unitPrefabs.Add(prefab, unitInstance.GetComponent()); + + Plugin.logger.LogInfo($"Registered prefab info from {mod.id} mod"); + } + catch (Exception e) + { + Plugin.logger.LogError($"Error on loading prefab info from {mod.id} mod: {e.Message}"); + } + } + + private static Material? ClearExistingPartsAndExtractMaterial(Transform spriteContainer) + { + Material? material = null; + for (int i = 0; i < spriteContainer.childCount; i++) + { + var child = spriteContainer.GetChild(i); + if (child.gameObject.name == "Head") + { + var renderer = child.GetComponent(); + if (renderer != null) + material = renderer.material; + } + GameObject.Destroy(child.gameObject); + } + return material; + } + + private static List ApplyVisualParts( + List partInfos, + Transform spriteContainer, + Material? material) + { + List parts = new(); + + foreach (var info in partInfos) + { + parts.Add(CreateVisualPart(info, spriteContainer, material)); + } + + return parts; + } + + private static SkinVisualsReference.VisualPart CreateVisualPart( + Visual.VisualPartInfo info, + Transform parent, + Material? material) + { + var visualPartObj = new GameObject(info.gameObjectName); + visualPartObj.transform.SetParent(parent); + visualPartObj.transform.position = info.coordinates; + visualPartObj.transform.localScale = info.scale; + visualPartObj.transform.rotation = Quaternion.Euler(0f, 0f, info.rotation); + + var outlineObj = new GameObject("Outline"); + outlineObj.transform.SetParent(visualPartObj.transform); + outlineObj.transform.position = info.coordinates; + outlineObj.transform.localScale = info.scale; + outlineObj.transform.rotation = Quaternion.Euler(0f, 0f, info.rotation); + + var visualPart = new SkinVisualsReference.VisualPart + { + DefaultSpriteName = info.baseName, + visualPart = visualPartObj, + outline = outlineObj, + tintable = info.tintable + }; + + var renderer = visualPartObj.AddComponent(); + renderer.material = material; + renderer.sortingLayerName = "Units"; + renderer.sortingOrder = info.tintable ? 0 : 1; + + visualPart.renderer = new SkinVisualsReference.RendererUnion { spriteRenderer = renderer }; + + var outlineRenderer = outlineObj.AddComponent(); + outlineRenderer.material = material; + outlineRenderer.sortingLayerName = "Units"; + outlineRenderer.sortingOrder = -1; + + visualPart.outlineRenderer = new SkinVisualsReference.RendererUnion { spriteRenderer = outlineRenderer }; + + return visualPart; + } + public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch) { try diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs index db9ab38..68892ed 100644 --- a/src/Managers/Main.cs +++ b/src/Managers/Main.cs @@ -32,6 +32,28 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb if (!fullyInitialized) { Load(rootObject); + foreach (System.Collections.Generic.KeyValuePair item in Registry.prefabNames) + { + UnitData.Type unitPrefabType = UnitData.Type.Scout; + string prefabId = item.Value; + if (Enum.TryParse(prefabId, out UnitData.Type parsedType)) + { + unitPrefabType = parsedType; + PrefabManager.units.TryAdd(item.Key, PrefabManager.units[(int)unitPrefabType]); + } + else + { + KeyValuePair prefabInfo = Registry.unitPrefabs.FirstOrDefault(kv => kv.Key.name == prefabId); + if (!EqualityComparer.Default.Equals(prefabInfo.Key, default)) + { + PrefabManager.units.TryAdd(item.Key, prefabInfo.Value); + } + else + { + PrefabManager.units.TryAdd(item.Key, PrefabManager.units[(int)unitPrefabType]); + } + } + } foreach (Visual.SkinInfo skin in Registry.skinInfo) { if (skin.skinData != null) @@ -286,6 +308,15 @@ private static void StartTurnAction_Execute(StartTurnAction __instance, GameStat } } + [HarmonyPrefix] + [HarmonyPatch(typeof(Unit), nameof(Unit.CreateUnit))] + private static bool Unit_CreateUnit(Unit __instance, UnitData unitData, TribeData.Type tribe, SkinType unitSkin) + { + Unit unit = PrefabManager.GetPrefab(unitData.type, tribe, unitSkin); + if (unit == null) Console.Write("THIS FUCKING SHIT IS NULL WHAT THE FUCK"); + return true; + } + internal static void Init() { stopwatch.Start(); @@ -359,6 +390,14 @@ internal static void Load(JObject gameLogicdata) ); continue; } + if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$")) + { + Loader.LoadPrefabInfoFile( + mod, + file + ); + continue; + } switch (Path.GetExtension(file.name)) { diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs index 96b3350..e816f0b 100644 --- a/src/Managers/Visual.cs +++ b/src/Managers/Visual.cs @@ -37,6 +37,22 @@ public record SkinInfo(int idx, string id, SkinData? skinData); private static bool firstTimeOpeningPreview = true; private static UnitData.Type currentUnitTypeUI = UnitData.Type.None; private static TribeData.Type attackerTribe = TribeData.Type.None; + public enum PrefabType + { + Unit, + Improvement, + Resource + } + public record PrefabInfo(PrefabType type, string name, List visualParts); + public record VisualPartInfo( + string gameObjectName, + string baseName, + float rotation = 0f, + Vector2 coordinates = new Vector2(), + Vector2 scale = new Vector2(), + bool tintable = false + ); + private static bool enableOutlines = false; #region General @@ -105,6 +121,30 @@ private static void SpriteAtlasManager_DoSpriteLookup(ref SpriteAtlasManager.Spr #endregion #region Units + // lobotomy + + [HarmonyPrefix] + [HarmonyPatch(typeof(InteractionBar), nameof(InteractionBar.Show))] + private static bool InteractionBar_Show(InteractionBar __instance, bool instant, bool force) + { + enableOutlines = true; + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(UISpriteDuplicator), nameof(UISpriteDuplicator.CreateImage), typeof(SpriteRenderer), typeof(Transform), typeof(Transform), typeof(float), typeof(Vector2), typeof(bool))] + private static bool UISpriteDuplicator_CreateImage(SpriteRenderer spriteRenderer, Transform source, Transform destination, float scale, Vector2 offset, bool forceFullAlpha) + { + return !(spriteRenderer.sortingOrder == -1 && !enableOutlines); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(InteractionBar), nameof(InteractionBar.Show))] + private static void InteractionBar_Show_Postfix(InteractionBar __instance, bool instant, bool force) + { + enableOutlines = false; + } + [HarmonyPrefix] [HarmonyPatch(typeof(UIUnitRenderer), nameof(UIUnitRenderer.CreateUnit))] private static bool UIUnitRenderer_CreateUnit_Prefix(UIUnitRenderer __instance) diff --git a/src/Registry.cs b/src/Registry.cs index 3861034..207d225 100644 --- a/src/Registry.cs +++ b/src/Registry.cs @@ -14,6 +14,10 @@ public static class Registry internal static Dictionary mods = new(); public static Dictionary tribePreviews = new(); public static Dictionary spriteInfos = new(); + public static Dictionary prefabNames = new(); + public static Dictionary unitPrefabs = new(); + public static Dictionary resourcePrefabs = new(); + public static Dictionary improvementsPrefabs = new(); public static Dictionary assetBundles = new(); public static List customTribes = new(); public static List skinInfo = new(); From 3a6558bdc39a92e4e5805a3b2340282ee2f072c6 Mon Sep 17 00:00:00 2001 From: Lubyanoy Ivan Date: Mon, 30 Jun 2025 22:05:16 +0300 Subject: [PATCH 23/23] AutoUpdate (#74) --- installer/main.py | 19 +++-- resources/localization.json | 20 +++++ src/Managers/AutoUpdate.cs | 162 ++++++++++++++++++++++++++++++++++++ src/Managers/Hub.cs | 48 ++++++++--- src/Plugin.cs | 7 +- src/Util.cs | 23 ++++- 6 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 src/Managers/AutoUpdate.cs diff --git a/installer/main.py b/installer/main.py index 9454650..a4f1fef 100644 --- a/installer/main.py +++ b/installer/main.py @@ -15,8 +15,8 @@ "win32": "win", "darwin": "macos", }[sys.platform] -BEPINEX = f"733/BepInEx-Unity.IL2CPP-{OS}-x64-6.0.0-be.733%2B995f049" -POLYMOD = "https://github.com/PolyModdingTeam/PolyMod/releases/latest/download/PolyMod.dll" +BEPINEX = "https://polymod.dev/data/bepinex.txt" +POLYMOD = "https://api.github.com/repos/PolyModdingTeam/PolyMod/releases" def resource_path(path): @@ -54,6 +54,7 @@ def prepare(target): return path_entry.configure(state=customtkinter.DISABLED) browse_button.configure(state=customtkinter.DISABLED) + prerelease_checkbox.destroy() install_button.destroy() uninstall_button.destroy() progress_bar = customtkinter.CTkProgressBar(app, determinate_speed=50 / 2) @@ -65,13 +66,16 @@ def prepare(target): def install(path): to_zip( requests.get( - f"https://builds.bepinex.dev/projects/bepinex_be/{BEPINEX}.zip" + requests.get(BEPINEX).text.strip().replace("{os}", OS) ) ).extractall(path) progress_bar.step() + for release in requests.get(POLYMOD).json(): + if release["prerelease"] and not prerelease_checkbox.get(): continue + latest = release open(path + "/BepInEx/plugins/PolyMod.dll", "wb").write( - requests.get(POLYMOD).content + requests.get(latest["assets"][0]["browser_download_url"]).content ) progress_bar.step() @@ -133,6 +137,8 @@ def quit(): app, placeholder_text="Game path", width=228) browse_button = customtkinter.CTkButton( app, text="Browse", command=browse, width=1) +prerelease_checkbox = customtkinter.CTkCheckBox( + app, text="Prerelease", width=1) install_button = customtkinter.CTkButton( app, text="Install", command=lambda: prepare(install)) uninstall_button = customtkinter.CTkButton( @@ -140,7 +146,8 @@ def quit(): path_entry.grid(column=0, row=0, padx=5, pady=5) browse_button.grid(column=1, row=0, padx=(0, 5), pady=5) -install_button.grid(column=0, row=1, columnspan=2, padx=5, pady=5) -uninstall_button.grid(column=0, row=2, columnspan=2, padx=5, pady=5) +prerelease_checkbox.grid(column=0, row=1, columnspan=2, padx=5, pady=5) +install_button.grid(column=0, row=2, columnspan=2, padx=5, pady=5) +uninstall_button.grid(column=0, row=3, columnspan=2, padx=5, pady=5) app.mainloop() diff --git a/resources/localization.json b/resources/localization.json index dfc7f03..efc9a56 100644 --- a/resources/localization.json +++ b/resources/localization.json @@ -161,6 +161,26 @@ "Elyrion": "πȱ∫ỹmȱδ ƒƒƒƒƒƒƒ ŋȱŧ ȱrrȱ #₺rr∑ŋŧ ƒƒƒƒƒƒƒ ỹ maỹ ŋȱŧ ~ȱr§ #ȱrr∑#ŧ∫ỹ!", "German (Germany)": "Diese Version von PolyMod ist nicht für die aktuelle Version der Anwendung ausgelegt und könnte nicht funktionieren!" }, + "polymod_debug": { + "English": "Debug", + "Russian": "Дебаг" + }, + "polymod_autoupdate": { + "English": "Auto-update", + "Russian": "Автообновление" + }, + "polymod_autoupdate_alpha": { + "English": "Include alphas", + "Russian": "Include alphas" + }, + "polymod_autoupdate_description": { + "English": "New update available!", + "Russian": "Доступно новое обновление!" + }, + "polymod_autoupdate_update": { + "English": "Update", + "Russian": "Обновить" + }, "polymod_hub_config": { "English": "CONFIG", "Russian": "КОНФИГ" diff --git a/src/Managers/AutoUpdate.cs b/src/Managers/AutoUpdate.cs new file mode 100644 index 0000000..ef7a043 --- /dev/null +++ b/src/Managers/AutoUpdate.cs @@ -0,0 +1,162 @@ +using System.Diagnostics; +using System.IO.Compression; +using System.Text.Json; +using HarmonyLib; +using UnityEngine; + +namespace PolyMod.Managers; + +internal static class AutoUpdate +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))] + private static void StartScreen_Start() + { + if (!Plugin.config.autoUpdate) return; + if (Environment.GetEnvironmentVariable("WINEPREFIX") != null) + { + Plugin.logger.LogError("Autoupdate is not supported on Wine!"); + return; + } + HttpClient client = new(); + client.DefaultRequestHeaders.Add("User-Agent", "PolyMod"); + try + { + var json = JsonDocument.Parse( + client.GetAsync("https://api.github.com/repos/PolyModdingTeam/PolyMod/releases").UnwrapAsync() + .Content.ReadAsStringAsync().UnwrapAsync() + ); + JsonElement? latest = null; + for (int i = 0; i < json.RootElement.GetArrayLength(); i++) + { + var release = json.RootElement[i]; + if (release.GetProperty("prerelease").GetBoolean() && !Plugin.config.updatePrerelease) continue; + latest = release; + break; + } + string newVersion = latest?.GetProperty("tag_name").GetString()!.TrimStart('v')!; + if (newVersion.IsVersionOlderOrEqual(Plugin.VERSION)) return; + string os = Application.platform switch + { + RuntimePlatform.WindowsPlayer => "win", + RuntimePlatform.LinuxPlayer => "linux", + RuntimePlatform.OSXPlayer => "macos", + _ => "unknown", + }; + if (os == "unknown") + { + Plugin.logger.LogError("Unsupported platform for autoupdate!"); + return; + } + string bepinex_url = client + .GetAsync("https://polymod.dev/data/bepinex.txt").UnwrapAsync() + .Content.ReadAsStringAsync().UnwrapAsync() + .Replace("{os}", os); + void Update() + { + Time.timeScale = 0; + File.WriteAllBytes( + Path.Combine(Plugin.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); + ProcessStartInfo info = new() + { + WorkingDirectory = Path.Combine(Plugin.BASE_PATH), + CreateNoWindow = true, + }; + if (Application.platform == RuntimePlatform.WindowsPlayer) + { + string batchPath = Path.Combine(Plugin.BASE_PATH, "update.bat"); + File.WriteAllText(batchPath, $@" + @echo off + echo Waiting for Polytopia.exe to exit... + :waitloop + tasklist | findstr /I ""Polytopia.exe"" >nul + if not errorlevel 1 ( + timeout /T 1 >nul + goto waitloop + ) + + echo Updating... + robocopy ""New"" . /E /MOVE /NFL /NDL /NJH /NJS /NP >nul + rmdir /S /Q ""New"" + del /F /Q ""BepInEx\plugins\PolyMod.dll"" + move /Y ""PolyMod.new.dll"" ""BepInEx\plugins\PolyMod.dll"" + + echo Launching game... + start steam://rungameid/874390 + timeout /T 3 /NOBREAK >nul + exit + "); + info.FileName = "cmd.exe"; + info.Arguments = $"/C start \"\" \"{batchPath}\""; + info.WorkingDirectory = Plugin.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"); + File.WriteAllText(bashPath, $@" + #!/bin/bash + + echo ""Waiting for Polytopia to exit..."" + while pgrep -x ""Polytopia"" > /dev/null; do + sleep 1 + done + + echo ""Updating..."" + mv New/* . && rm -rf New + rm -f BepInEx/plugins/PolyMod.dll + mv -f PolyMod.new.dll BepInEx/plugins/PolyMod.dll + + echo ""Launching game..."" + xdg-open steam://rungameid/874390 & + + sleep 3 + exit 0 + "); + + System.Diagnostics.Process chmod = new System.Diagnostics.Process(); + chmod.StartInfo.FileName = "chmod"; + chmod.StartInfo.Arguments = $"+x \"{bashPath}\""; + chmod.StartInfo.UseShellExecute = false; + chmod.StartInfo.CreateNoWindow = true; + chmod.Start(); + chmod.WaitForExit(); + + info.FileName = "/bin/bash"; + info.Arguments = $"\"{bashPath}\""; + info.WorkingDirectory = Plugin.BASE_PATH; + info.CreateNoWindow = true; + info.UseShellExecute = false; + } + Process.Start(info); + Application.Quit(); + } + PopupManager.GetBasicPopup(new( + Localization.Get("polymod.autoupdate"), + Localization.Get("polymod.autoupdate.description"), + new(new PopupBase.PopupButtonData[] { + new( + "polymod.autoupdate.update", + PopupBase.PopupButtonData.States.None, + (Il2CppSystem.Action)Update + ) + })) + ).Show(); + } + catch (Exception e) + { + Plugin.logger.LogError($"Failed to check updates: {e.Message}"); + } + } + + internal static void Init() + { + Harmony.CreateAndPatchAll(typeof(AutoUpdate)); + } +} \ No newline at end of file diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs index ff8abc1..43b186f 100644 --- a/src/Managers/Hub.cs +++ b/src/Managers/Hub.cs @@ -298,6 +298,7 @@ internal static void ShowConfigPopup() polymodPopup.Description = ""; polymodPopup.buttonData = CreateConfigPopupButtonData(); + polymodPopup.ShowSetWidth(POPUP_WIDTH); polymodPopup.Show(); } @@ -315,41 +316,62 @@ internal static PopupButtonData[] CreateConfigPopupButtonData() else { string debugButtonName = Localization.Get( - "polymod.hub.config.enable", - new Il2CppSystem.Object[] { "DEBUG" } + Plugin.config.debug ? "polymod.hub.config.disable" : "polymod.hub.config.enable", + new Il2CppSystem.Object[] { Localization.Get("polymod.debug", + new Il2CppSystem.Object[]{}).ToUpperInvariant() } + ); + string autoUpdateButtonName = Localization.Get( + Plugin.config.autoUpdate ? "polymod.hub.config.disable" : "polymod.hub.config.enable", + new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate", + new Il2CppSystem.Object[]{}).ToUpperInvariant() } + ); + string includeAlphasButtonName = Localization.Get( + Plugin.config.updatePrerelease ? "polymod.hub.config.disable" : "polymod.hub.config.enable", + new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate.alpha", + new Il2CppSystem.Object[]{}).ToUpperInvariant() } ); - if (Plugin.config.debug) - { - debugButtonName = Localization.Get( - "polymod.hub.config.disable", - new Il2CppSystem.Object[] { "DEBUG" } - ); - } popupButtons.Add(new PopupButtonData(debugButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnDebugButtonClicked, -1, true, null)); - //popupButtons.Add(new PopupButtonData("", PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null)); - //popupButtons.Add(new PopupButtonData("", PopupButtonData.States.Disabled, (UIButtonBase.ButtonAction)OnIncludeAlphasButtonClicked, -1, true, null)); + popupButtons.Add(new PopupButtonData(autoUpdateButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnAutoUpdateButtonClicked, -1, true, null)); + popupButtons.Add(new PopupButtonData(includeAlphasButtonName, Plugin.config.autoUpdate ? PopupButtonData.States.None : PopupButtonData.States.Disabled, (UIButtonBase.ButtonAction)OnIncludeAlphasButtonClicked, -1, true, null)); } return popupButtons.ToArray(); void OnDebugButtonClicked(int buttonId, BaseEventData eventData) { - Plugin.config = new(debug: !Plugin.config.debug); + Plugin.config = new(debug: !Plugin.config.debug, autoUpdate: Plugin.config.autoUpdate, updatePrerelease: Plugin.config.updatePrerelease); Plugin.WriteConfig(); Plugin.UpdateConsole(); NotificationManager.Notify(Localization.Get( "polymod.config.setto", - new Il2CppSystem.Object[] { "Debug", Plugin.config.debug } + new Il2CppSystem.Object[] { Localization.Get("polymod.debug", + new Il2CppSystem.Object[]{}), Plugin.config.debug } )); isConfigPopupActive = false; } void OnAutoUpdateButtonClicked(int buttonId, BaseEventData eventData) { + Plugin.config = new(debug: Plugin.config.debug, autoUpdate: !Plugin.config.autoUpdate, updatePrerelease: Plugin.config.updatePrerelease); + Plugin.WriteConfig(); + Plugin.UpdateConsole(); + NotificationManager.Notify(Localization.Get( + "polymod.config.setto", + new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate", + new Il2CppSystem.Object[]{}), Plugin.config.autoUpdate } + )); isConfigPopupActive = false; } void OnIncludeAlphasButtonClicked(int buttonId, BaseEventData eventData) { + Plugin.config = new(debug: Plugin.config.debug, autoUpdate: Plugin.config.autoUpdate, updatePrerelease: !Plugin.config.updatePrerelease); + Plugin.WriteConfig(); + Plugin.UpdateConsole(); + NotificationManager.Notify(Localization.Get( + "polymod.config.setto", + new Il2CppSystem.Object[] { Localization.Get("polymod.autoupdate.alpha", + new Il2CppSystem.Object[]{}), Plugin.config.updatePrerelease } + )); isConfigPopupActive = false; } diff --git a/src/Plugin.cs b/src/Plugin.cs index c09811d..025258c 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -12,7 +12,9 @@ namespace PolyMod; public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin { internal record PolyConfig( - bool debug = false + bool debug = false, + bool autoUpdate = true, + bool updatePrerelease = false ); internal const int AUTOIDX_STARTS_FROM = 1000; @@ -49,8 +51,8 @@ public override void Load() catch { config = new(); - WriteConfig(); } + WriteConfig(); UpdateConsole(); logger = Log; ConfigFile.CoreConfig[new("Logging.Disk", "WriteUnityLog")].BoxedValue = true; @@ -58,6 +60,7 @@ public override void Load() Compatibility.Init(); Audio.Init(); + AutoUpdate.Init(); Loc.Init(); Visual.Init(); Hub.Init(); diff --git a/src/Util.cs b/src/Util.cs index fad25af..4d2fce2 100644 --- a/src/Util.cs +++ b/src/Util.cs @@ -6,7 +6,6 @@ using Polytopia.Data; namespace PolyMod; - internal static class Util { internal static Il2CppSystem.Type WrapType() where T : class @@ -31,11 +30,33 @@ internal static Version Cast(this Il2CppSystem.Version self) return new(self.ToString()); } + internal static T UnwrapAsync(this Task self) + { + return self.GetAwaiter().GetResult(); + } + internal static Version CutRevision(this Version self) { return new(self.Major, self.Minor, self.Build); } + internal static bool IsVersionOlderOrEqual(this string version1, string version2) + { + Version version1_ = new(version1.Split('-')[0]); + Version version2_ = new(version2.Split('-')[0]); + + if (version1_ < version2_) return true; + if (version1_ > version2_) return false; + + string pre1 = version1.Contains('-') ? version1.Split('-')[1] : ""; + string pre2 = version2.Contains('-') ? version2.Split('-')[1] : ""; + + if (string.IsNullOrEmpty(pre1) && !string.IsNullOrEmpty(pre2)) return false; + if (!string.IsNullOrEmpty(pre1) && string.IsNullOrEmpty(pre2)) return true; + + return string.Compare(pre1, pre2, StringComparison.Ordinal) <= 0; + } + internal static string GetStyle(TribeData.Type tribe, SkinType skin) { return skin != SkinType.Default ? EnumCache.GetName(skin) : EnumCache.GetName(tribe);