Decompiled source of EnemySkinManager v0.1.1

EnemySkinManager.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("EnemySkinManager")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("A mod library for R.E.P.O. to manage enemy skin variations.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("EnemySkinManager")]
[assembly: AssemblyTitle("EnemySkinManager")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace EnemySkinManager
{
	public class SkinData
	{
		public string Identifier { get; set; }

		public Material[] Materials { get; set; }

		public Mesh SharedMesh { get; set; }

		public Dictionary<string, Texture2D> TextureOverrides { get; set; }

		public bool OverrideProtectedParts { get; set; } = false;


		public SkinData(string id, Material[] materials = null, Mesh mesh = null, Dictionary<string, Texture2D> textures = null, bool overrideProtectedParts = false)
		{
			Identifier = id;
			Materials = (Material[])(((object)materials) ?? ((object)new Material[0]));
			SharedMesh = mesh;
			TextureOverrides = textures ?? new Dictionary<string, Texture2D>();
			OverrideProtectedParts = overrideProtectedParts;
		}
	}
	public static class EnemySkinManager
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> MasterEnableConfig;

		private static ConfigFile AppConfig;

		public const string VanillaSkinIdentifier = "Vanilla";

		internal static readonly Dictionary<string, List<SkinData>> RegisteredSkins = new Dictionary<string, List<SkinData>>();

		private static readonly Dictionary<string, Dictionary<string, ConfigEntry<int>>> enemySkinWeightsConfigs = new Dictionary<string, Dictionary<string, ConfigEntry<int>>>();

		public static Func<string, string, bool> IsSkinEnabledCheck = (string enemyId, string skinId) => true;

		internal static void Initialize(ManualLogSource log, ConfigEntry<bool> masterEnableConfig, ConfigFile config)
		{
			Log = log;
			MasterEnableConfig = masterEnableConfig;
			AppConfig = config;
			Log.LogInfo((object)"EnemySkinManager Initialized. Skins will be configured with weights per enemy.");
		}

		public static void RegisterSkin(string enemyId, SkinData newSkin, string? friendlyCategoryName = null)
		{
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Expected O, but got Unknown
			if (string.IsNullOrEmpty(enemyId) || newSkin == null || string.IsNullOrEmpty(newSkin.Identifier))
			{
				Log.LogError((object)"Invalid parameters for RegisterSkin.");
				return;
			}
			if (!RegisteredSkins.ContainsKey(enemyId))
			{
				RegisteredSkins[enemyId] = new List<SkinData>();
			}
			if (!RegisteredSkins[enemyId].Any((SkinData s) => s.Identifier == newSkin.Identifier))
			{
				RegisteredSkins[enemyId].Add(newSkin);
				Log.LogInfo((object)("Registered skin '" + newSkin.Identifier + "' for enemy '" + enemyId + "'."));
			}
			string text = (string.IsNullOrEmpty(friendlyCategoryName) ? enemyId : friendlyCategoryName);
			string text2 = text + " Skins";
			if (!enemySkinWeightsConfigs.ContainsKey(enemyId))
			{
				Log.LogInfo((object)("Creating new weight configuration section for enemy: " + enemyId + " (displaying as '" + text2 + "')"));
				enemySkinWeightsConfigs.Add(enemyId, new Dictionary<string, ConfigEntry<int>>());
				if (!enemySkinWeightsConfigs[enemyId].ContainsKey("Vanilla"))
				{
					Log.LogInfo((object)("Creating weight config for 'Vanilla' skin in section '" + text2 + "'"));
					ConfigEntry<int> value = AppConfig.Bind<int>(text2, "Vanilla", 10, new ConfigDescription("Spawn weight for 'Vanilla' (original game skin) on " + text + ". 0 = disabled. Higher values increase spawn chance relative to other enabled skins.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
					enemySkinWeightsConfigs[enemyId].Add("Vanilla", value);
				}
			}
			if (!enemySkinWeightsConfigs[enemyId].ContainsKey(newSkin.Identifier))
			{
				Log.LogInfo((object)("Creating weight config for skin '" + newSkin.Identifier + "' in section '" + text2 + "'"));
				ConfigEntry<int> value2 = AppConfig.Bind<int>(text2, newSkin.Identifier, 10, new ConfigDescription("Spawn weight for '" + newSkin.Identifier + "' on " + text + ". 0 = disabled. Higher values increase spawn chance relative to other enabled skins.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
				enemySkinWeightsConfigs[enemyId].Add(newSkin.Identifier, value2);
			}
		}

		public static SkinData GetRandomSkinForEnemy(string enemyId)
		{
			if (MasterEnableConfig != null && !MasterEnableConfig.Value)
			{
				return null;
			}
			if (!enemySkinWeightsConfigs.TryGetValue(enemyId, out var value))
			{
				Log.LogWarning((object)("No weight configuration section found for enemy '" + enemyId + "'. Skins may be registered, but weights are missing. Using vanilla."));
				return null;
			}
			RegisteredSkins.TryGetValue(enemyId, out var value2);
			value2 = value2 ?? new List<SkinData>();
			List<KeyValuePair<SkinData, int>> list = new List<KeyValuePair<SkinData, int>>();
			int num = 0;
			if (value.TryGetValue("Vanilla", out var value3) && value3.Value > 0)
			{
				list.Add(new KeyValuePair<SkinData, int>(null, value3.Value));
				num += value3.Value;
			}
			foreach (SkinData item in value2)
			{
				if (value.TryGetValue(item.Identifier, out var value4) && value4.Value > 0)
				{
					list.Add(new KeyValuePair<SkinData, int>(item, value4.Value));
					num += value4.Value;
				}
			}
			if (list.Count == 0 || num == 0)
			{
				return null;
			}
			int num2 = Random.Range(0, num);
			foreach (KeyValuePair<SkinData, int> item2 in list)
			{
				if (num2 < item2.Value)
				{
					return item2.Key;
				}
				num2 -= item2.Value;
			}
			if (list.Any((KeyValuePair<SkinData, int> opt) => opt.Key == null && opt.Value > 0))
			{
				return null;
			}
			return list.FirstOrDefault((KeyValuePair<SkinData, int> opt) => opt.Key != null).Key;
		}

		public static void ApplySkin(GameObject enemyInstance, SkinData skinData)
		{
			if ((Object)(object)enemyInstance == (Object)null || skinData == null)
			{
				Log.LogWarning((object)"ApplySkin called with null enemyInstance or skinData.");
				return;
			}
			Renderer componentInChildren = enemyInstance.GetComponentInChildren<Renderer>();
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Log.LogWarning((object)("Could not find a Renderer component on '" + ((Object)enemyInstance).name + "' or its children to apply skin '" + (skinData.Identifier ?? "Unnamed") + "'."));
				return;
			}
			if (skinData.Materials != null && skinData.Materials.Length != 0)
			{
				if (componentInChildren.sharedMaterials.Length != skinData.Materials.Length)
				{
					Log.LogWarning((object)("Skin '" + skinData.Identifier + "' for " + ((Object)enemyInstance).name + ": " + $"Provided material count ({skinData.Materials.Length}) " + $"differs from renderer's material slot count ({componentInChildren.sharedMaterials.Length}). " + "Applying materials anyway. This might lead to an incomplete or unexpected appearance if counts don't effectively match."));
				}
				componentInChildren.sharedMaterials = skinData.Materials;
				if (Plugin.EnableDebugLogging.Value)
				{
					Log.LogDebug((object)$"Applied {skinData.Materials.Length} material(s) from skin '{skinData.Identifier}' to {((Object)enemyInstance).name}.");
				}
			}
			else if (Plugin.EnableDebugLogging.Value)
			{
				Log.LogDebug((object)("Skin '" + (skinData.Identifier ?? "Unnamed") + "' has no materials defined."));
			}
			if (skinData.TextureOverrides != null && skinData.TextureOverrides.Count > 0)
			{
				foreach (KeyValuePair<string, Texture2D> textureOverride in skinData.TextureOverrides)
				{
					Material[] sharedMaterials = componentInChildren.sharedMaterials;
					foreach (Material val in sharedMaterials)
					{
						if (val.HasProperty(textureOverride.Key))
						{
							val.SetTexture(textureOverride.Key, (Texture)(object)textureOverride.Value);
							if (Plugin.EnableDebugLogging.Value)
							{
								Log.LogDebug((object)("Applied texture override '" + textureOverride.Key + "' to material '" + ((Object)val).name + "' on '" + ((Object)componentInChildren).name + "'."));
							}
						}
					}
				}
			}
			else if (Plugin.EnableDebugLogging.Value)
			{
				Log.LogDebug((object)("Skin '" + (skinData.Identifier ?? "Unnamed") + "' has no texture overrides defined."));
			}
			if ((Object)(object)skinData.SharedMesh != (Object)null)
			{
				MeshFilter component = ((Component)componentInChildren).GetComponent<MeshFilter>();
				SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((componentInChildren is SkinnedMeshRenderer) ? componentInChildren : null);
				if ((Object)(object)component != (Object)null)
				{
					component.sharedMesh = skinData.SharedMesh;
					if (Plugin.EnableDebugLogging.Value)
					{
						Log.LogDebug((object)("Applied shared mesh '" + ((Object)skinData.SharedMesh).name + "' to MeshFilter on '" + ((Object)componentInChildren).name + "'."));
					}
				}
				else if ((Object)(object)val2 != (Object)null)
				{
					val2.sharedMesh = skinData.SharedMesh;
					if (Plugin.EnableDebugLogging.Value)
					{
						Log.LogDebug((object)("Applied shared mesh '" + ((Object)skinData.SharedMesh).name + "' to SkinnedMeshRenderer on '" + ((Object)componentInChildren).name + "'."));
					}
				}
				else
				{
					Log.LogWarning((object)("Could not apply mesh '" + ((Object)skinData.SharedMesh).name + "'. No MeshFilter or SkinnedMeshRenderer found on '" + ((Object)componentInChildren).name + "'."));
				}
			}
			Log.LogInfo((object)("Successfully applied skin '" + (skinData.Identifier ?? "Unnamed") + "' to '" + ((Object)enemyInstance).name + "'."));
		}
	}
	[BepInPlugin("JustSomeDevGuy.EnemySkinManager.Alpha", "EnemySkinManager (ALPHA)", "0.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony harmony;

		private static ConfigEntry<bool> MasterEnableToggle;

		internal static ManualLogSource Log { get; private set; }

		public static ConfigEntry<bool> EnableDebugLogging { get; private set; }

		private void Awake()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			harmony = new Harmony("JustSomeDevGuy.EnemySkinManager.Alpha");
			MasterEnableToggle = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Master Enable", true, "Globally enable or disable all custom skins from this manager.");
			EnableDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable Detailed Logging", false, "Enable detailed debug messages in the log, useful for troubleshooting skin application.");
			EnemySkinManager.Initialize(Log, MasterEnableToggle, ((BaseUnityPlugin)this).Config);
			harmony.PatchAll();
			Log.LogInfo((object)"Registering default 'Vanilla' skins...");
			string[] array = new string[19]
			{
				"Duck", "Animal", "Banger", "Bowtie", "Chef", "Clown", "Gnome", "Headman", "Hidden", "Huntsman",
				"Peeper", "Mentalist", "Reaper", "Robe", "Rugrat", "Shadow Child", "Spewer", "Trudge", "Upscream"
			};
			string[] array2 = array;
			foreach (string enemyId in array2)
			{
				EnemySkinManager.RegisterSkin(enemyId, new SkinData("Vanilla"));
			}
			Log.LogInfo((object)"Finished registering default 'Vanilla' skins.");
			Log.LogInfo((object)"Plugin JustSomeDevGuy.EnemySkinManager.Alpha patched methods.");
			Log.LogInfo((object)"Plugin JustSomeDevGuy.EnemySkinManager.Alpha is loaded!");
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "JustSomeDevGuy.EnemySkinManager.Alpha";

		public const string PLUGIN_NAME = "EnemySkinManager (ALPHA)";

		public const string PLUGIN_VERSION = "0.1.0";
	}
}
namespace EnemySkinManager.Patches
{
	[HarmonyPatch]
	internal class EnemySpawnPatches
	{
		private const string LOG_PREFIX = "[EnemySkinMod] ";

		private static ManualLogSource Log => Plugin.Log;

		[HarmonyPatch(typeof(EnemyParent), "Spawn")]
		[HarmonyPostfix]
		private static void EnemyParent_Spawn_Postfix(EnemyParent __instance)
		{
			Plugin.Log.LogInfo((object)string.Format("{0}--- EnemyParent_Spawn_Postfix executing for instance {1} (Name: {2}) ---", "[EnemySkinMod] ", ((Object)__instance).GetInstanceID(), ((Object)((Component)__instance).gameObject).name));
			if (!EnemySkinManager.MasterEnableConfig.Value)
			{
				if (Plugin.EnableDebugLogging.Value)
				{
					Plugin.Log.LogDebug((object)"EnemySkinManager is disabled via config. Skipping skin application.");
				}
				return;
			}
			string name = ((Object)((Component)__instance).gameObject).name;
			string text = name.Replace("(Clone)", "").Trim();
			string text2 = text;
			if (text.StartsWith("Enemy - "))
			{
				text2 = text.Substring("Enemy - ".Length).Trim();
			}
			if (string.IsNullOrEmpty(text2))
			{
				Plugin.Log.LogWarning((object)("Could not get valid Enemy ID from GameObject name: '" + name + "'. Cannot apply skin."));
				return;
			}
			Plugin.Log.LogInfo((object)("Enemy ID derived from GameObject name: '" + text2 + "' (Original: '" + name + "')"));
			if (!EnemySkinManager.RegisteredSkins.ContainsKey(text2) || EnemySkinManager.RegisteredSkins[text2].Count == 0)
			{
				Plugin.Log.LogInfo((object)("No skins registered for enemy ID '" + text2 + "'. Skipping."));
				return;
			}
			Plugin.Log.LogInfo((object)("Getting random skin for enemy '" + text2 + "'..."));
			SkinData randomSkinForEnemy = EnemySkinManager.GetRandomSkinForEnemy(text2);
			if (randomSkinForEnemy == null || randomSkinForEnemy.Identifier == null)
			{
				Plugin.Log.LogInfo((object)("GetRandomSkinForEnemy returned no specific skin (maybe all disabled or only 'Vanilla' exists and was chosen). Enemy ID: '" + text2 + "'. No custom skin applied."));
				return;
			}
			Plugin.Log.LogInfo((object)("Selected skin '" + randomSkinForEnemy.Identifier + "' for enemy '" + text2 + "'. Applying..."));
			GameObject gameObject = ((Component)__instance).gameObject;
			if ((Object)(object)gameObject == (Object)null)
			{
				Plugin.Log.LogError((object)"EnemyParent_Spawn_Postfix triggered on a null instance or GameObject.");
			}
			else
			{
				ApplySkinLogic(gameObject, randomSkinForEnemy);
			}
		}

		private static bool IsSkinEnabledCheck(string enemyId, string skinId)
		{
			return EnemySkinManager.IsSkinEnabledCheck(enemyId, skinId);
		}

		private static void ApplySkinLogic(GameObject enemyInstance, SkinData skinData)
		{
			if ((Object)(object)enemyInstance == (Object)null || skinData.Identifier == null)
			{
				Plugin.Log.LogError((object)"ApplySkinLogic: Received null enemyInstance or skinData.Identifier.");
				return;
			}
			if (skinData.TextureOverrides != null && skinData.TextureOverrides.Count > 0)
			{
				Plugin.Log.LogInfo((object)string.Format("{0}ApplySkinLogic: Attempting to apply {1} texture overrides using INSTANCED materials.", "[EnemySkinMod] ", skinData.TextureOverrides.Count));
				bool flag = false;
				Renderer[] componentsInChildren = enemyInstance.GetComponentsInChildren<Renderer>(true);
				if (componentsInChildren == null || componentsInChildren.Length == 0)
				{
					Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: No Renderers found on '" + ((Object)enemyInstance).name + "' or its children."));
				}
				else
				{
					if (Plugin.EnableDebugLogging.Value)
					{
						Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Found {1} renderers on '{2}'. Iterating...", "[EnemySkinMod] ", componentsInChildren.Length, ((Object)enemyInstance).name));
					}
					foreach (KeyValuePair<string, Texture2D> textureOverride in skinData.TextureOverrides)
					{
						string key = textureOverride.Key;
						Texture2D value = textureOverride.Value;
						Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic: Processing override: Key='" + key + "', Texture='" + (((Object)(object)value != (Object)null) ? ((Object)value).name : "NULL") + "'"));
						if (string.IsNullOrEmpty(key) || (Object)(object)value == (Object)null)
						{
							Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: Skipping invalid texture override entry (null key or value). Key: " + key));
							continue;
						}
						bool flag2 = false;
						Renderer[] array = componentsInChildren;
						foreach (Renderer val in array)
						{
							Material[] materials = val.materials;
							for (int j = 0; j < materials.Length; j++)
							{
								Material val2 = materials[j];
								if (val2.HasProperty(key))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Found property '{1}' on material instance '{2}' (Index {3}) of renderer '{4}'. Applying texture '{5}'.", "[EnemySkinMod] ", key, ((Object)val2).name, j, ((Object)val).name, ((Object)value).name));
									}
									val2.SetTexture(key, (Texture)(object)value);
									flag2 = true;
									flag = true;
								}
								else if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Material instance '{1}' (Index {2}) of renderer '{3}' does not have property '{4}'.", "[EnemySkinMod] ", ((Object)val2).name, j, ((Object)val).name, key));
								}
							}
							val.materials = materials;
						}
						if (!flag2)
						{
							Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: Texture override key '" + key + "' was not found on ANY material instance of ANY renderer on '" + ((Object)enemyInstance).name + "'."));
						}
					}
				}
				if (flag)
				{
					Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic: Finished applying texture overrides for '" + skinData.Identifier + "' on '" + ((Object)enemyInstance).name + "'."));
				}
				else
				{
					Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: Finished applying texture overrides for '" + skinData.Identifier + "' on '" + ((Object)enemyInstance).name + "', but no matching properties were found on any materials."));
				}
			}
			else if (Plugin.EnableDebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skin '" + skinData.Identifier + "' has no texture overrides defined."));
			}
			if (skinData.Materials != null && skinData.Materials.Length != 0)
			{
				Plugin.Log.LogInfo((object)string.Format("{0}ApplySkinLogic: Skin '{1}' has {2} material(s). Attempting to apply.", "[EnemySkinMod] ", skinData.Identifier, skinData.Materials.Length));
				bool flag3 = false;
				Renderer[] componentsInChildren2 = enemyInstance.GetComponentsInChildren<Renderer>(true);
				if (componentsInChildren2 == null || componentsInChildren2.Length == 0)
				{
					Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: No Renderers found on '" + ((Object)enemyInstance).name + "' or its children for shared material application."));
				}
				else
				{
					string text = ((Object)enemyInstance).name;
					if (text.EndsWith("(Clone)"))
					{
						text = text.Substring(0, text.Length - "(Clone)".Length);
					}
					if (text.StartsWith("Enemy - "))
					{
						text = text.Substring("Enemy - ".Length);
					}
					switch (text)
					{
					case "Head":
					{
						Plugin.Log.LogInfo((object)"[EnemySkinMod] ApplySkinLogic: Special handling for enemy 'Head'. Targeting 'Eye L Mesh' and 'Eye R Mesh'.");
						Renderer[] array6 = componentsInChildren2;
						foreach (Renderer val14 in array6)
						{
							if ((((Object)((Component)val14).gameObject).name == "Eye L Mesh" || ((Object)((Component)val14).gameObject).name == "Eye R Mesh") && skinData.Materials.Length != 0)
							{
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying material '" + ((Object)skinData.Materials[0]).name + "' to '" + ((Object)((Component)val14).gameObject).name + "' on 'Head' enemy."));
								}
								val14.material = skinData.Materials[0];
								flag3 = true;
							}
						}
						break;
					}
					case "Upscream":
					{
						Plugin.Log.LogInfo((object)"[EnemySkinMod] ApplySkinLogic: Special handling for enemy 'Upscream'. Applying head/leg materials to specific parts.");
						if (skinData.Materials == null || skinData.Materials.Length < 2)
						{
							ManualLogSource log = Plugin.Log;
							string identifier = skinData.Identifier;
							Material[] materials2 = skinData.Materials;
							log.LogError((object)string.Format("{0}ApplySkinLogic: 'Upscream' skin '{1}' requires 2 materials (head, legs) but found {2}. Aborting skin application for this enemy.", "[EnemySkinMod] ", identifier, (materials2 != null) ? materials2.Length : 0));
							break;
						}
						Material val7 = skinData.Materials[0];
						Material val8 = skinData.Materials[1];
						if ((Object)(object)val7 == (Object)null || (Object)(object)val8 == (Object)null)
						{
							Plugin.Log.LogError((object)string.Format("{0}ApplySkinLogic: One or both materials for 'Upscream' skin '{1}' are null. Head: {2}, Legs: {3}. Aborting.", "[EnemySkinMod] ", skinData.Identifier, (Object)(object)val7 == (Object)null, (Object)(object)val8 == (Object)null));
							break;
						}
						Renderer[] array4 = componentsInChildren2;
						foreach (Renderer val9 in array4)
						{
							string name3 = ((Object)((Component)val9).gameObject).name;
							switch (name3)
							{
							default:
								if (!(name3 == "eye_R"))
								{
									switch (name3)
									{
									default:
										if (!(name3 == "leg front R3"))
										{
											goto end_IL_0747;
										}
										break;
									case "leg back L1":
									case "leg back L2":
									case "leg back R1":
									case "leg back R2":
									case "leg front L1":
									case "leg front L2":
									case "leg front R1":
									case "leg front R2":
									case "leg back L3":
									case "leg back R3":
									case "leg front L3":
										break;
									}
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying LEGS material '" + ((Object)val8).name + "' to '" + name3 + "' on 'Upscream'."));
									}
									val9.material = val8;
									flag3 = true;
									break;
								}
								goto case "Upscream_Head - Mouth Closed";
							case "Upscream_Head - Mouth Closed":
							case "Upscream_Head - Mouth Open":
							case "eye_L":
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying HEAD material '" + ((Object)val7).name + "' to '" + name3 + "' on 'Upscream'."));
									}
									val9.material = val7;
									flag3 = true;
									break;
								}
								end_IL_0747:
								break;
							}
						}
						break;
					}
					case "Duck":
					{
						Plugin.Log.LogInfo((object)"[EnemySkinMod] ApplySkinLogic: Special handling for enemy 'Duck'. Applying two materials to specified parts and preserving particles.");
						if (skinData.Materials == null || skinData.Materials.Length < 2)
						{
							ManualLogSource log2 = Plugin.Log;
							string identifier2 = skinData.Identifier;
							Material[] materials3 = skinData.Materials;
							log2.LogWarning((object)string.Format("{0}ApplySkinLogic: SkinData for '{1}' on 'Duck' requires 2 materials, but found {2}. No materials applied.", "[EnemySkinMod] ", identifier2, (materials3 != null) ? materials3.Length : 0));
							break;
						}
						Material val10 = skinData.Materials[0];
						Material val11 = skinData.Materials[1];
						Renderer[] array5 = componentsInChildren2;
						foreach (Renderer val12 in array5)
						{
							bool flag6 = false;
							string name4 = ((Object)((Component)val12).gameObject).name;
							switch (name4)
							{
							default:
								if (!(name4 == "mesh Mon Mouth Bot"))
								{
									break;
								}
								goto case "mesh Eye R";
							case "mesh Eye R":
							case "mesh Eye L":
							case "mesh Mon Eye L":
							case "mesh Mon Eye R":
							case "mesh Mon Mouth Top":
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name4 + "' on 'Duck' to keep its material vanilla (eye or mouth part)."));
								}
								flag6 = true;
								break;
							}
							if (!flag6)
							{
								string text4 = name4.ToLower();
								if (text4.Contains("particle") || text4.Contains("effect") || text4.Contains("vfx"))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name4 + "' on 'Duck' due to name containing particle/effect keywords."));
									}
									flag6 = true;
								}
								if (!flag6 && val12.sharedMaterials != null)
								{
									Material[] sharedMaterials3 = val12.sharedMaterials;
									foreach (Material val13 in sharedMaterials3)
									{
										if (!((Object)(object)val13 != (Object)null) || !((Object)(object)val13.shader != (Object)null))
										{
											continue;
										}
										string name5 = ((Object)val13.shader).name;
										if (name5 == "Particles/Standard Unlit" || name5 == "Particles/Standard Surface")
										{
											if (Plugin.EnableDebugLogging.Value)
											{
												Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name4 + "' on 'Duck' due to material '" + ((Object)val13).name + "' using shader '" + name5 + "'."));
											}
											flag6 = true;
											break;
										}
									}
								}
							}
							if (flag6)
							{
								continue;
							}
							switch (name4)
							{
							default:
								if (!(name4 == "mesh Mon Head"))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying main body material (" + ((Object)val10).name + ") to '" + name4 + "' on 'Duck'."));
									}
									val12.material = val10;
									flag3 = true;
									break;
								}
								goto case "mesh Mon Body";
							case "mesh Mon Body":
							case "mesh Mon Wing L":
							case "mesh Mon Wing R":
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying 'Monster' material (" + ((Object)val11).name + ") to '" + name4 + "' on 'Duck'."));
								}
								val12.material = val11;
								flag3 = true;
								break;
							}
						}
						break;
					}
					case "Animal":
					{
						Plugin.Log.LogInfo((object)"[EnemySkinMod] ApplySkinLogic: Special handling for enemy 'Animal'. Preserving 'Eyes' and particles.");
						if (skinData.Materials == null || skinData.Materials.Length == 0 || (Object)(object)skinData.Materials[0] == (Object)null)
						{
							Plugin.Log.LogError((object)("[EnemySkinMod] ApplySkinLogic: Skin '" + skinData.Identifier + "' for 'Animal' has no valid material at index 0. Aborting skin application for this instance."));
							return;
						}
						Material val15 = skinData.Materials[0];
						Renderer[] array7 = componentsInChildren2;
						foreach (Renderer val16 in array7)
						{
							bool flag7 = false;
							string name6 = ((Object)((Component)val16).gameObject).name;
							if (name6 == "Eyes")
							{
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name6 + "' on 'Animal' to keep its material vanilla."));
								}
								flag7 = true;
							}
							if (!flag7)
							{
								string text5 = name6.ToLower();
								if (text5.Contains("particle") || text5.Contains("effect") || text5.Contains("vfx"))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name6 + "' on 'Animal' due to name containing particle/effect keywords."));
									}
									flag7 = true;
								}
								if (!flag7 && val16.sharedMaterials != null)
								{
									Material[] sharedMaterials4 = val16.sharedMaterials;
									foreach (Material val17 in sharedMaterials4)
									{
										if (!((Object)(object)val17 != (Object)null) || !((Object)(object)val17.shader != (Object)null))
										{
											continue;
										}
										string name7 = ((Object)val17.shader).name;
										if (name7 == "Particles/Standard Unlit" || name7 == "Particles/Standard Surface")
										{
											if (Plugin.EnableDebugLogging.Value)
											{
												Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + name6 + "' on 'Animal' due to material '" + ((Object)val17).name + "' using shader '" + name7 + "'."));
											}
											flag7 = true;
											break;
										}
									}
								}
							}
							if (!flag7)
							{
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Applying skin material (" + ((Object)val15).name + ") to '" + name6 + "' on 'Animal'."));
								}
								val16.material = val15;
							}
						}
						break;
					}
					case "Bowtie":
					{
						Plugin.Log.LogInfo((object)"[EnemySkinMod] ApplySkinLogic: Special handling for enemy 'Bowtie'. Preserving 'bend body' and particle effects.");
						if (skinData.Materials == null || skinData.Materials.Length == 0)
						{
							Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: SkinData for '" + skinData.Identifier + "' on 'Bowtie' has no materials. No materials applied."));
							break;
						}
						Renderer[] array3 = componentsInChildren2;
						foreach (Renderer val5 in array3)
						{
							bool flag5 = false;
							if (((Object)((Component)val5).gameObject).name == "bend body")
							{
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)"[EnemySkinMod] ApplySkinLogic: Skipping renderer 'bend body' on 'Bowtie' to keep its material vanilla.");
								}
								flag5 = true;
							}
							if (!flag5)
							{
								string text3 = ((Object)((Component)val5).gameObject).name.ToLower();
								if (text3.Contains("particle") || text3.Contains("effect") || text3.Contains("vfx"))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + ((Object)((Component)val5).gameObject).name + "' on 'Bowtie' due to name containing particle/effect keywords."));
									}
									flag5 = true;
								}
								if (!flag5 && val5.sharedMaterials != null)
								{
									Material[] sharedMaterials2 = val5.sharedMaterials;
									foreach (Material val6 in sharedMaterials2)
									{
										if (!((Object)(object)val6 != (Object)null) || !((Object)(object)val6.shader != (Object)null))
										{
											continue;
										}
										string name2 = ((Object)val6.shader).name;
										if (name2 == "Particles/Standard Unlit" || name2 == "Particles/Standard Surface")
										{
											if (Plugin.EnableDebugLogging.Value)
											{
												Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + ((Object)((Component)val5).gameObject).name + "' on 'Bowtie' due to material '" + ((Object)val6).name + "' using shader '" + name2 + "'."));
											}
											flag5 = true;
											break;
										}
									}
								}
							}
							if (flag5)
							{
								continue;
							}
							if (val5.sharedMaterials == null || val5.sharedMaterials.Length == 0)
							{
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Renderer '" + ((Object)((Component)val5).gameObject).name + "' on 'Bowtie' has no materials to replace. Skipping."));
								}
								continue;
							}
							if (Plugin.EnableDebugLogging.Value)
							{
								Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Applying skin materials to renderer '{1}' on 'Bowtie'. Original material count: {2}, New material count: {3}", "[EnemySkinMod] ", ((Object)((Component)val5).gameObject).name, val5.sharedMaterials.Length, skinData.Materials.Length));
							}
							val5.materials = skinData.Materials;
							flag3 = true;
						}
						break;
					}
					default:
						if (Plugin.EnableDebugLogging.Value)
						{
							Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Found {1} renderers on '{2}' ({3}). Applying shared materials, skipping particle systems if named appropriately.", "[EnemySkinMod] ", componentsInChildren2.Length, ((Object)enemyInstance).name, text));
						}
						if (skinData.Materials != null && skinData.Materials.Length != 0)
						{
							Renderer[] array2 = componentsInChildren2;
							foreach (Renderer val3 in array2)
							{
								bool flag4 = false;
								string text2 = ((Object)((Component)val3).gameObject).name.ToLower();
								if (text2.Contains("particle") || text2.Contains("effect") || text2.Contains("vfx"))
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + ((Object)((Component)val3).gameObject).name + "' on '" + text + "' due to name containing particle/effect keywords."));
									}
									flag4 = true;
								}
								if (!flag4 && val3.sharedMaterials != null)
								{
									Material[] sharedMaterials = val3.sharedMaterials;
									foreach (Material val4 in sharedMaterials)
									{
										if (!((Object)(object)val4 != (Object)null) || !((Object)(object)val4.shader != (Object)null))
										{
											continue;
										}
										string name = ((Object)val4.shader).name;
										if (name == "Particles/Standard Unlit" || name == "Particles/Standard Surface")
										{
											if (Plugin.EnableDebugLogging.Value)
											{
												Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skipping renderer '" + ((Object)((Component)val3).gameObject).name + "' on '" + text + "' due to material '" + ((Object)val4).name + "' using shader '" + name + "'."));
											}
											flag4 = true;
											break;
										}
									}
								}
								if (flag4)
								{
									continue;
								}
								if (val3.sharedMaterials == null || val3.sharedMaterials.Length == 0)
								{
									if (Plugin.EnableDebugLogging.Value)
									{
										Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Renderer '" + ((Object)((Component)val3).gameObject).name + "' on '" + text + "' has no materials to replace. Skipping."));
									}
									continue;
								}
								if (Plugin.EnableDebugLogging.Value)
								{
									Plugin.Log.LogDebug((object)string.Format("{0}ApplySkinLogic: Applying skin materials to renderer '{1}' on '{2}'. Original material count: {3}, New material count: {4}", "[EnemySkinMod] ", ((Object)((Component)val3).gameObject).name, text, val3.sharedMaterials.Length, skinData.Materials.Length));
								}
								val3.materials = skinData.Materials;
								flag3 = true;
							}
						}
						else
						{
							Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: SkinData for '" + skinData.Identifier + "' on '" + text + "' has no materials. No general materials applied."));
						}
						break;
					}
				}
				if (flag3)
				{
					Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic: Finished applying shared materials for '" + skinData.Identifier + "' on '" + ((Object)enemyInstance).name + "'."));
				}
				else
				{
					Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: Finished applying shared materials for '" + skinData.Identifier + "' on '" + ((Object)enemyInstance).name + "', but no materials were actually applied (e.g., specific renderers not found for 'Head', or no renderers for others, or no materials in skinData)."));
				}
			}
			else if (Plugin.EnableDebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skin '" + skinData.Identifier + "' has no shared materials defined."));
			}
			if ((Object)(object)skinData.SharedMesh != (Object)null)
			{
				Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic: Skin '" + skinData.Identifier + "' has a shared mesh defined. Attempting to apply."));
				MeshFilter componentInChildren = enemyInstance.GetComponentInChildren<MeshFilter>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					componentInChildren.mesh = skinData.SharedMesh;
					Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic: Successfully applied shared mesh '" + ((Object)skinData.SharedMesh).name + "' to MeshFilter on '" + ((Object)((Component)componentInChildren).gameObject).name + "'."));
				}
				else
				{
					Plugin.Log.LogWarning((object)("[EnemySkinMod] ApplySkinLogic: No MeshFilter found on '" + ((Object)enemyInstance).name + "' or its children to apply shared mesh."));
				}
			}
			else if (Plugin.EnableDebugLogging.Value)
			{
				Plugin.Log.LogDebug((object)("[EnemySkinMod] ApplySkinLogic: Skin '" + skinData.Identifier + "' has no shared mesh defined."));
			}
			Plugin.Log.LogInfo((object)("[EnemySkinMod] ApplySkinLogic completed for '" + ((Object)enemyInstance).name + "' with skin '" + skinData.Identifier + "'. Check logs for details."));
		}

		[HarmonyPatch(typeof(EnemyDirector), "Awake")]
		[HarmonyPostfix]
		private static void EnemyDirector_Awake_Patch(EnemyDirector __instance)
		{
			Plugin.Log.LogInfo((object)$"--- EnemyDirector_Awake_Patch executing for instance {((Object)__instance).GetInstanceID()} ---");
			string name = ((Object)((Component)__instance).gameObject).name;
			string text = name.Replace("(Clone)", "").Trim();
			string text2 = text;
			if (text.StartsWith("Enemy - "))
			{
				text2 = text.Substring("Enemy - ".Length).Trim();
			}
			if (string.IsNullOrEmpty(text2))
			{
				Plugin.Log.LogWarning((object)("  Could not get valid Enemy ID from GameObject name: '" + name + "'. Cannot apply skin."));
				return;
			}
			Plugin.Log.LogInfo((object)("  Enemy ID derived from GameObject name: '" + text2 + "' (Original: '" + name + "')"));
			if (!EnemySkinManager.RegisteredSkins.ContainsKey(text2) || EnemySkinManager.RegisteredSkins[text2].Count == 0)
			{
				Plugin.Log.LogInfo((object)("  No skins registered for enemy ID '" + text2 + "'. Skipping."));
				return;
			}
			SkinData randomSkinForEnemy = EnemySkinManager.GetRandomSkinForEnemy(text2);
			if (randomSkinForEnemy.Identifier == null)
			{
				Plugin.Log.LogInfo((object)("  GetRandomSkinForEnemy returned no skin (likely all disabled or only Vanilla exists and was chosen). Enemy ID: '" + text2 + "'"));
				return;
			}
			if (randomSkinForEnemy.Identifier == "Vanilla")
			{
				Plugin.Log.LogInfo((object)("  Selected skin is 'Vanilla'. No custom skin applied for enemy ID: '" + text2 + "'."));
				return;
			}
			Plugin.Log.LogInfo((object)("  Selected skin '" + randomSkinForEnemy.Identifier + "' for enemy ID '" + text2 + "'. Applying..."));
			GameObject gameObject = ((Component)__instance).gameObject;
			ApplySkinLogic(gameObject, randomSkinForEnemy);
		}
	}
}