From 91fc980b4333c284c189c76e9dacc13d20551ce2 Mon Sep 17 00:00:00 2001 From: Jouke van Dam Date: Sun, 24 Aug 2025 14:47:53 +0200 Subject: [PATCH] add doc-comments --- src/Json/EnumCacheJson.cs | 17 +++++ src/Json/Vector2Json.cs | 16 +++++ src/Json/VersionJson.cs | 16 +++++ src/Loader.cs | 117 ++++++++++++++++++++++++++++++++++ src/Managers/Audio.cs | 24 +++++++ src/Managers/AutoUpdate.cs | 20 ++++++ src/Managers/Compatibility.cs | 48 ++++++++++++++ src/Managers/Hub.cs | 32 ++++++++++ src/Managers/Loc.cs | 22 +++++++ src/Managers/Main.cs | 101 +++++++++++++++++++++++++++-- src/Managers/Visual.cs | 63 +++++++++++++++++- src/Mod.cs | 81 +++++++++++++++++++++++ src/NullableFix.cs | 26 ++++++++ src/Plugin.cs | 65 +++++++++++++++++++ src/Registry.cs | 77 ++++++++++++++++++++++ src/Util.cs | 58 +++++++++++++++++ 16 files changed, 775 insertions(+), 8 deletions(-) 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));