diff --git a/Features/Extensions/ExiledExtensions.cs b/Features/Extensions/ExiledExtensions.cs new file mode 100644 index 0000000..00d65ff --- /dev/null +++ b/Features/Extensions/ExiledExtensions.cs @@ -0,0 +1,133 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using InventorySystem.Items.Pickups; +using LabApi.Features.Wrappers; +using LabApi.Loader; +using LabApi.Loader.Features.Plugins; +using UnityEngine; +namespace ProjectMER.Features.Extensions +{ + public static class ExiledExtensions + { + private static MethodInfo? CustomItemTrySpawnMethodInfo; + + private static MethodInfo? PickupGetBaseMethodInfo; + + public static Pickup? TrySpawn(string customItemName, Vector3 position, Quaternion rotation, Vector3 scale) + { + if (!TrySpawnCustomItem(customItemName, position, out Pickup? pickup)) + return null; + + pickup.Rotation = rotation; + pickup.Transform.localScale = scale; + + return pickup; + } + + internal static bool TrySpawnCustomItem(string name, Vector3 position, [NotNullWhen(true)] out Pickup? pickup) + { + pickup = null; + + if (CustomItemTrySpawnMethodInfo is null) + return false; + + object?[] args = [name, position, null]; + + CustomItemTrySpawnMethodInfo.Invoke(null, args); + + if (args[2] is null) + return false; + + pickup = GetPickupFromExiledPickup(args[2]!); + + return pickup is not null; + } + + + internal static Pickup? GetPickupFromExiledPickup(object pickup) + { + if (PickupGetBaseMethodInfo is null) + return null; + + object obj = PickupGetBaseMethodInfo.Invoke(pickup, []); + + if (obj is not ItemPickupBase pickupBase) + { + Logger.Warn("Failed to get ItemPickupBase from Exiled Pickup"); + return null; + } + + return Pickup.Get(pickupBase); + } + + internal static void TryInitialize() + { + const string ErrorMessage = "Failed to find a component of Exiled! This can be ignored if you do not use Exiled Custom Items in ProjectMER. Cause: "; + + KeyValuePair kvp = PluginLoader.Plugins.FirstOrDefault(kvp => kvp.Key.Name is "Exiled Loader"); + + if (kvp.Value is null) + { + // No debug in config :( + // not printed because ppl can just not use Exiled, but having Exiled.Loader and not API / CI is suspicious + // Logger.Debug(ErrorMessage + "Failed to get ExiledLoader Plugin"); + return; + } + + Assembly? exiledLoaderAssembly = kvp.Value; + + object? plugin = exiledLoaderAssembly.GetType("Exiled.Loader.Loader")?.GetMethod("GetPlugin")?.Invoke(null, ["exiled_custom_items"]); + + if (plugin is null) + { + // It's possible people use Exiled without CI, so I'll leave this out too + // Logger.Debug(ErrorMessage + "Failed to get CustomItems Plugin"); + return; + } + + object? nullableAssembly = plugin.GetType().GetProperty("Assembly")?.GetValue(plugin); + + if (nullableAssembly is not Assembly customItemAssembly) + { + Logger.Warn(ErrorMessage + "Failed to get CustomItems Assembly"); + return; + } + + Type? customItem = customItemAssembly.GetType("Exiled.CustomItems.API.Features.CustomItem"); + + if (customItem is null) + { + Logger.Warn(ErrorMessage + "Failed to get CustomItem Type"); + return; + } + + CustomItemTrySpawnMethodInfo = customItem.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(method => method.Name is "TrySpawn" && method.GetParameters().Any(parameter => parameter.ParameterType == typeof(string))); + + if (CustomItemTrySpawnMethodInfo is null) + Logger.Warn(ErrorMessage + "Failed to get CustomItem.TrySpawn method"); + + Assembly? apiAssembly = PluginLoader.Dependencies.FirstOrDefault(assembly => assembly.GetName().Name is "Exiled.API"); + + if (apiAssembly is null) + { + Logger.Warn(ErrorMessage + "Failed to get Exiled.API Assembly"); + return; + } + + Type? pickupType = apiAssembly.GetType("Exiled.API.Features.Pickups.Pickup"); + + if (pickupType is null) + { + Logger.Warn(ErrorMessage + "Failed to get Pickup Type"); + return; + } + + PickupGetBaseMethodInfo = pickupType.GetProperty("Base")?.GetGetMethod(); + + if (PickupGetBaseMethodInfo is null) + { + Logger.Warn(ErrorMessage + "Failed to get Pickup::Base getter method"); + } + } + } +} \ No newline at end of file diff --git a/Features/Serializable/Schematics/SchematicBlockData.cs b/Features/Serializable/Schematics/SchematicBlockData.cs index 5e2ae64..1878fb1 100644 --- a/Features/Serializable/Schematics/SchematicBlockData.cs +++ b/Features/Serializable/Schematics/SchematicBlockData.cs @@ -143,7 +143,17 @@ private GameObject CreatePickup(SchematicObject schematicObject) if (Properties.TryGetValue("Chance", out object property) && UnityEngine.Random.Range(0, 101) > Convert.ToSingle(property)) return new("Empty Pickup"); - Pickup pickup = Pickup.Create((ItemType)Convert.ToInt32(Properties["ItemType"]), Vector3.zero)!; + Pickup? pickup = null; + if (Properties["CustomItem"] is string str && str != string.Empty) + { + ExiledExtensions.TrySpawnCustomItem(str, Vector3.zero, out pickup); + } + + pickup ??= Pickup.Create((ItemType)Convert.ToInt32(Properties["ItemType"]), Vector3.zero); + + if (pickup is null) + return new("Empty Pickup"); + if (Properties.ContainsKey("Locked")) PickupEventsHandler.ButtonPickups.Add(pickup.Serial, schematicObject); diff --git a/Features/Serializable/SerializableItemSpawnpoint.cs b/Features/Serializable/SerializableItemSpawnpoint.cs index d1eae85..f412c84 100644 --- a/Features/Serializable/SerializableItemSpawnpoint.cs +++ b/Features/Serializable/SerializableItemSpawnpoint.cs @@ -14,6 +14,7 @@ namespace ProjectMER.Features.Serializable; public class SerializableItemSpawnpoint : SerializableObject, IIndicatorDefinition { public ItemType ItemType { get; set; } = ItemType.Lantern; + public string CustomItem { get; set; } = string.Empty; public float Weight { get; set; } = -1; public string AttachmentsCode { get; set; } = "-1"; public uint NumberOfItems { get; set; } = 1; @@ -41,7 +42,14 @@ public class SerializableItemSpawnpoint : SerializableObject, IIndicatorDefiniti for (int i = 0; i < NumberOfItems; i++) { - Pickup pickup = Pickup.Create(ItemType, position, rotation, Scale)!; + Pickup? pickup = CustomItem != string.Empty ? ExiledExtensions.TrySpawn(CustomItem, position, rotation, Scale) : Pickup.Create(ItemType, position, rotation, Scale); + + if (pickup is null) + { + // there is no Debug config option? + // Logger.Debug($"Failed to spawn an item with CustomItem: {CustomItem}", true); + continue; + } pickup.Transform.parent = itemSpawnPoint.transform; if (Weight != -1) diff --git a/ProjectMER.cs b/ProjectMER.cs index 330c7bb..bf43646 100644 --- a/ProjectMER.cs +++ b/ProjectMER.cs @@ -8,6 +8,7 @@ using ProjectMER.Configs; using ProjectMER.Events.Handlers.Internal; using ProjectMER.Features; +using ProjectMER.Features.Extensions; namespace ProjectMER; @@ -90,6 +91,9 @@ public override void Enable() Logger.Debug("FileSystemWatcher enabled!"); } + + // plugin loading is synchronous (minus exiled delaying 1 tick), so after all plugins loaded, try checking for Exiled and Exiled CI + Timing.CallDelayed(1, ExiledExtensions.TryInitialize); } private void OnMapFileChanged(object _, FileSystemEventArgs ev) @@ -131,7 +135,7 @@ public override void Disable() public override string Author => "Michal78900"; - public override Version Version => new Version(2025, 8, 4, 2); + public override Version Version => new Version(2025, 11, 2, 1); public override Version RequiredApiVersion => new Version(1, 0, 0, 0); }