Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/Json/EnumCacheJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@

namespace PolyMod.Json;

/// <summary>
/// Converts an <see cref="Enum"/> to and from a JSON string using the <see cref="EnumCache{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the enum.</typeparam>
internal class EnumCacheJson<T> : JsonConverter<T> where T : struct, Enum
{
/// <summary>
/// Reads and converts the JSON to an enum value.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="typeToConvert">The type to convert.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
/// <returns>The converted value.</returns>
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return EnumCache<T>.GetType(reader.GetString());
}

/// <summary>
/// Writes a specified value as JSON.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="value">The value to convert to JSON.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStringValue(EnumCache<T>.GetName(value));
Expand Down
16 changes: 16 additions & 0 deletions src/Json/Vector2Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@

namespace PolyMod.Json;

/// <summary>
/// Converts a <see cref="Vector2"/> to and from a JSON array of two numbers.
/// </summary>
internal class Vector2Json : JsonConverter<Vector2>
{
/// <summary>
/// Reads and converts the JSON to a <see cref="Vector2"/>.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="typeToConvert">The type to convert.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
/// <returns>The converted value.</returns>
public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
List<float> values = new();
Expand All @@ -22,6 +32,12 @@ public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, Json
return new(values[0], values[1]);
}

/// <summary>
/// Writes a specified value as JSON.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="value">The value to convert to JSON.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
public override void Write(Utf8JsonWriter writer, Vector2 value, JsonSerializerOptions options)
{
writer.WriteStartArray();
Expand Down
16 changes: 16 additions & 0 deletions src/Json/VersionJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,29 @@

namespace PolyMod.Json;

/// <summary>
/// Converts a <see cref="Version"/> to and from a JSON string.
/// </summary>
internal class VersionJson : JsonConverter<Version>
{
/// <summary>
/// Reads and converts the JSON to a <see cref="Version"/>.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="typeToConvert">The type to convert.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
/// <returns>The converted value.</returns>
public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new(reader.GetString()!);
}

/// <summary>
/// Writes a specified value as JSON.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="value">The value to convert to JSON.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
Expand Down
117 changes: 117 additions & 0 deletions src/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@

namespace PolyMod;

/// <summary>
/// Handles loading of mods and their assets.
/// </summary>
public static class Loader
{
/// <summary>
/// Mappings from JSON data types to their corresponding C# types.
/// </summary>
internal static Dictionary<string, Type> typeMappings = new()
{
{ "tribeData", typeof(TribeData.Type) },
Expand All @@ -35,7 +41,15 @@ public static class Loader
{ "playerAbility", typeof(PlayerAbility.Type) },
{ "weaponData", typeof(UnitData.WeaponEnum) }
};

/// <summary>
/// List of custom game modes to be added.
/// </summary>
internal static List<GameModeButtonsInformation> gamemodes = new();

/// <summary>
/// Handlers for processing specific data types during mod loading.
/// </summary>
private static readonly Dictionary<Type, Action<JObject, bool>> typeHandlers = new()
{
[typeof(TribeData.Type)] = new((token, duringEnumCacheCreation) =>
Expand Down Expand Up @@ -131,8 +145,21 @@ public static class Loader
})
};

/// <summary>
/// Represents information for a custom game mode button.
/// </summary>
/// <param name="gameModeIndex">The index of the game mode.</param>
/// <param name="action">The action to perform when the button is clicked.</param>
/// <param name="buttonIndex">The index of the button in the UI.</param>
/// <param name="sprite">The sprite for the button.</param>
public record GameModeButtonsInformation(int gameModeIndex, UIButtonBase.ButtonAction action, int? buttonIndex, Sprite? sprite);

/// <summary>
/// Adds a new game mode button.
/// </summary>
/// <param name="id">The unique identifier for the game mode.</param>
/// <param name="action">The action to perform when the button is clicked.</param>
/// <param name="sprite">The sprite for the button.</param>
public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action, Sprite? sprite)
{
EnumCache<GameMode>.AddMapping(id, (GameMode)Registry.gameModesAutoidx);
Expand All @@ -141,12 +168,21 @@ public static void AddGameModeButton(string id, UIButtonBase.ButtonAction action
Registry.gameModesAutoidx++;
}

/// <summary>
/// Adds a new data type for patching.
/// </summary>
/// <param name="typeId">The identifier for the data type in JSON.</param>
/// <param name="type">The C# type corresponding to the identifier.</param>
public static void AddPatchDataType(string typeId, Type type)
{
if (!typeMappings.ContainsKey(typeId))
typeMappings.Add(typeId, type);
}

/// <summary>
/// Loads all mods from the mods directory.
/// </summary>
/// <param name="mods">A dictionary to populate with the loaded mods.</param>
internal static void LoadMods(Dictionary<string, Mod> mods)
{
Directory.CreateDirectory(Plugin.MODS_PATH);
Expand All @@ -159,6 +195,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
Mod.Manifest? manifest = null;
List<Mod.File> files = new();

// Load mod from directory or zip archive
if (Directory.Exists(modContainer))
{
foreach (var file in Directory.GetFiles(modContainer))
Expand Down Expand Up @@ -196,6 +233,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
}
}

// Validate manifest
if (manifest == null)
{
Plugin.logger.LogError($"Mod manifest not found in {modContainer}");
Expand Down Expand Up @@ -234,6 +272,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
Plugin.logger.LogInfo($"Registered mod {manifest.id}");
}

// Check dependencies
foreach (var (id, mod) in mods)
{
foreach (var dependency in mod.dependencies ?? Array.Empty<Mod.Dependency>())
Expand Down Expand Up @@ -271,6 +310,11 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
}
}

/// <summary>
/// Sorts mods based on their dependencies using a topological sort.
/// </summary>
/// <param name="mods">The dictionary of mods to sort.</param>
/// <returns>True if the mods could be sorted (no circular dependencies), false otherwise.</returns>
internal static bool SortMods(Dictionary<string, Mod> mods)
{
Stopwatch s = new();
Expand Down Expand Up @@ -330,6 +374,11 @@ internal static bool SortMods(Dictionary<string, Mod> mods)
return true;
}

/// <summary>
/// Loads an assembly file from a mod.
/// </summary>
/// <param name="mod">The mod the assembly belongs to.</param>
/// <param name="file">The assembly file to load.</param>
public static void LoadAssemblyFile(Mod mod, Mod.File file)
{
try
Expand Down Expand Up @@ -364,6 +413,11 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file)
}
}

/// <summary>
/// Loads a localization file from a mod.
/// </summary>
/// <param name="mod">The mod the localization file belongs to.</param>
/// <param name="file">The localization file to load.</param>
public static void LoadLocalizationFile(Mod mod, Mod.File file)
{
try
Expand All @@ -378,6 +432,11 @@ public static void LoadLocalizationFile(Mod mod, Mod.File file)
}
}

/// <summary>
/// Loads a sprite file from a mod.
/// </summary>
/// <param name="mod">The mod the sprite file belongs to.</param>
/// <param name="file">The sprite file to load.</param>
public static void LoadSpriteFile(Mod mod, Mod.File file)
{
string name = Path.GetFileNameWithoutExtension(file.name);
Expand All @@ -400,6 +459,10 @@ public static void LoadSpriteFile(Mod mod, Mod.File file)
Registry.sprites.Add(name, sprite);
}

/// <summary>
/// Updates a sprite with new information.
/// </summary>
/// <param name="name">The name of the sprite to update.</param>
public static void UpdateSprite(string name)
{
if (Registry.spriteInfos.ContainsKey(name) && Registry.sprites.ContainsKey(name))
Expand All @@ -415,6 +478,12 @@ public static void UpdateSprite(string name)
}
}

/// <summary>
/// Loads a sprite info file from a mod.
/// </summary>
/// <param name="mod">The mod the sprite info file belongs to.</param>
/// <param name="file">The sprite info file to load.</param>
/// <returns>A dictionary of sprite information, or null if an error occurred.</returns>
public static Dictionary<string, Visual.SpriteInfo>? LoadSpriteInfoFile(Mod mod, Mod.File file)
{
try
Expand Down Expand Up @@ -445,6 +514,11 @@ public static void UpdateSprite(string name)
}
}

/// <summary>
/// Loads an audio file from a mod.
/// </summary>
/// <param name="mod">The mod the audio file belongs to.</param>
/// <param name="file">The audio file to load.</param>
public static void LoadAudioFile(Mod mod, Mod.File file)
{
// AudioSource audioSource = new GameObject().AddComponent<AudioSource>();
Expand All @@ -454,6 +528,11 @@ public static void LoadAudioFile(Mod mod, Mod.File file)
// TODO: issue #71
}

/// <summary>
/// Loads a prefab info file from a mod and creates a new unit prefab.
/// </summary>
/// <param name="mod">The mod the prefab info file belongs to.</param>
/// <param name="file">The prefab info file to load.</param>
public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
{
try
Expand Down Expand Up @@ -493,6 +572,11 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
}
}

/// <summary>
/// Clears existing visual parts from a prefab and extracts the material.
/// </summary>
/// <param name="spriteContainer">The transform containing the sprite parts.</param>
/// <returns>The material of the original sprite parts.</returns>
private static Material? ClearExistingPartsAndExtractMaterial(Transform spriteContainer)
{
Material? material = null;
Expand All @@ -510,6 +594,13 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
return material;
}

/// <summary>
/// Applies new visual parts to a prefab.
/// </summary>
/// <param name="partInfos">A list of visual part information.</param>
/// <param name="spriteContainer">The transform to add the parts to.</param>
/// <param name="material">The material to use for the parts.</param>
/// <returns>A list of the created visual parts.</returns>
private static List<SkinVisualsReference.VisualPart> ApplyVisualParts(
List<Visual.VisualPartInfo> partInfos,
Transform spriteContainer,
Expand All @@ -525,6 +616,13 @@ public static void LoadPrefabInfoFile(Mod mod, Mod.File file)
return parts;
}

/// <summary>
/// Creates a single visual part for a prefab.
/// </summary>
/// <param name="info">The information for the visual part.</param>
/// <param name="parent">The parent transform for the part.</param>
/// <param name="material">The material to use for the part.</param>
/// <returns>The created visual part.</returns>
private static SkinVisualsReference.VisualPart CreateVisualPart(
Visual.VisualPartInfo info,
Transform parent,
Expand Down Expand Up @@ -567,11 +665,18 @@ private static SkinVisualsReference.VisualPart CreateVisualPart(
return visualPart;
}

/// <summary>
/// Loads and applies a game logic data patch from a mod.
/// </summary>
/// <param name="mod">The mod the patch belongs to.</param>
/// <param name="gld">The original game logic data.</param>
/// <param name="patch">The patch to apply.</param>
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<JObject>();
Expand Down Expand Up @@ -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<JObject>();
Expand All @@ -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");
}
Expand All @@ -626,6 +733,11 @@ public static void LoadGameLogicDataPatch(Mod mod, JObject gld, JObject patch)
}
}

/// <summary>
/// Loads an asset bundle from a mod.
/// </summary>
/// <param name="mod">The mod the asset bundle belongs to.</param>
/// <param name="file">The asset bundle file to load.</param>
public static void LoadAssetBundle(Mod mod, Mod.File file)
{
Registry.assetBundles.Add(
Expand All @@ -634,6 +746,11 @@ public static void LoadAssetBundle(Mod mod, Mod.File file)
);
}

/// <summary>
/// Handles skin data from a patch.
/// </summary>
/// <param name="gld">The original game logic data.</param>
/// <param name="patch">The patch to apply.</param>
public static void HandleSkins(JObject gld, JObject patch)
{
foreach (JToken jtoken in patch.SelectTokens("$.tribeData.*").ToArray())
Expand Down
Loading
Loading