diff --git a/PolyMod.csproj b/PolyMod.csproj
index 7cf560e..e5bc0f7 100644
--- a/PolyMod.csproj
+++ b/PolyMod.csproj
@@ -10,8 +10,8 @@
https://polymod.dev/nuget/v3/index.json;
IL2CPP
- 1.1.9-pre
- 2.13.2.14360
+ 1.2.0-pre
+ 2.13.0.14218
PolyModdingTeam
The Battle of Polytopia's mod loader.
IDE0130
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 8a39199..efc9a56 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}",
@@ -160,5 +160,53 @@
"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_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": "КОНФИГ"
+ },
+ "polymod_hub_config_enable": {
+ "English": "ENABLE {0}",
+ "Russian": "ВКЛЮЧИТЬ {0}"
+ },
+ "polymod_hub_config_disable": {
+ "English": "DISABLE {0}",
+ "Russian": "ВЫЛЮЧИТЬ {0}"
+ },
+ "polymod_config_setto": {
+ "English": "{0} is set to {1}!",
+ "Russian": "{0} задан на {1}!"
+ },
+ "polymod_hub_spriteinfo_update": {
+ "English": "UPDATE SPRITES",
+ "Russian": "ОБНОВИТЬ СПРАЙТЫ"
+ },
+ "polymod_spriteinfo_updated": {
+ "English": "Sprite info for mod {0} updated!",
+ "Russian": "Данные спрайтов обновлены для мода {0}!"
+ },
+ "polymod_spriteinfo_notupdated": {
+ "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 8b3662f..bdf79a2 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;
@@ -16,6 +17,7 @@
using UnityEngine;
namespace PolyMod;
+
public static class Loader
{
internal static Dictionary typeMappings = new()
@@ -30,9 +32,115 @@ 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)
+ {
+ if (token["prefab"] != null)
+ {
+ Registry.prefabNames.Add((int)(UnitData.Type)Registry.autoidx, CultureInfo.CurrentCulture.TextInfo.ToTitleCase(token["prefab"]!.ToString()));
+ }
+ }
+ 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)
+ {
+ EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
+ EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
+ gamemodes.Add(new GameModeButtonsInformation(Registry.gameModesAutoidx, action, null, sprite));
+ Registry.gameModesAutoidx++;
+ }
+
public static void AddPatchDataType(string typeId, Type type)
{
if (!typeMappings.ContainsKey(typeId))
@@ -88,30 +196,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)
@@ -250,7 +370,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)
{
@@ -280,24 +400,48 @@ 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,
+ 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;
}
}
@@ -310,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
@@ -320,66 +577,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++;
@@ -388,18 +601,23 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}
}
- foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray())
+ foreach (JToken jtoken in patch.SelectTokens("$.*.*").ToArray())
{
- JObject token = jtoken.Cast();
-
- if (token["preview"] != null)
+ JObject? token = jtoken.TryCast();
+ if (token != null)
{
- Visual.PreviewTile[] preview = JsonSerializer.Deserialize(token["preview"].ToString())!;
- Registry.tribePreviews[Util.GetJTokenName(token)] = preview;
+ 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 });
- Plugin.logger.LogInfo($"Registried patch from {mod.id} mod");
+ Plugin.logger.LogInfo($"Registered patch from {mod.id} mod");
}
catch (Exception e)
{
@@ -408,6 +626,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/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/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/Compatibility.cs b/src/Managers/Compatibility.cs
index d270cb4..12f41fb 100644
--- a/src/Managers/Compatibility.cs
+++ b/src/Managers/Compatibility.cs
@@ -4,16 +4,16 @@
using UnityEngine.EventSystems;
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 +24,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 +43,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,18 +50,33 @@ private static bool CheckSignatures(Action action, int id, B
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
{
- Version incompatibilityWarningLastVersion = Plugin.POLYTOPIA_VERSION.CutRevision();
+ string lastChecksum = checksum;
try
{
- incompatibilityWarningLastVersion = new(File.ReadAllText(Plugin.INCOMPATIBILITY_WARNING_LAST_VERSION_PATH));
+ lastChecksum = new(File.ReadAllText(Plugin.CHECKSUM_PATH));
}
catch (FileNotFoundException) { }
+
+ File.WriteAllText(
+ Plugin.CHECKSUM_PATH,
+ checksum
+ );
+ if (lastChecksum != checksum)
+ {
+ shouldResetSettings = true;
+ }
+
+ 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()
);
+ PlayerPrefs.Save();
PopupManager.GetBasicPopup(new(
Localization.Get("polymod.version.mismatch"),
Localization.Get("polymod.version.mismatch.description"),
@@ -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 9bee0fc..43b186f 100644
--- a/src/Managers/Hub.cs
+++ b/src/Managers/Hub.cs
@@ -1,16 +1,23 @@
+using System.Text.Json;
using Cpp2IL.Core.Extensions;
using HarmonyLib;
+using I2.Loc;
using Il2CppInterop.Runtime;
+using Polytopia.Data;
using TMPro;
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 = "";
+ private const int POPUP_WIDTH = 1400;
+ public static bool isConfigPopupActive = false;
[HarmonyPrefix]
[HarmonyPatch(typeof(SplashController), nameof(SplashController.LoadAndPlayClip))]
@@ -121,7 +128,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";
}
@@ -134,30 +142,91 @@ 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))
+ ),
+ new(
+ "polymod.hub.config",
+ callback: (UIButtonBase.ButtonAction)((_, _) =>
+ {
+ ShowConfigPopup();
+ })
)
};
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)
+ );
+ 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);
+ }
+ }
+ }
+ 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
));
+ popupButtons.Add(new(
+ "polymod.hub.spriteinfo.update",
+ callback: (UIButtonBase.ButtonAction)((_, _) =>
+ {
+ UpdateSpriteInfos();
+ }),
+ closesPopup: false
+ ));
+ }
popup.buttonData = popupButtons.ToArray();
- popup.ShowSetWidth(1000);
+ popup.ShowSetWidth(POPUP_WIDTH);
}
if (Main.dependencyCycle)
@@ -180,6 +249,144 @@ 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) && !isConfigPopupActive)
+ {
+ ShowConfigPopup();
+ }
+ }
+
+ 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 ShowConfigPopup()
+ {
+ BasicPopup polymodPopup = PopupManager.GetBasicPopup();
+
+ polymodPopup.Header = Localization.Get("polymod.hub.config");
+ polymodPopup.Description = "";
+
+ polymodPopup.buttonData = CreateConfigPopupButtonData();
+ polymodPopup.ShowSetWidth(POPUP_WIDTH);
+ polymodPopup.Show();
+ }
+
+ internal static PopupButtonData[] CreateConfigPopupButtonData()
+ {
+ 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(Localization.Get("polymod.hub.spriteinfo.update"), PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnUpdateSpritesButtonClicked, -1, true, null));
+ }
+ else
+ {
+ string debugButtonName = Localization.Get(
+ 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() }
+ );
+ popupButtons.Add(new PopupButtonData(debugButtonName, PopupButtonData.States.None, (UIButtonBase.ButtonAction)OnDebugButtonClicked, -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, 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.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;
+ }
+
+ void OnUpdateSpritesButtonClicked(int buttonId, BaseEventData eventData)
+ {
+ UpdateSpriteInfos();
+ isConfigPopupActive = false;
+ }
+
+ void OnBackButtonClicked(int buttonId, BaseEventData eventData)
+ {
+ isConfigPopupActive = false;
+ }
+ }
+
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Hub));
diff --git a/src/Managers/Loc.cs b/src/Managers/Loc.cs
index 4033f60..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]
@@ -41,7 +42,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 d1fabaf..68892ed 100644
--- a/src/Managers/Main.cs
+++ b/src/Managers/Main.cs
@@ -2,19 +2,28 @@
using HarmonyLib;
using Newtonsoft.Json.Linq;
using Polytopia.Data;
+using PolytopiaBackendBase.Game;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
+using System.Text.RegularExpressions;
using UnityEngine;
namespace PolyMod.Managers;
+
public static class Main
{
internal const int MAX_TECH_TIER = 100;
internal static readonly Stopwatch stopwatch = new();
internal static bool fullyInitialized;
internal static bool dependencyCycle;
-
+ internal static 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))]
@@ -23,11 +32,75 @@ 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)
__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}");
+ }
+ }
+ 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;
}
}
@@ -70,6 +143,180 @@ private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string
return true;
}
+ [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++)
+ {
+ 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, __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;
+ }
+
+ for (int i = 0; i < Loader.gamemodes.Count; i++)
+ {
+ 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++)
+ {
+ GamemodeButton button = __instance.buttons[i];
+ var newData = button.gamemodeData.ToList();
+ foreach (var info in Loader.gamemodes)
+ {
+ 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
+ });
+ }
+ button.gamemodeData = newData.ToArray();
+
+ for (int j = 0; j < Loader.gamemodes.Count; j++)
+ {
+ Loader.GameModeButtonsInformation info = Loader.gamemodes[j];
+
+ if (info.buttonIndex == i)
+ {
+ button.SetGamemode(info.buttonIndex.Value);
+ }
+ }
+ }
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))]
+ 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;
+ foreach (var techData in data.techUnlocks)
+ {
+ if (gameLogicData.TryGetData(techData.type, out TechData techData2))
+ {
+ 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;
+ }
+ }
+ Il2CppSystem.Action onItemsRefreshed = __instance.OnItemsRefreshed;
+ if (onItemsRefreshed == null)
+ {
+ return false;
+ }
+ onItemsRefreshed.Invoke(__instance);
+ 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;
+ }
+
+ [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;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ [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();
@@ -77,6 +324,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()
@@ -86,13 +334,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);
@@ -104,14 +352,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();
}
@@ -131,21 +376,40 @@ 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")
- {
- Loader.LoadGameLogicDataPatch(mod, gameLogicdata, JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd()));
- }
if (Path.GetFileName(file.name) == "localization.json")
{
Loader.LoadLocalizationFile(mod, file);
+ continue;
}
- if (Path.GetExtension(file.name) == ".png")
+ if (Regex.IsMatch(Path.GetFileName(file.name), @"^patch(_.*)?\.json$"))
{
- Loader.LoadSpriteFile(mod, file);
+ Loader.LoadGameLogicDataPatch(
+ mod,
+ gameLogicdata,
+ JObject.Parse(new StreamReader(new MemoryStream(file.bytes)).ReadToEnd())
+ );
+ continue;
}
- if (Path.GetExtension(file.name) == ".wav")
+ if (Regex.IsMatch(Path.GetFileName(file.name), @"^prefab(_.*)?\.json$"))
+ {
+ Loader.LoadPrefabInfoFile(
+ mod,
+ file
+ );
+ continue;
+ }
+
+ 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/Managers/Visual.cs b/src/Managers/Visual.cs
index 8a0ac9f..e816f0b 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
@@ -35,6 +36,23 @@ 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;
+ 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
@@ -69,7 +87,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))
{
@@ -103,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)
@@ -189,6 +231,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 +284,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)
@@ -447,10 +528,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)
@@ -499,11 +606,16 @@ 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),
pivot ?? new(0.5f, 0.5f),
- pixelsPerUnit
+ pixelsPerUnit ?? 2112f
);
}
diff --git a/src/Mod.cs b/src/Mod.cs
index c8c179f..36e81c1 100644
--- a/src/Mod.cs
+++ b/src/Mod.cs
@@ -1,8 +1,9 @@
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 +14,7 @@ public enum Status
public string id;
public string? name;
+ public string? description;
public Version version;
public string[] authors;
public Dependency[]? dependencies;
@@ -24,6 +26,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;
diff --git a/src/Plugin.cs b/src/Plugin.cs
index 86a49ea..025258c 100644
--- a/src/Plugin.cs
+++ b/src/Plugin.cs
@@ -4,22 +4,28 @@
using BepInEx.Configuration;
using BepInEx.Logging;
using PolyMod.Managers;
+using UnityEngine;
namespace PolyMod;
+
[BepInPlugin("com.polymod", "PolyMod", VERSION)]
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;
+ 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";
internal static readonly List LOG_MESSAGES_IGNORE = new()
{
@@ -45,15 +51,16 @@ public override void Load()
catch
{
config = new();
- File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config));
}
- if (!config.debug) ConsoleManager.DetachConsole();
+ WriteConfig();
+ UpdateConsole();
logger = Log;
ConfigFile.CoreConfig[new("Logging.Disk", "WriteUnityLog")].BoxedValue = true;
Compatibility.Init();
Audio.Init();
+ AutoUpdate.Init();
Loc.Init();
Visual.Init();
Hub.Init();
@@ -67,4 +74,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 2c7a612..207d225 100644
--- a/src/Registry.cs
+++ b/src/Registry.cs
@@ -1,9 +1,11 @@
using LibCpp2IL;
using PolyMod.Managers;
using Polytopia.Data;
+using PolytopiaBackendBase.Game;
using UnityEngine;
namespace PolyMod;
+
public static class Registry
{
public static int autoidx = Plugin.AUTOIDX_STARTS_FROM;
@@ -12,9 +14,15 @@ 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();
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)
{
diff --git a/src/Util.cs b/src/Util.cs
index d62e51b..4d2fce2 100644
--- a/src/Util.cs
+++ b/src/Util.cs
@@ -30,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);