diff --git a/src/Json/EnumCacheJson.cs b/src/Json/EnumCacheJson.cs
index 9b98432..0aae58d 100644
--- a/src/Json/EnumCacheJson.cs
+++ b/src/Json/EnumCacheJson.cs
@@ -3,13 +3,30 @@
namespace PolyMod.Json;
+///
+/// Converts an to and from a JSON string using the .
+///
+/// The type of the enum.
internal class EnumCacheJson : JsonConverter where T : struct, Enum
{
+ ///
+ /// Reads and converts the JSON to an enum value.
+ ///
+ /// The reader.
+ /// The type to convert.
+ /// An object that specifies serialization options to use.
+ /// The converted value.
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return EnumCache.GetType(reader.GetString());
}
+ ///
+ /// Writes a specified value as JSON.
+ ///
+ /// The writer to write to.
+ /// The value to convert to JSON.
+ /// An object that specifies serialization options to use.
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStringValue(EnumCache.GetName(value));
diff --git a/src/Json/Vector2Json.cs b/src/Json/Vector2Json.cs
index 847cc62..4764952 100644
--- a/src/Json/Vector2Json.cs
+++ b/src/Json/Vector2Json.cs
@@ -4,8 +4,18 @@
namespace PolyMod.Json;
+///
+/// Converts a to and from a JSON array of two numbers.
+///
internal class Vector2Json : JsonConverter
{
+ ///
+ /// Reads and converts the JSON to a .
+ ///
+ /// The reader.
+ /// The type to convert.
+ /// An object that specifies serialization options to use.
+ /// The converted value.
public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
List values = new();
@@ -22,6 +32,12 @@ public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, Json
return new(values[0], values[1]);
}
+ ///
+ /// Writes a specified value as JSON.
+ ///
+ /// The writer to write to.
+ /// The value to convert to JSON.
+ /// An object that specifies serialization options to use.
public override void Write(Utf8JsonWriter writer, Vector2 value, JsonSerializerOptions options)
{
writer.WriteStartArray();
diff --git a/src/Json/VersionJson.cs b/src/Json/VersionJson.cs
index 5ccf7ec..54bb0a1 100644
--- a/src/Json/VersionJson.cs
+++ b/src/Json/VersionJson.cs
@@ -3,13 +3,29 @@
namespace PolyMod.Json;
+///
+/// Converts a to and from a JSON string.
+///
internal class VersionJson : JsonConverter
{
+ ///
+ /// Reads and converts the JSON to a .
+ ///
+ /// The reader.
+ /// The type to convert.
+ /// An object that specifies serialization options to use.
+ /// The converted value.
public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(reader.GetString()!);
}
+ ///
+ /// Writes a specified value as JSON.
+ ///
+ /// The writer to write to.
+ /// The value to convert to JSON.
+ /// An object that specifies serialization options to use.
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
diff --git a/src/Loader.cs b/src/Loader.cs
index bdf79a2..de33248 100644
--- a/src/Loader.cs
+++ b/src/Loader.cs
@@ -18,8 +18,14 @@
namespace PolyMod;
+///
+/// Handles loading of mods and their assets.
+///
public static class Loader
{
+ ///
+ /// Mappings from JSON data types to their corresponding C# types.
+ ///
internal static Dictionary typeMappings = new()
{
{ "tribeData", typeof(TribeData.Type) },
@@ -35,7 +41,15 @@ public static class Loader
{ "playerAbility", typeof(PlayerAbility.Type) },
{ "weaponData", typeof(UnitData.WeaponEnum) }
};
+
+ ///
+ /// List of custom game modes to be added.
+ ///
internal static List gamemodes = new();
+
+ ///
+ /// Handlers for processing specific data types during mod loading.
+ ///
private static readonly Dictionary> typeHandlers = new()
{
[typeof(TribeData.Type)] = new((token, duringEnumCacheCreation) =>
@@ -131,8 +145,21 @@ public static class Loader
})
};
+ ///
+ /// Represents information for a custom game mode button.
+ ///
+ /// The index of the game mode.
+ /// The action to perform when the button is clicked.
+ /// The index of the button in the UI.
+ /// The sprite for the button.
public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite);
+ ///
+ /// Adds a new game mode button.
+ ///
+ /// The unique identifier for the game mode.
+ /// The action to perform when the button is clicked.
+ /// The sprite for the button.
public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite)
{
EnumCache.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
@@ -141,12 +168,21 @@ public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action
Registry.gameModesAutoidx++;
}
+ ///
+ /// Adds a new data type for patching.
+ ///
+ /// The identifier for the data type in JSON.
+ /// The C# type corresponding to the identifier.
public static void AddPatchDataType(string typeId, Type type)
{
if (!typeMappings.ContainsKey(typeId))
typeMappings.Add(typeId, type);
}
+ ///
+ /// Loads all mods from the mods directory.
+ ///
+ /// A dictionary to populate with the loaded mods.
internal static void LoadMods(Dictionary mods)
{
Directory.CreateDirectory(Plugin.MODS_PATH);
@@ -159,6 +195,7 @@ internal static void LoadMods(Dictionary mods)
Mod.Manifest? manifest = null;
List files = new();
+ // Load mod from directory or zip archive
if (Directory.Exists(modContainer))
{
foreach (var file in Directory.GetFiles(modContainer))
@@ -196,6 +233,7 @@ internal static void LoadMods(Dictionary mods)
}
}
+ // Validate manifest
if (manifest == null)
{
Plugin.logger.LogError($"Mod manifest not found in {modContainer}");
@@ -234,6 +272,7 @@ internal static void LoadMods(Dictionary mods)
Plugin.logger.LogInfo($"Registered mod {manifest.id}");
}
+ // Check dependencies
foreach (var (id, mod) in mods)
{
foreach (var dependency in mod.dependencies ?? Array.Empty())
@@ -271,6 +310,11 @@ internal static void LoadMods(Dictionary mods)
}
}
+ ///
+ /// Sorts mods based on their dependencies using a topological sort.
+ ///
+ /// The dictionary of mods to sort.
+ /// True if the mods could be sorted (no circular dependencies), false otherwise.
internal static bool SortMods(Dictionary mods)
{
Stopwatch s = new();
@@ -330,6 +374,11 @@ internal static bool SortMods(Dictionary mods)
return true;
}
+ ///
+ /// Loads an assembly file from a mod.
+ ///
+ /// The mod the assembly belongs to.
+ /// The assembly file to load.
public static void LoadAssemblyFile(Mod mod, Mod.File file)
{
try
@@ -364,6 +413,11 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file)
}
}
+ ///
+ /// Loads a localization file from a mod.
+ ///
+ /// The mod the localization file belongs to.
+ /// The localization file to load.
public static void LoadLocalizationFile(Mod mod, Mod.File file)
{
try
@@ -378,6 +432,11 @@ public static void LoadLocalizationFile(Mod mod, Mod.File file)
}
}
+ ///
+ /// Loads a sprite file from a mod.
+ ///
+ /// The mod the sprite file belongs to.
+ /// The sprite file to load.
public static void LoadSpriteFile(Mod mod, Mod.File file)
{
string name = Path.GetFileNameWithoutExtension(file.name);
@@ -400,6 +459,10 @@ public static void LoadSpriteFile(Mod mod, Mod.File file)
Registry.sprites.Add(name, sprite);
}
+ ///
+ /// Updates a sprite with new information.
+ ///
+ /// The name of the sprite to update.
public static void UpdateSprite(string name)
{
if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name))
@@ -415,6 +478,12 @@ public static void UpdateSprite(string name)
}
}
+ ///
+ /// Loads a sprite info file from a mod.
+ ///
+ /// The mod the sprite info file belongs to.
+ /// The sprite info file to load.
+ /// A dictionary of sprite information, or null if an error occurred.
public static Dictionary? LoadSpriteInfoFile(Mod mod, Mod.File file)
{
try
@@ -445,6 +514,11 @@ public static void UpdateSprite(string name)
}
}
+ ///
+ /// Loads an audio file from a mod.
+ ///
+ /// The mod the audio file belongs to.
+ /// The audio file to load.
public static void LoadAudioFile(Mod mod, Mod.File file)
{
// AudioSource audioSource = new GameObject().AddComponent();
@@ -454,6 +528,11 @@ public static void LoadAudioFile(Mod mod, Mod.File file)
// TODO: issue #71
}
+ ///
+ /// Loads a prefab info file from a mod and creates a new unit prefab.
+ ///
+ /// The mod the prefab info file belongs to.
+ /// The prefab info file to load.
public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
{
try
@@ -493,6 +572,11 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
}
}
+ ///
+ /// Clears existing visual parts from a prefab and extracts the material.
+ ///
+ /// The transform containing the sprite parts.
+ /// The material of the original sprite parts.
private static Material? ClearExistingPartsAndExtractMaterial(Transform spriteContainer)
{
Material? material = null;
@@ -510,6 +594,13 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
return material;
}
+ ///
+ /// Applies new visual parts to a prefab.
+ ///
+ /// A list of visual part information.
+ /// The transform to add the parts to.
+ /// The material to use for the parts.
+ /// A list of the created visual parts.
private static List ApplyVisualParts(
List partInfos,
Transform spriteContainer,
@@ -525,6 +616,13 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
return parts;
}
+ ///
+ /// Creates a single visual part for a prefab.
+ ///
+ /// The information for the visual part.
+ /// The parent transform for the part.
+ /// The material to use for the part.
+ /// The created visual part.
private static SkinVisualsReference.VisualPart CreateVisualPart(
Visual.VisualPartInfo info,
Transform parent,
@@ -567,11 +665,18 @@ private static SkinVisualsReference.VisualPart CreateVisualPart(
return visualPart;
}
+ ///
+ /// Loads and applies a game logic data patch from a mod.
+ ///
+ /// The mod the patch belongs to.
+ /// The original game logic data.
+ /// The patch to apply.
public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
{
try
{
HandleSkins(gld, patch);
+ // First pass: add new enum values and create mappings
foreach (JToken jtoken in patch.SelectTokens("$.*.*").ToArray())
{
JObject? token = jtoken.TryCast();
@@ -601,6 +706,7 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}
}
+ // Second pass: apply special handling
foreach (JToken jtoken in patch.SelectTokens("$.*.*").ToArray())
{
JObject? token = jtoken.TryCast();
@@ -616,6 +722,7 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}
}
+ // Final pass: merge the patch into the game logic data
gld.Merge(patch, new() { MergeArrayHandling = MergeArrayHandling.Replace, MergeNullValueHandling = MergeNullValueHandling.Merge });
Plugin.logger.LogInfo($"Registered patch from {mod.id} mod");
}
@@ -626,6 +733,11 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}
+ ///
+ /// Loads an asset bundle from a mod.
+ ///
+ /// The mod the asset bundle belongs to.
+ /// The asset bundle file to load.
public static void LoadAssetBundle(Mod mod, Mod.File file)
{
Registry.assetBundles.Add(
@@ -634,6 +746,11 @@ public static void LoadAssetBundle(Mod mod, Mod.File file)
);
}
+ ///
+ /// Handles skin data from a patch.
+ ///
+ /// The original game logic data.
+ /// The patch to apply.
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 f93ee78..5712e5f 100644
--- a/src/Managers/Audio.cs
+++ b/src/Managers/Audio.cs
@@ -5,8 +5,14 @@
namespace PolyMod.Managers;
+///
+/// Manages custom audio for PolyMod.
+///
public static class Audio
{
+ ///
+ /// Patches the audio manager to set up data for custom tribes.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(AudioManager), nameof(AudioManager.SetupData))]
private static void AudioManager_SetupData()
@@ -20,6 +26,9 @@ private static void AudioManager_SetupData()
}
}
+ ///
+ /// Patches the music data to get custom nature audio clips.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(MusicData), nameof(MusicData.GetNatureAudioClip))]
private static bool MusicData_GetNatureAudioClip(ref AudioClip __result, TribeData.Type type, SkinType skinType)
@@ -33,6 +42,9 @@ private static bool MusicData_GetNatureAudioClip(ref AudioClip __result, TribeDa
return true;
}
+ ///
+ /// Patches the music data to get custom music audio clips.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(MusicData), nameof(MusicData.GetMusicAudioClip))]
private static bool MusicData_GetMusicAudioClip(ref AudioClip __result, TribeData.Type type, SkinType skinType)
@@ -46,6 +58,9 @@ private static bool MusicData_GetMusicAudioClip(ref AudioClip __result, TribeDat
return true;
}
+ ///
+ /// Patches the audio SFX data to get custom sound effect clips.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(AudioSFXData), nameof(AudioSFXData.GetClip))]
private static bool AudioSFXData_GetClip(ref AudioClip __result, SFXTypes id, SkinType skinType)
@@ -62,11 +77,20 @@ private static bool AudioSFXData_GetClip(ref AudioClip __result, SFXTypes id, Sk
return true;
}
+ ///
+ /// Builds an audio clip from raw byte data.
+ ///
+ /// The byte data of the audio file.
+ /// The created audio clip.
public static AudioClip BuildAudioClip(byte[] data)
{
+ // TODO: This is a placeholder. The actual implementation is tracked in issue #71.
return new AudioClip(new());
}
+ ///
+ /// Initializes the Audio manager by patching the necessary methods.
+ ///
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Audio));
diff --git a/src/Managers/AutoUpdate.cs b/src/Managers/AutoUpdate.cs
index ef7a043..1f94fb8 100644
--- a/src/Managers/AutoUpdate.cs
+++ b/src/Managers/AutoUpdate.cs
@@ -6,8 +6,14 @@
namespace PolyMod.Managers;
+///
+/// Manages the automatic update process for PolyMod.
+///
internal static class AutoUpdate
{
+ ///
+ /// Checks for updates when the start screen is shown.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
@@ -22,6 +28,7 @@ private static void StartScreen_Start()
client.DefaultRequestHeaders.Add("User-Agent", "PolyMod");
try
{
+ // Fetch release information from GitHub API
var json = JsonDocument.Parse(
client.GetAsync("https://api.github.com/repos/PolyModdingTeam/PolyMod/releases").UnwrapAsync()
.Content.ReadAsStringAsync().UnwrapAsync()
@@ -36,6 +43,8 @@ private static void StartScreen_Start()
}
string newVersion = latest?.GetProperty("tag_name").GetString()!.TrimStart('v')!;
if (newVersion.IsVersionOlderOrEqual(Plugin.VERSION)) return;
+
+ // Determine the correct BepInEx URL for the current OS
string os = Application.platform switch
{
RuntimePlatform.WindowsPlayer => "win",
@@ -52,9 +61,13 @@ private static void StartScreen_Start()
.GetAsync("https://polymod.dev/data/bepinex.txt").UnwrapAsync()
.Content.ReadAsStringAsync().UnwrapAsync()
.Replace("{os}", os);
+
+ // The actual update logic
void Update()
{
Time.timeScale = 0;
+
+ // Download new PolyMod DLL and BepInEx files
File.WriteAllBytes(
Path.Combine(Plugin.BASE_PATH, "PolyMod.new.dll"),
client.GetAsync(latest?.GetProperty("assets")[0].GetProperty("browser_download_url").GetString()!).UnwrapAsync()
@@ -62,6 +75,8 @@ void Update()
);
using ZipArchive bepinex = new(client.GetAsync(bepinex_url).UnwrapAsync().Content.ReadAsStream());
bepinex.ExtractToDirectory(Path.Combine(Plugin.BASE_PATH, "New"), overwriteFiles: true);
+
+ // Create and run the appropriate update script for the OS
ProcessStartInfo info = new()
{
WorkingDirectory = Path.Combine(Plugin.BASE_PATH),
@@ -137,6 +152,8 @@ exit 0
Process.Start(info);
Application.Quit();
}
+
+ // Show a popup to the user asking if they want to update
PopupManager.GetBasicPopup(new(
Localization.Get("polymod.autoupdate"),
Localization.Get("polymod.autoupdate.description"),
@@ -155,6 +172,9 @@ exit 0
}
}
+ ///
+ /// Initializes the AutoUpdate manager by patching the necessary methods.
+ ///
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(AutoUpdate));
diff --git a/src/Managers/Compatibility.cs b/src/Managers/Compatibility.cs
index c0b2321..4b04ee8 100644
--- a/src/Managers/Compatibility.cs
+++ b/src/Managers/Compatibility.cs
@@ -5,17 +5,35 @@
namespace PolyMod.Managers;
+///
+/// Manages compatibility checks for mods, including checksum verification and version warnings.
+///
internal static class Compatibility
{
+ ///
+ /// The checksum of all loaded mods.
+ ///
internal static string checksum = string.Empty;
+
+ ///
+ /// Whether the game settings should be reset due to a change in mods.
+ ///
internal static bool shouldResetSettings = false;
private static bool sawSignatureWarning;
+ ///
+ /// Hashes the signatures of all loaded mods to create a checksum.
+ ///
+ /// A string builder containing the signatures to hash.
public static void HashSignatures(StringBuilder checksumString)
{
checksum = Util.Hash(checksumString);
}
+ ///
+ /// Checks the signature of a saved game to ensure it is compatible with the current mods.
+ ///
+ /// True if the signatures match or if the check is ignored, false otherwise.
private static bool CheckSignatures(Action action, int id, BaseEventData eventData, Il2CppSystem.Guid gameId)
{
if (sawSignatureWarning)
@@ -61,6 +79,9 @@ private static bool CheckSignatures(Action action, int id, B
return true;
}
+ ///
+ /// Performs compatibility checks when the start screen is shown.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
@@ -108,6 +129,9 @@ private static void StartScreen_Start()
}
}
+ ///
+ /// Checks the signature of a pass-and-play game before loading it.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(GameInfoPopup), nameof(GameInfoPopup.OnMainButtonClicked))]
private static bool GameInfoPopup_OnMainButtonClicked(GameInfoPopup __instance, int id, BaseEventData eventData)
@@ -115,6 +139,9 @@ private static bool GameInfoPopup_OnMainButtonClicked(GameInfoPopup __instance,
return CheckSignatures(__instance.OnMainButtonClicked, id, eventData, __instance.gameId);
}
+ ///
+ /// Checks the signature of a single-player game before resuming it.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.OnResumeButtonClick))]
private static bool StartScreen_OnResumeButtonClick(StartScreen __instance, int id, BaseEventData eventData)
@@ -122,6 +149,9 @@ private static bool StartScreen_OnResumeButtonClick(StartScreen __instance, int
return CheckSignatures(__instance.OnResumeButtonClick, id, eventData, ClientBase.GetSinglePlayerSessions()[0]);
}
+ ///
+ /// Deletes the signature file of a pass-and-play game when it is deleted.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(GameInfoPopup), nameof(GameInfoPopup.DeletePaPGame))]
private static void ClientBase_DeletePassAndPlayGame(GameInfoPopup __instance)
@@ -129,6 +159,9 @@ private static void ClientBase_DeletePassAndPlayGame(GameInfoPopup __instance)
File.Delete(Path.Combine(Application.persistentDataPath, $"{__instance.gameId}.signatures"));
}
+ ///
+ /// Deletes the signature files of all single-player games when they are deleted.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(ClientBase), nameof(ClientBase.DeleteSinglePlayerGames))]
private static void ClientBase_DeleteSinglePlayerGames()
@@ -139,6 +172,9 @@ private static void ClientBase_DeleteSinglePlayerGames()
}
}
+ ///
+ /// Deletes the signature file of a game when the match ends.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(GameManager), nameof(GameManager.MatchEnded))]
private static void GameManager_MatchEnded(bool localPlayerIsWinner, ScoreDetails scoreDetails, byte winnerId)
@@ -146,6 +182,9 @@ private static void GameManager_MatchEnded(bool localPlayerIsWinner, ScoreDetail
File.Delete(Path.Combine(Application.persistentDataPath, $"{GameManager.Client.gameId}.signatures"));
}
+ ///
+ /// Creates a signature file when a new game session is created.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(ClientBase), nameof(ClientBase.CreateSession), typeof(GameSettings), typeof(Il2CppSystem.Guid))]
private static void ClientBase_CreateSession(GameSettings settings, Il2CppSystem.Guid gameId)
@@ -156,6 +195,9 @@ private static void ClientBase_CreateSession(GameSettings settings, Il2CppSystem
);
}
+ ///
+ /// Resets game settings if necessary when the tribe selector screen is shown.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(TribeSelectorScreen), nameof(TribeSelectorScreen.Show))]
private static bool TribeSelectorScreen_Show(bool instant = false)
@@ -168,6 +210,9 @@ private static bool TribeSelectorScreen_Show(bool instant = false)
return true;
}
+ ///
+ /// Restores the preliminary game settings to their default values.
+ ///
internal static void RestorePreliminaryGameSettings()
{
GameManager.PreliminaryGameSettings.disabledTribes.Clear();
@@ -175,6 +220,9 @@ internal static void RestorePreliminaryGameSettings()
GameManager.PreliminaryGameSettings.SaveToDisk();
}
+ ///
+ /// Initializes the Compatibility manager by patching the necessary methods.
+ ///
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Compatibility));
diff --git a/src/Managers/Hub.cs b/src/Managers/Hub.cs
index 43b186f..e6b9539 100644
--- a/src/Managers/Hub.cs
+++ b/src/Managers/Hub.cs
@@ -12,13 +12,23 @@
namespace PolyMod.Managers;
+///
+/// Manages the PolyMod hub, including UI elements and popups.
+///
internal static class Hub
{
private const string HEADER_PREFIX = "";
private const string HEADER_POSTFIX = "";
private const int POPUP_WIDTH = 1400;
+
+ ///
+ /// Whether the configuration popup is currently active.
+ ///
public static bool isConfigPopupActive = false;
+ ///
+ /// Patches the splash screen to play a custom intro video.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(SplashController), nameof(SplashController.LoadAndPlayClip))]
private static bool SplashController_LoadAndPlayClip(SplashController __instance)
@@ -32,6 +42,9 @@ private static bool SplashController_LoadAndPlayClip(SplashController __instance
return false;
}
+ ///
+ /// Patches the popup button container to correctly anchor buttons.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(PopupButtonContainer), nameof(PopupButtonContainer.SetButtonData))]
private static void PopupButtonContainer_SetButtonData(PopupButtonContainer __instance)
@@ -47,6 +60,9 @@ private static void PopupButtonContainer_SetButtonData(PopupButtonContainer __in
}
}
+ ///
+ /// Patches the start screen to add the PolyMod hub button and version text.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
@@ -249,6 +265,9 @@ static void PolyModHubButtonClicked(int buttonId, BaseEventData eventData)
}
}
+ ///
+ /// Patches the game manager to handle key presses for the config popup.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(GameManager), nameof(GameManager.Update))]
private static void GameManager_Update()
@@ -259,6 +278,9 @@ private static void GameManager_Update()
}
}
+ ///
+ /// Updates sprite information from dumped files.
+ ///
internal static void UpdateSpriteInfos()
{
string message = string.Empty;
@@ -290,6 +312,9 @@ internal static void UpdateSpriteInfos()
NotificationManager.Notify(message);
}
+ ///
+ /// Shows the configuration popup.
+ ///
internal static void ShowConfigPopup()
{
BasicPopup polymodPopup = PopupManager.GetBasicPopup();
@@ -302,6 +327,10 @@ internal static void ShowConfigPopup()
polymodPopup.Show();
}
+ ///
+ /// Creates the button data for the configuration popup.
+ ///
+ /// An array of popup button data.
internal static PopupButtonData[] CreateConfigPopupButtonData()
{
List popupButtons = new()
@@ -387,6 +416,9 @@ void OnBackButtonClicked(int buttonId, BaseEventData eventData)
}
}
+ ///
+ /// Initializes the Hub manager by patching the necessary methods.
+ ///
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Hub));
diff --git a/src/Managers/Loc.cs b/src/Managers/Loc.cs
index 5b1c70f..37d7767 100644
--- a/src/Managers/Loc.cs
+++ b/src/Managers/Loc.cs
@@ -7,8 +7,14 @@
namespace PolyMod.Managers;
+///
+/// Manages localization for PolyMod.
+///
public static class Loc
{
+ ///
+ /// Patches the tribe selection popup to correctly display descriptions for custom skins.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(SelectTribePopup), nameof(SelectTribePopup.SetDescription))]
private static void SetDescription(SelectTribePopup __instance)
@@ -31,6 +37,9 @@ private static void SetDescription(SelectTribePopup __instance)
}
}
+ ///
+ /// Patches the localization getter to handle custom enum values.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(Localization), nameof(Localization.Get), typeof(string), typeof(Il2CppReferenceArray))]
private static bool Localization_Get(ref string key, Il2CppReferenceArray args)
@@ -38,6 +47,8 @@ private static bool Localization_Get(ref string key, Il2CppReferenceArray keys = key.Split('.').ToList();
int? idx = null;
string? name = null;
+
+ // Find any custom enum indices in the localization key
foreach (string item in keys)
{
if (int.TryParse(item, out int parsedIdx))
@@ -48,6 +59,8 @@ private static bool Localization_Get(ref string key, Il2CppReferenceArray
+ /// Builds and loads localization data from a dictionary.
+ ///
+ /// A dictionary containing the localization data.
public static void BuildAndLoadLocalization(Dictionary> localization)
{
foreach (var (key, data) in localization)
@@ -93,6 +112,9 @@ public static void BuildAndLoadLocalization(Dictionary
+ /// Initializes the Loc manager by patching the necessary methods.
+ ///
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Loc));
diff --git a/src/Managers/Main.cs b/src/Managers/Main.cs
index 68892ed..acc8c5e 100644
--- a/src/Managers/Main.cs
+++ b/src/Managers/Main.cs
@@ -11,20 +11,69 @@
namespace PolyMod.Managers;
+///
+/// The main manager for PolyMod, responsible for initializing the mod and patching game logic.
+///
public static class Main
{
+ ///
+ /// The maximum tier for technology, used to extend the tech tree.
+ ///
internal const int MAX_TECH_TIER = 100;
+
+ ///
+ /// A stopwatch to measure the time taken to load mods.
+ ///
internal static readonly Stopwatch stopwatch = new();
+
+ ///
+ /// Whether the mod has been fully initialized.
+ ///
internal static bool fullyInitialized;
+
+ ///
+ /// Whether a dependency cycle was detected among the loaded mods.
+ ///
internal static bool dependencyCycle;
+
+ ///
+ /// A dictionary mapping unit IDs to the IDs of the units they embark into.
+ ///
internal static Dictionary embarkNames = new();
+
+ ///
+ /// A dictionary mapping unit types to the types of the units they embark into.
+ ///
internal static Dictionary embarkOverrides = new();
+
+ ///
+ /// Whether an embark action is currently being executed.
+ ///
internal static bool currentlyEmbarking = false;
+
+ ///
+ /// A dictionary mapping improvement IDs to the IDs of the resources they attract.
+ ///
internal static Dictionary attractsResourceNames = new();
+
+ ///
+ /// A dictionary mapping improvement IDs to the IDs of the terrain types they attract resources on.
+ ///
internal static Dictionary attractsTerrainNames = new();
+
+ ///
+ /// A dictionary mapping improvement types to the types of the resources they attract.
+ ///
internal static Dictionary attractsResourceOverrides = new();
+
+ ///
+ /// A dictionary mapping improvement types to the types of the terrain they attract resources on.
+ ///
internal static Dictionary attractsTerrainOverrides = new();
+ ///
+ /// Patches the game logic data parsing to load PolyMod content.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(GameLogicData), nameof(GameLogicData.AddGameLogicPlaceholders))]
private static void GameLogicData_Parse(GameLogicData __instance, JObject rootObject)
@@ -105,6 +154,9 @@ private static void GameLogicData_Parse(GameLogicData __instance, JObject rootOb
}
}
+ ///
+ /// Patches the purchase manager to unlock custom skins.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsSkinUnlocked))]
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsSkinUnlockedInternal))]
@@ -114,6 +166,9 @@ private static bool PurchaseManager_IsSkinUnlockedInternal(ref bool __result, Sk
return !__result;
}
+ ///
+ /// Patches the purchase manager to unlock custom tribes.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.IsTribeUnlocked))]
private static void PurchaseManager_IsTribeUnlocked(ref bool __result, TribeData.Type type)
@@ -121,6 +176,9 @@ private static void PurchaseManager_IsTribeUnlocked(ref bool __result, TribeData
__result = (int)type >= Plugin.AUTOIDX_STARTS_FROM || __result;
}
+ ///
+ /// Patches the purchase manager to add custom tribes to the list of unlocked tribes.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(PurchaseManager), nameof(PurchaseManager.GetUnlockedTribes))]
private static void PurchaseManager_GetUnlockedTribes(
@@ -131,6 +189,9 @@ private static void PurchaseManager_GetUnlockedTribes(
foreach (var tribe in Registry.customTribes) __result.Add(tribe);
}
+ ///
+ /// Patches the Unity log callback to filter out spammy messages.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(IL2CPPUnityLogSource), nameof(IL2CPPUnityLogSource.UnityLogCallback))]
private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string exception, LogType type)
@@ -143,6 +204,9 @@ private static bool IL2CPPUnityLogSource_UnityLogCallback(string logLine, string
return true;
}
+ ///
+ /// Patches the game mode screen to add custom game modes.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(GameModeScreen), nameof(GameModeScreen.Init))]
private static void GameModeScreen_Init(GameModeScreen __instance)
@@ -200,10 +264,15 @@ private static void GameModeScreen_Init(GameModeScreen __instance)
}
}
+ ///
+ /// Patches the tech view to correctly display the tech tree with custom technologies.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(TechView), nameof(TechView.CreateNode))]
public static bool TechView_CreateNode(TechView __instance, TechData data, TechItem parentItem, float angle)
{
+ // This patch is a reimplementation of the original CreateNode method to fix layout issues with custom techs.
+ // It recalculates the angles for each node to ensure they are displayed correctly.
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;
@@ -231,6 +300,9 @@ public static bool TechView_CreateNode(TechView __instance, TechData data, TechI
return false;
}
+ ///
+ /// Patches the embark action to set a flag indicating that an embark is in progress.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(EmbarkAction), nameof(EmbarkAction.Execute))]
private static bool EmbarkAction_Execute_Prefix(EmbarkAction __instance, GameState gameState)
@@ -239,18 +311,18 @@ private static bool EmbarkAction_Execute_Prefix(EmbarkAction __instance, GameSta
return true;
}
+ ///
+ /// Patches the unit training method to handle custom embark units.
+ ///
[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)
+ if (tile == null || tile.unit == null)
{
return true;
}
+
if (currentlyEmbarking)
{
if (embarkOverrides.TryGetValue(tile.unit.type, out UnitData.Type newType))
@@ -262,10 +334,14 @@ private static bool ActionUtils_TrainUnit(ref UnitState __result, GameState game
return true;
}
+ ///
+ /// Patches the start turn action to handle the 'Attract' ability for custom improvements.
+ ///
[HarmonyPostfix]
[HarmonyPatch(typeof(StartTurnAction), nameof(StartTurnAction.Execute))]
private static void StartTurnAction_Execute(StartTurnAction __instance, GameState state)
{
+ // Clear any existing CreateResource actions to prevent duplicates.
for (int i = state.ActionStack.Count - 1; i >= 0; i--)
{
if (state.ActionStack[i].GetActionType() == ActionType.CreateResource)
@@ -273,6 +349,8 @@ private static void StartTurnAction_Execute(StartTurnAction __instance, GameStat
state.ActionStack.RemoveAt(i);
}
}
+
+ // Iterate through all tiles owned by the current player.
for (int i = 0; i < state.Map.Tiles.Length; i++)
{
TileData tileData = state.Map.Tiles[i];
@@ -282,6 +360,7 @@ private static void StartTurnAction_Execute(StartTurnAction __instance, GameStat
state.GameLogicData.TryGetData(tileData.improvement.type, out improvementData);
if (improvementData != null)
{
+ // If the improvement has the 'Attract' ability and is ready to spawn a resource.
if (improvementData.HasAbility(ImprovementAbility.Type.Attract) && tileData.improvement.GetAge(state) % improvementData.growthRate == 0)
{
ResourceData.Type resourceType = ResourceData.Type.Game;
@@ -294,6 +373,8 @@ private static void StartTurnAction_Execute(StartTurnAction __instance, GameStat
{
targetTerrain = newTerrain;
}
+
+ // Find a valid tile to spawn the resource on.
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)
@@ -308,6 +389,9 @@ private static void StartTurnAction_Execute(StartTurnAction __instance, GameStat
}
}
+ ///
+ /// Patches the unit creation method to handle custom unit prefabs.
+ ///
[HarmonyPrefix]
[HarmonyPatch(typeof(Unit), nameof(Unit.CreateUnit))]
private static bool Unit_CreateUnit(Unit __instance, UnitData unitData, TribeData.Type tribe, SkinType unitSkin)
@@ -317,6 +401,9 @@ private static bool Unit_CreateUnit(Unit __instance, UnitData unitData, TribeDat
return true;
}
+ ///
+ /// Initializes the Main manager.
+ ///
internal static void Init()
{
stopwatch.Start();
@@ -361,6 +448,10 @@ internal static void Init()
stopwatch.Stop();
}
+ ///
+ /// Loads all mod content.
+ ///
+ /// The game logic data to patch.
internal static void Load(JObject gameLogicdata)
{
stopwatch.Start();
diff --git a/src/Managers/Visual.cs b/src/Managers/Visual.cs
index e816f0b..f2ae379 100644
--- a/src/Managers/Visual.cs
+++ b/src/Managers/Visual.cs
@@ -10,40 +10,64 @@
namespace PolyMod.Managers;
+///
+/// Manages visual aspects of the game, including sprites, UI, and in-game objects.
+///
public static class Visual
{
+ ///
+ /// Represents a tile in a tribe preview.
+ ///
public class PreviewTile
{
+ /// The x-coordinate of the tile.
[JsonInclude]
public int? x = null;
+ /// The y-coordinate of the tile.
[JsonInclude]
public int? y = null;
+ /// The terrain type of the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public Polytopia.Data.TerrainData.Type terrainType = Polytopia.Data.TerrainData.Type.Ocean;
+ /// The resource type on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public ResourceData.Type resourceType = ResourceData.Type.None;
+ /// The unit type on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public UnitData.Type unitType = UnitData.Type.None;
+ /// The improvement type on the tile.
[JsonInclude]
[JsonConverter(typeof(EnumCacheJson))]
public ImprovementData.Type improvementType = ImprovementData.Type.None;
}
+
+ /// Represents information about a sprite, such as its pivot and pixels per unit.
public record SpriteInfo(float? pixelsPerUnit, Vector2? pivot);
+
+ /// Represents information about a custom skin.
public record SkinInfo(int idx, string id, SkinData? skinData);
+
+ /// A dictionary of custom widths for basic popups.
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;
+
+ /// The type of a custom prefab.
public enum PrefabType
{
Unit,
Improvement,
Resource
}
+
+ /// Represents information about a custom prefab.
public record PrefabInfo(PrefabType type, string name, List visualParts);
+
+ /// Represents information about a visual part of a prefab.
public record VisualPartInfo(
string gameObjectName,
string baseName,
@@ -56,12 +80,14 @@ public record VisualPartInfo(
#region General
+ /// A placeholder patch for the TechItem.SetupComplete method.
[HarmonyPostfix]
[HarmonyPatch(typeof(TechItem), nameof(TechItem.SetupComplete))]
private static void TechItem_SetupComplete()
{
}
+ /// Resets the firstTimeOpeningPreview flag when the start screen is shown.
[HarmonyPrefix]
[HarmonyPatch(typeof(StartScreen), nameof(StartScreen.Start))]
private static void StartScreen_Start()
@@ -69,6 +95,7 @@ private static void StartScreen_Start()
firstTimeOpeningPreview = true;
}
+ /// Patches the sprite atlas manager to load custom sprites.
[HarmonyPrefix]
[HarmonyPatch(typeof(SpriteAtlasManager), nameof(SpriteAtlasManager.LoadSprite), typeof(string), typeof(string), typeof(SpriteCallback))]
private static bool SpriteAtlasManager_LoadSprite(SpriteAtlasManager __instance, string atlas, string sprite, SpriteCallback completion)
@@ -107,6 +134,7 @@ void GetAtlas(SpriteAtlas spriteAtlas)
}
}
+ /// Patches the sprite atlas manager to look up custom sprites.
[HarmonyPostfix]
[HarmonyPatch(typeof(SpriteAtlasManager), nameof(SpriteAtlasManager.DoSpriteLookup))]
private static void SpriteAtlasManager_DoSpriteLookup(ref SpriteAtlasManager.SpriteLookupResult __result, SpriteAtlasManager __instance, string baseName, TribeData.Type tribe, SkinType skin, bool checkForOutline, int level)
@@ -121,8 +149,7 @@ private static void SpriteAtlasManager_DoSpriteLookup(ref SpriteAtlasManager.Spr
#endregion
#region Units
- // lobotomy
-
+ /// Enables outlines when the interaction bar is shown.
[HarmonyPrefix]
[HarmonyPatch(typeof(InteractionBar), nameof(InteractionBar.Show))]
private static bool InteractionBar_Show(InteractionBar __instance, bool instant, bool force)
@@ -131,6 +158,7 @@ private static bool InteractionBar_Show(InteractionBar __instance, bool instant,
return true;
}
+ /// Prevents outlines from being created when they are disabled.
[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)
@@ -138,6 +166,7 @@ private static bool UISpriteDuplicator_CreateImage(SpriteRenderer spriteRenderer
return !(spriteRenderer.sortingOrder == -1 && !enableOutlines);
}
+ /// Disables outlines after the interaction bar is shown.
[HarmonyPostfix]
[HarmonyPatch(typeof(InteractionBar), nameof(InteractionBar.Show))]
private static void InteractionBar_Show_Postfix(InteractionBar __instance, bool instant, bool force)
@@ -145,6 +174,7 @@ private static void InteractionBar_Show_Postfix(InteractionBar __instance, bool
enableOutlines = false;
}
+ /// Sets the current unit type being rendered in the UI.
[HarmonyPrefix]
[HarmonyPatch(typeof(UIUnitRenderer), nameof(UIUnitRenderer.CreateUnit))]
private static bool UIUnitRenderer_CreateUnit_Prefix(UIUnitRenderer __instance)
@@ -153,6 +183,7 @@ private static bool UIUnitRenderer_CreateUnit_Prefix(UIUnitRenderer __instance)
return true;
}
+ /// Resets the current unit type being rendered in the UI.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIUnitRenderer), nameof(UIUnitRenderer.CreateUnit))]
private static void UIUnitRenderer_CreateUnit_Postfix(UIUnitRenderer __instance)
@@ -160,6 +191,7 @@ private static void UIUnitRenderer_CreateUnit_Postfix(UIUnitRenderer __instance)
currentUnitTypeUI = UnitData.Type.None;
}
+ /// Skins the visual parts of a unit with custom sprites.
[HarmonyPostfix]
[HarmonyPatch(typeof(SkinVisualsRenderer), nameof(SkinVisualsRenderer.SkinWorldObject))]
private static void SkinVisualsRenderer_SkinWorldObject(
@@ -190,6 +222,7 @@ private static void SkinVisualsRenderer_SkinWorldObject(
#endregion
#region Level
+ /// Updates the visual parts of a resource with custom sprites.
[HarmonyPostfix]
[HarmonyPatch(typeof(Resource), nameof(Resource.UpdateObject), typeof(SkinVisualsTransientData))]
private static void Resource_UpdateObject(Resource __instance, SkinVisualsTransientData transientSkinData)
@@ -206,6 +239,7 @@ private static void Resource_UpdateObject(Resource __instance, SkinVisualsTransi
}
}
+ /// Updates a building with a custom sprite.
[HarmonyPostfix]
[HarmonyPatch(typeof(Building), nameof(Building.UpdateObject), typeof(SkinVisualsTransientData))]
private static void Building_UpdateObject(Building __instance, SkinVisualsTransientData transientSkinData)
@@ -219,6 +253,7 @@ private static void Building_UpdateObject(Building __instance, SkinVisualsTransi
}
}
+ /// Updates the terrain graphics with custom sprites.
[HarmonyPostfix]
[HarmonyPatch(typeof(TerrainRenderer), nameof(TerrainRenderer.UpdateGraphics))]
private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, Tile tile)
@@ -284,6 +319,7 @@ private static void TerrainRenderer_UpdateGraphics(TerrainRenderer __instance, T
}
}
+ /// Executes a flood command when a tile is flooded.
[HarmonyPostfix]
[HarmonyPatch(typeof(TileData), nameof(TileData.Flood))]
private static void TileData_Flood(TileData __instance, PlayerState playerState)
@@ -294,6 +330,7 @@ private static void TileData_Flood(TileData __instance, PlayerState playerState)
}
}
+ /// Ensures that flood commands are always considered valid.
[HarmonyPrefix]
[HarmonyPatch(typeof(FloodCommand), nameof(FloodCommand.IsValid))]
private static bool FloodCommand_IsValid(ref bool __result, FloodCommand __instance, GameState state, ref string validationError)
@@ -302,6 +339,7 @@ private static bool FloodCommand_IsValid(ref bool __result, FloodCommand __insta
return false;
}
+ /// Forces an update of the mesh for a PolytopiaSpriteRenderer with a custom sprite.
[HarmonyPostfix]
[HarmonyPatch(typeof(PolytopiaSpriteRenderer), nameof(PolytopiaSpriteRenderer.ForceUpdateMesh))]
private static void PolytopiaSpriteRenderer_ForceUpdateMesh(PolytopiaSpriteRenderer __instance)
@@ -318,6 +356,7 @@ private static void PolytopiaSpriteRenderer_ForceUpdateMesh(PolytopiaSpriteRende
#endregion
#region TribePreview
+ /// Provides custom data for the tribe preview.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIWorldPreviewData), nameof(UIWorldPreviewData.TryGetData))]
private static void UIWorldPreviewData_TryGetData(ref bool __result, UIWorldPreviewData __instance, Vector2Int position, TribeData.Type tribeType, ref UITileData uiTile)
@@ -346,6 +385,7 @@ private static void UIWorldPreviewData_TryGetData(ref bool __result, UIWorldPrev
}
}
+ /// Modifies the tribe preview for debugging purposes.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIWorldPreview), nameof(UIWorldPreview.SetPreview), new Type[] { })]
private static void UIWorldPreview_SetPreview(UIWorldPreview __instance)
@@ -370,6 +410,7 @@ private static void UIWorldPreview_SetPreview(UIWorldPreview __instance)
#endregion
#region UI
+ /// Provides custom sprites for improvements in the UI.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIUtils), nameof(UIUtils.GetImprovementSprite), typeof(ImprovementData.Type), typeof(TribeData.Type), typeof(SkinType), typeof(SpriteAtlasManager))]
private static void UIUtils_GetImprovementSprite(ref Sprite __result, ImprovementData.Type improvement, TribeData.Type tribe, SkinType skin, SpriteAtlasManager atlasManager)
@@ -381,6 +422,7 @@ private static void UIUtils_GetImprovementSprite(ref Sprite __result, Improvemen
}
}
+ /// Provides custom sprites for improvements in the UI.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIUtils), nameof(UIUtils.GetImprovementSprite), typeof(SkinVisualsTransientData), typeof(ImprovementData.Type), typeof(SpriteAtlasManager))]
private static void UIUtils_GetImprovementSprite_2(ref Sprite __result, SkinVisualsTransientData data, ImprovementData.Type improvement, SpriteAtlasManager atlasManager)
@@ -388,6 +430,7 @@ private static void UIUtils_GetImprovementSprite_2(ref Sprite __result, SkinVisu
UIUtils_GetImprovementSprite(ref __result, improvement, data.foundingTribeSettings.tribe, data.foundingTribeSettings.skin, atlasManager);
}
+ /// Provides custom sprites for resources in the UI.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIUtils), nameof(UIUtils.GetResourceSprite))]
private static void UIUtils_GetResourceSprite(ref Sprite __result, SkinVisualsTransientData data, ResourceData.Type resource, SpriteAtlasManager atlasManager)
@@ -402,6 +445,7 @@ private static void UIUtils_GetResourceSprite(ref Sprite __result, SkinVisualsTr
#endregion
#region Houses
+ /// Provides custom sprites for houses in the city view.
[HarmonyPostfix]
[HarmonyPatch(typeof(CityRenderer), nameof(CityRenderer.GetHouse))]
private static void CityRenderer_GetHouse(ref PolytopiaSpriteRenderer __result, CityRenderer __instance, TribeData.Type tribe, int type, SkinType skinType)
@@ -420,6 +464,7 @@ private static void CityRenderer_GetHouse(ref PolytopiaSpriteRenderer __result,
}
}
+ /// Provides custom sprites for houses in the UI city renderer.
[HarmonyPostfix]
[HarmonyPatch(typeof(UICityRenderer), nameof(UICityRenderer.GetResource))]
private static void UICityRenderer_GetResource(ref GameObject __result, string baseName, Polytopia.Data.TribeData.Type tribe, Polytopia.Data.SkinType skin)
@@ -450,6 +495,7 @@ private static void UICityRenderer_GetResource(ref GameObject __result, string b
#endregion
#region Icons
+ /// Provides custom sprites for UI icons.
[HarmonyPostfix]
[HarmonyPatch(typeof(UIIconData), nameof(UIIconData.GetImage))]
private static void UIIconData_GetImage(ref Image __result, string id)
@@ -471,6 +517,7 @@ private static void UIIconData_GetImage(ref Image __result, string id)
}
}
+ /// Provides custom sprites for face icons in the game info row.
[HarmonyPostfix]
[HarmonyPatch(typeof(GameInfoRow), nameof(GameInfoRow.LoadFaceIcon), typeof(TribeData.Type), typeof(SkinType))]
private static void GameInfoRow_LoadFaceIcon(GameInfoRow __instance, TribeData.Type type, SkinType skinType)
@@ -504,6 +551,7 @@ private static void GameInfoRow_LoadFaceIcon(GameInfoRow __instance, TribeData.T
}
}
+ /// Provides custom sprites for player info icons.
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerInfoIcon), nameof(PlayerInfoIcon.SetData), typeof(TribeData.Type), typeof(SkinType), typeof(SpriteData.SpecialFaceIcon), typeof(Color), typeof(DiplomacyRelationState), typeof(PlayerInfoIcon.Mood))]
private static void PlayerInfoIcon_SetData(PlayerInfoIcon __instance, TribeData.Type tribe, SkinType skin, SpriteData.SpecialFaceIcon face, Color color, DiplomacyRelationState diplomacyState, PlayerInfoIcon.Mood mood)
@@ -523,6 +571,7 @@ private static void PlayerInfoIcon_SetData(PlayerInfoIcon __instance, TribeData.
#endregion
#region Popups
+ /// Updates the width of a basic popup if a custom width is set.
[HarmonyPostfix]
[HarmonyPatch(typeof(BasicPopup), nameof(BasicPopup.Update))]
private static void BasicPopup_Update(BasicPopup __instance)
@@ -532,6 +581,7 @@ private static void BasicPopup_Update(BasicPopup __instance)
__instance.rectTransform.SetWidth(basicPopupWidths[id]);
}
+ /// Sets the attacker's tribe before a unit attacks.
[HarmonyPrefix]
[HarmonyPatch(typeof(Unit), nameof(Unit.Attack))]
private static bool Unit_Attack(Unit __instance, WorldCoordinates target, bool moveToTarget, Il2CppSystem.Action onComplete)
@@ -543,6 +593,7 @@ private static bool Unit_Attack(Unit __instance, WorldCoordinates target, bool m
return true;
}
+ /// Sets the skin of a weapon's graphics, using custom sprites if available.
[HarmonyPostfix]
[HarmonyPatch(typeof(WeaponGFX), nameof(WeaponGFX.SetSkin))]
private static void WeaponGFX_SetSkin(WeaponGFX __instance, SkinType skinType)
@@ -558,6 +609,7 @@ private static void WeaponGFX_SetSkin(WeaponGFX __instance, SkinType skinType)
}
}
+ /// Removes a popup's custom width when it is hidden.
[HarmonyPostfix]
[HarmonyPatch(typeof(PopupBase), nameof(PopupBase.Hide))]
private static void PopupBase_Hide(PopupBase __instance)
@@ -565,6 +617,7 @@ private static void PopupBase_Hide(PopupBase __instance)
basicPopupWidths.Remove(__instance.GetInstanceID());
}
+ /// Shows a basic popup with a custom width.
public static void ShowSetWidth(this BasicPopup self, int width)
{
basicPopupWidths.Add(self.GetInstanceID(), width);
@@ -573,6 +626,7 @@ public static void ShowSetWidth(this BasicPopup self, int width)
#endregion
+ /// Updates a visual part with a custom sprite.
private static void UpdateVisualPart(SkinVisualsReference.VisualPart visualPart, string name, string style)
{
Sprite? sprite = Registry.GetSprite(name, style) ?? Registry.GetSprite(visualPart.visualPart.name, style);
@@ -589,11 +643,12 @@ private static void UpdateVisualPart(SkinVisualsReference.VisualPart visualPart,
{
if (visualPart.outlineRenderer.spriteRenderer != null)
visualPart.outlineRenderer.spriteRenderer.sprite = outlineSprite;
- else if (visualPart.outlineRenderer.polytopiaSpriteRenderer != null)
+ else if (visualpart.outlineRenderer.polytopiaSpriteRenderer != null)
visualPart.outlineRenderer.polytopiaSpriteRenderer.sprite = outlineSprite;
}
}
+ /// Builds a sprite from raw byte data.
public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixelsPerUnit = 2112f)
{
Texture2D texture = new(1, 1, TextureFormat.RGBA32, true);
@@ -609,6 +664,7 @@ public static Sprite BuildSprite(byte[] data, Vector2? pivot = null, float pixel
return BuildSpriteWithTexture(texture, pivot, pixelsPerUnit);
}
+ /// Builds a sprite from a texture.
public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot = null, float? pixelsPerUnit = 2112f)
{
return Sprite.Create(
@@ -619,6 +675,7 @@ public static Sprite BuildSpriteWithTexture(Texture2D texture, Vector2? pivot =
);
}
+ /// Initializes the Visual manager by patching the necessary methods.
internal static void Init()
{
Harmony.CreateAndPatchAll(typeof(Visual));
diff --git a/src/Mod.cs b/src/Mod.cs
index 36e81c1..61fbe13 100644
--- a/src/Mod.cs
+++ b/src/Mod.cs
@@ -1,27 +1,108 @@
namespace PolyMod;
+///
+/// Represents a mod in the PolyMod framework.
+///
public class Mod
{
+ ///
+ /// Represents a dependency of a mod.
+ ///
+ /// The unique identifier of the dependency.
+ /// The minimum compatible version of the dependency.
+ /// The maximum compatible version of the dependency.
+ /// Whether the dependency is required for the mod to function.
public record Dependency(string id, Version min, Version max, bool required = true);
+
+ ///
+ /// Represents the manifest of a mod, containing metadata.
+ ///
+ /// The unique identifier of the mod.
+ /// The name of the mod.
+ /// A description of the mod.
+ /// The version of the mod.
+ /// The authors of the mod.
+ /// The dependencies of the mod.
+ /// Whether the mod is client-side only.
public record Manifest(string id, string? name, string? description, Version version, string[] authors, Dependency[]? dependencies, bool client = false);
+
+ ///
+ /// Represents a file included in a mod.
+ ///
+ /// The name of the file.
+ /// The content of the file as a byte array.
public record File(string name, byte[] bytes);
+
+ ///
+ /// Represents the loading status of a mod.
+ ///
public enum Status
{
+ ///
+ /// The mod was loaded successfully.
+ ///
Success,
+ ///
+ /// An error occurred while loading the mod.
+ ///
Error,
+ ///
+ /// The mod's dependencies were not satisfied.
+ ///
DependenciesUnsatisfied,
}
+ ///
+ /// The unique identifier of the mod.
+ ///
public string id;
+
+ ///
+ /// The name of the mod.
+ ///
public string? name;
+
+ ///
+ /// A description of the mod.
+ ///
public string? description;
+
+ ///
+ /// The version of the mod.
+ ///
public Version version;
+
+ ///
+ /// The authors of the mod.
+ ///
public string[] authors;
+
+ ///
+ /// The dependencies of the mod.
+ ///
public Dependency[]? dependencies;
+
+ ///
+ /// Whether the mod is client-side only.
+ ///
public bool client;
+
+ ///
+ /// The loading status of the mod.
+ ///
public Status status;
+
+ ///
+ /// The files included in the mod.
+ ///
public List files;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The manifest of the mod.
+ /// The loading status of the mod.
+ /// The files included in the mod.
public Mod(Manifest manifest, Status status, List files)
{
id = manifest.id;
diff --git a/src/NullableFix.cs b/src/NullableFix.cs
index 86e89ae..adc6cdf 100644
--- a/src/NullableFix.cs
+++ b/src/NullableFix.cs
@@ -2,22 +2,48 @@
namespace System.Runtime.CompilerServices
{
+ ///
+ /// A polyfill for the Nullable attribute, which is used by the compiler for nullable reference type analysis.
+ ///
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true)]
public sealed class NullableAttribute : System.Attribute
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The nullable flags.
public NullableAttribute(byte b) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The nullable flags.
public NullableAttribute(byte[] b) { }
}
+ ///
+ /// A polyfill for the NullableContext attribute, which is used by the compiler for nullable reference type analysis.
+ ///
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = false)]
public sealed class NullableContextAttribute : System.Attribute
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The nullable context flags.
public NullableContextAttribute(byte b) { }
}
+ ///
+ /// A polyfill for the NullablePublicOnly attribute, which is used by the compiler for nullable reference type analysis.
+ ///
[System.AttributeUsage(System.AttributeTargets.Module)]
public sealed class NullablePublicOnlyAttribute : System.Attribute
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Whether to apply nullable analysis to public members only.
public NullablePublicOnlyAttribute(bool b) { }
}
}
diff --git a/src/Plugin.cs b/src/Plugin.cs
index 025258c..8be7d8d 100644
--- a/src/Plugin.cs
+++ b/src/Plugin.cs
@@ -8,25 +8,69 @@
namespace PolyMod;
+///
+/// Main plugin class for PolyMod.
+///
[BepInPlugin("com.polymod", "PolyMod", VERSION)]
public partial class Plugin : BepInEx.Unity.IL2CPP.BasePlugin
{
+ ///
+ /// Represents the configuration for PolyMod.
+ ///
+ /// Whether to enable debug mode.
+ /// Whether to automatically update PolyMod.
+ /// Whether to include pre-release versions when updating.
internal record PolyConfig(
bool debug = false,
bool autoUpdate = true,
bool updatePrerelease = false
);
+ ///
+ /// The starting index for automatically assigned IDs.
+ ///
internal const int AUTOIDX_STARTS_FROM = 1000;
+
+ ///
+ /// The key used to store the last version for which the incompatibility warning was shown.
+ ///
internal const string INCOMPATIBILITY_WARNING_LAST_VERSION_KEY
= "INCOMPATIBILITY_WARNING_LAST_VERSION";
+
+ ///
+ /// The base path for PolyMod files.
+ ///
public static readonly string BASE_PATH = Path.Combine(BepInEx.Paths.BepInExRootPath, "..");
+
+ ///
+ /// The path to the mods directory.
+ ///
public static readonly string MODS_PATH = Path.Combine(BASE_PATH, "Mods");
+
+ ///
+ /// The path to the directory where game data is dumped.
+ ///
public static readonly string DUMPED_DATA_PATH = Path.Combine(BASE_PATH, "DumpedData");
+
+ ///
+ /// The path to the PolyMod configuration file.
+ ///
internal static readonly string CONFIG_PATH = Path.Combine(BASE_PATH, "PolyMod.json");
+
+ ///
+ /// The path to the checksum file.
+ ///
internal static readonly string CHECKSUM_PATH
= Path.Combine(BASE_PATH, "CHECKSUM");
+
+ ///
+ /// The link to the PolyMod Discord server.
+ ///
internal static readonly string DISCORD_LINK = "https://discord.gg/eWPdhWtfVy";
+
+ ///
+ /// A list of log messages to ignore.
+ ///
internal static readonly List LOG_MESSAGES_IGNORE = new()
{
"Failed to find atlas",
@@ -38,10 +82,20 @@ internal static readonly string CHECKSUM_PATH
#pragma warning disable CS8618
+ ///
+ /// The PolyMod configuration.
+ ///
internal static PolyConfig config;
+
+ ///
+ /// The logger instance for PolyMod.
+ ///
internal static ManualLogSource logger;
#pragma warning restore CS8618
+ ///
+ /// The entry point for the plugin.
+ ///
public override void Load()
{
try
@@ -68,6 +122,11 @@ public override void Load()
Main.Init();
}
+ ///
+ /// Gets a resource stream from the assembly.
+ ///
+ /// The ID of the resource.
+ /// The resource stream.
internal static Stream GetResource(string id)
{
return Assembly.GetExecutingAssembly().GetManifestResourceStream(
@@ -75,11 +134,17 @@ internal static Stream GetResource(string id)
)!;
}
+ ///
+ /// Writes the configuration to disk.
+ ///
internal static void WriteConfig()
{
File.WriteAllText(CONFIG_PATH, JsonSerializer.Serialize(config));
}
+ ///
+ /// Updates the console based on the debug configuration.
+ ///
internal static void UpdateConsole()
{
if (config.debug)
diff --git a/src/Registry.cs b/src/Registry.cs
index 207d225..092e2d6 100644
--- a/src/Registry.cs
+++ b/src/Registry.cs
@@ -6,29 +6,100 @@
namespace PolyMod;
+///
+/// A central registry for storing and accessing modded content.
+///
public static class Registry
{
+ ///
+ /// The next available index for automatically assigned IDs.
+ ///
public static int autoidx = Plugin.AUTOIDX_STARTS_FROM;
+
+ ///
+ /// A dictionary of all loaded sprites, keyed by their name.
+ ///
public static Dictionary sprites = new();
+
+ ///
+ /// A dictionary of all loaded audio clips, keyed by their name.
+ ///
public static Dictionary audioClips = new();
+
+ ///
+ /// A dictionary of all loaded mods, keyed by their ID.
+ ///
internal static Dictionary mods = new();
+
+ ///
+ /// A dictionary of tribe previews, keyed by tribe name.
+ ///
public static Dictionary tribePreviews = new();
+
+ ///
+ /// A dictionary of sprite information, keyed by sprite name.
+ ///
public static Dictionary spriteInfos = new();
+
+ ///
+ /// A dictionary of prefab names, keyed by their enum value.
+ ///
public static Dictionary prefabNames = new();
+
+ ///
+ /// A dictionary of custom unit prefabs, keyed by their prefab info.
+ ///
public static Dictionary unitPrefabs = new();
+
+ ///
+ /// A dictionary of custom resource prefabs, keyed by their prefab info.
+ ///
public static Dictionary resourcePrefabs = new();
+
+ ///
+ /// A dictionary of custom improvement prefabs, keyed by their prefab info.
+ ///
public static Dictionary improvementsPrefabs = new();
+
+ ///
+ /// A dictionary of loaded asset bundles, keyed by their name.
+ ///
public static Dictionary assetBundles = new();
+
+ ///
+ /// A list of custom tribes.
+ ///
public static List customTribes = new();
+
+ ///
+ /// A list of custom skin information.
+ ///
public static List skinInfo = new();
+
+ ///
+ /// The next available index for climate styles.
+ ///
public static int climateAutoidx = (int)Enum.GetValues(typeof(TribeData.Type)).Cast().Last();
+
+ ///
+ /// The next available index for game modes.
+ ///
public static int gameModesAutoidx = Enum.GetValues(typeof(GameMode)).Length;
+ ///
+ /// Gets a sprite from the registry, trying to find the best match.
+ ///
+ /// The name of the sprite.
+ /// The style of the sprite (e.g., tribe or skin).
+ /// The level of the sprite (e.g., for cities).
+ /// The requested sprite, or null if not found. The lookup priority is: name_style_level, name__level, name_style_, name__.
public static Sprite? GetSprite(string name, string style = "", int level = 0)
{
Sprite? sprite = null;
name = name.ToLower();
style = style.ToLower();
+ // This looks backwards, but GetOrDefault returns the provided default value if the key isn't found.
+ // This means `sprite` is updated sequentially, and the last, most-specific match is the one that is kept.
sprite = sprites.GetOrDefault($"{name}__", sprite);
sprite = sprites.GetOrDefault($"{name}_{style}_", sprite);
sprite = sprites.GetOrDefault($"{name}__{level}", sprite);
@@ -36,6 +107,12 @@ public static class Registry
return sprite;
}
+ ///
+ /// Gets an audio clip from the registry.
+ ///
+ /// The name of the audio clip.
+ /// The style of the audio clip.
+ /// The requested audio clip, or null if not found.
public static AudioClip? GetAudioClip(string name, string style)
{
AudioSource? audioSource = null;
diff --git a/src/Util.cs b/src/Util.cs
index 4d2fce2..4da415e 100644
--- a/src/Util.cs
+++ b/src/Util.cs
@@ -6,8 +6,17 @@
using Polytopia.Data;
namespace PolyMod;
+
+///
+/// A collection of utility methods for the PolyMod framework.
+///
internal static class Util
{
+ ///
+ /// Wraps a managed type for use in the Il2Cpp runtime.
+ ///
+ /// The type to wrap.
+ /// The Il2Cpp representation of the type.
internal static Il2CppSystem.Type WrapType() where T : class
{
if (!ClassInjector.IsTypeRegisteredInIl2Cpp())
@@ -15,31 +24,65 @@ internal static Il2CppSystem.Type WrapType() where T : class
return Il2CppType.From(typeof(T));
}
+ ///
+ /// Computes the SHA256 hash of an object's string representation.
+ ///
+ /// The object to hash.
+ /// The hexadecimal string representation of the hash.
internal static string Hash(object data)
{
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(data.ToString()!)));
}
+ ///
+ /// Gets the name of a JToken from its path.
+ ///
+ /// The JToken.
+ /// The part of the path to retrieve, from the end.
+ /// The name of the JToken.
internal static string GetJTokenName(JToken token, int n = 1)
{
return token.Path.Split('.')[^n];
}
+ ///
+ /// Converts an Il2CppSystem.Version to a System.Version.
+ ///
+ /// The Il2CppSystem.Version to convert.
+ /// The equivalent System.Version.
internal static Version Cast(this Il2CppSystem.Version self)
{
return new(self.ToString());
}
+ ///
+ /// Synchronously unwraps the result of a Task.
+ ///
+ /// The result type of the Task.
+ /// The Task to unwrap.
+ /// The result of the Task.
internal static T UnwrapAsync(this Task self)
{
return self.GetAwaiter().GetResult();
}
+ ///
+ /// Removes the revision component from a Version.
+ ///
+ /// The Version to modify.
+ /// A new Version with the revision set to -1.
internal static Version CutRevision(this Version self)
{
return new(self.Major, self.Minor, self.Build);
}
+ ///
+ /// Compares two version strings to see if the first is older or equal to the second.
+ /// Handles pre-release identifiers.
+ ///
+ /// The first version string.
+ /// The second version string.
+ /// True if version1 is older or equal to version2, false otherwise.
internal static bool IsVersionOlderOrEqual(this string version1, string version2)
{
Version version1_ = new(version1.Split('-')[0]);
@@ -57,13 +100,28 @@ internal static bool IsVersionOlderOrEqual(this string version1, string version2
return string.Compare(pre1, pre2, StringComparison.Ordinal) <= 0;
}
+ ///
+ /// Gets the style name for a tribe and skin combination.
+ ///
+ /// The tribe.
+ /// The skin.
+ /// The style name.
internal static string GetStyle(TribeData.Type tribe, SkinType skin)
{
return skin != SkinType.Default ? EnumCache.GetName(skin) : EnumCache.GetName(tribe);
}
+ ///
+ /// Formats a sprite name to match the enum naming convention.
+ /// This is a workaround for inconsistencies in the game's sprite naming.
+ ///
+ /// The original sprite name.
+ /// The formatted sprite name.
internal static string FormatSpriteName(string baseName) // I cant believe i had to do this shit #MIDJIWANFIXYOURSHITCODE
{
+ // This method is necessary because the sprite names in the game's data do not always
+ // match the names in the corresponding enums. This method replaces the hardcoded sprite
+ // names with the enum names to ensure consistency.
baseName = baseName.Replace(SpriteData.IMPROVEMENT_AQUA_FARM, EnumCache.GetName(ImprovementData.Type.Aquafarm));
baseName = baseName.Replace(SpriteData.IMPROVEMENT_ATOLL, EnumCache.GetName(ImprovementData.Type.Atoll));
baseName = baseName.Replace(SpriteData.IMPROVEMENT_BURN_FOREST, EnumCache.GetName(ImprovementData.Type.BurnForest));