Decompiled source of CharmaKameleon v1.0.0

CharmaKameleon/CharmaKameleon.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("CharmaKameleon")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CharmaKameleon")]
[assembly: AssemblyTitle("CharmaKameleon")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 CharmaKameleon
{
	internal static class BreedingConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> FedDuration;

		public static ConfigEntry<int> MaxCreatures;

		public static ConfigEntry<float> TotalCheckRange;

		public static ConfigEntry<float> PartnerCheckRange;

		public static ConfigEntry<float> PregnancyChance;

		public static ConfigEntry<int> RequiredLovePoints;

		public static ConfigEntry<float> PregnancyDuration;

		public static ConfigEntry<float> UpdateInterval;

		public static ConfigEntry<bool> Debug;
	}
	public static class Breeding
	{
		public static string TargetPrefabName;

		public static void ApplyOverrides(GameObject neck, Tameable tameable, Procreation procreation)
		{
			if (BreedingConfig.Enabled != null && BreedingConfig.Enabled.Value)
			{
				tameable.m_fedDuration = BreedingConfig.FedDuration.Value;
				procreation.m_updateInterval = BreedingConfig.UpdateInterval.Value;
				procreation.m_totalCheckRange = BreedingConfig.TotalCheckRange.Value;
				procreation.m_maxCreatures = BreedingConfig.MaxCreatures.Value;
				procreation.m_partnerCheckRange = BreedingConfig.PartnerCheckRange.Value;
				procreation.m_pregnancyChance = BreedingConfig.PregnancyChance.Value;
				procreation.m_pregnancyDuration = BreedingConfig.PregnancyDuration.Value;
				procreation.m_requiredLovePoints = BreedingConfig.RequiredLovePoints.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Procreation), "Procreate")]
	internal static class Procreate_DebugPatch
	{
		private static readonly Dictionary<int, float> LastLog = new Dictionary<int, float>();

		private const float ThrottleSeconds = 20f;

		private static void Postfix(Procreation __instance)
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			if (BreedingConfig.Debug == null || !BreedingConfig.Debug.Value)
			{
				return;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return;
			}
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(component.GetZDO().GetPrefab()) : null);
			string text = (((Object)(object)val != (Object)null) ? ((Object)val).name : null);
			if (string.IsNullOrEmpty(Breeding.TargetPrefabName) || text != Breeding.TargetPrefabName)
			{
				return;
			}
			Tameable component2 = ((Component)__instance).GetComponent<Tameable>();
			if (!((Object)(object)component2 == (Object)null) && component2.IsTamed())
			{
				float time = Time.time;
				if (!LastLog.TryGetValue(((Object)__instance).GetInstanceID(), out var value) || !(time - value < 20f))
				{
					LastLog[((Object)__instance).GetInstanceID()] = time;
					BaseAI component3 = ((Component)__instance).GetComponent<BaseAI>();
					Vector3 position = ((Component)__instance).transform.position;
					long num = component.GetZDO().GetLong(ZDOVars.s_pregnant, 0L);
					bool flag = num != 0;
					bool flag2 = flag && (ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds > (double)__instance.m_pregnancyDuration;
					int num2 = (((Object)(object)val != (Object)null) ? SpawnSystem.GetNrOfInstances(val, position, __instance.m_totalCheckRange, false, false) : (-1));
					int num3 = (((Object)(object)val != (Object)null) ? SpawnSystem.GetNrOfInstances(val, position, __instance.m_partnerCheckRange, false, true) : (-1));
					Debug.Log((object)$"[CharmaKameleon] [Breeding] {text}: owner={component.IsOwner()} tamed={true} hungry={component2.IsHungry()} alert={(Object)(object)component3 != (Object)null && component3.IsAlerted()} love={__instance.GetLovePoints()}/{__instance.m_requiredLovePoints} pregnant={flag} due={flag2} pair={num3} total={num2}/{__instance.m_maxCreatures} (tcr={__instance.m_totalCheckRange} pcr={__instance.m_partnerCheckRange})");
				}
			}
		}
	}
	[DisallowMultipleComponent]
	public class NeckBabySizer : MonoBehaviour
	{
		public static ConfigEntry<float> BabyScale;

		public static ConfigEntry<float> GrowTime;

		public static ConfigEntry<bool> DebugLog;

		private const string KeyBabyStart = "neck_baby_start";

		private bool _decided;

		private Vector3 _babyScale = Vector3.one;

		private void Awake()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			_babyScale = Vector3.one * Mathf.Max(0.05f, BabyScale?.Value ?? 0.3f);
		}

		private void Update()
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			if (!_decided)
			{
				Decide();
			}
			ZNetView component = ((Component)this).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return;
			}
			long num = component.GetZDO().GetLong("neck_baby_start", 0L);
			if (num == 0L)
			{
				return;
			}
			float num2 = Mathf.Max(1f, GrowTime?.Value ?? 1800f);
			float num3 = Mathf.Clamp01((float)((ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds / (double)num2));
			Vector3 val = ((num3 <= 0f) ? _babyScale : Vector3.Lerp(_babyScale, Vector3.one, num3));
			if (((Component)this).transform.localScale != val)
			{
				((Component)this).transform.localScale = val;
			}
			if (num3 >= 1f)
			{
				if (DebugLog != null && DebugLog.Value)
				{
					Debug.Log((object)("[CharmaKameleon] [BabySizer] " + ((Object)((Component)this).gameObject).name + ": finished growing to full size."));
				}
				Object.Destroy((Object)(object)this);
			}
		}

		private void Decide()
		{
			Tameable component = ((Component)this).GetComponent<Tameable>();
			BaseAI component2 = ((Component)this).GetComponent<BaseAI>();
			ZNetView component3 = ((Component)this).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || !component3.IsValid())
			{
				return;
			}
			TimeSpan timeSinceSpawned = component2.GetTimeSinceSpawned();
			bool flag = component.IsTamed();
			if (component3.GetZDO().GetLong("neck_baby_start", 0L) != 0L)
			{
				_decided = true;
				if (DebugLog != null && DebugLog.Value)
				{
					Debug.Log((object)("[CharmaKameleon] [BabySizer] " + ((Object)((Component)this).gameObject).name + ": born earlier (zdo key set), resuming growth from started ticks."));
				}
			}
			else if (flag && timeSinceSpawned.TotalSeconds <= 30.0)
			{
				if (component3.IsOwner())
				{
					component3.GetZDO().Set("neck_baby_start", ZNet.instance.GetTime().Ticks);
				}
				_decided = true;
				if (DebugLog != null && DebugLog.Value)
				{
					Debug.Log((object)$"[CharmaKameleon] [BabySizer] {((Object)((Component)this).gameObject).name}: newborn detected (age={timeSinceSpawned.TotalSeconds:F1}s, tamed=true), shrinking to baby size.");
				}
			}
			else if (timeSinceSpawned.TotalSeconds > 30.0)
			{
				if (DebugLog != null && DebugLog.Value)
				{
					Debug.Log((object)$"[CharmaKameleon] [BabySizer] {((Object)((Component)this).gameObject).name}: adult neck (age={timeSinceSpawned.TotalSeconds:F1}s, tamed={flag}), leaving full size.");
				}
				Object.Destroy((Object)(object)this);
			}
		}
	}
	internal static class NeckColorConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> EmberChance;

		public static ConfigEntry<float> AshChance;

		public static ConfigEntry<float> CopperChance;

		public static ConfigEntry<string> ForceVariant;

		public static ConfigEntry<string> TintMode;

		public static ConfigEntry<bool> TintEyes;

		public static ConfigEntry<bool> ResetColors;

		public static ConfigEntry<bool> DebugLog;
	}
	[DisallowMultipleComponent]
	public class NeckColorizer : MonoBehaviour
	{
		private struct Variant
		{
			public string Name;

			public Func<ConfigEntry<float>> Chance;

			public Color Target;
		}

		private const string KeyBabyStart = "neck_baby_start";

		private const string KeyVariant = "neck_variant";

		private const int NaturalCode = 4;

		private static readonly Variant[] Variants = new Variant[3]
		{
			new Variant
			{
				Name = "Ember",
				Chance = () => NeckColorConfig.EmberChance,
				Target = new Color(0.78f, 0.32f, 0.1f)
			},
			new Variant
			{
				Name = "Ash",
				Chance = () => NeckColorConfig.AshChance,
				Target = new Color(0.4f, 0.38f, 0.42f)
			},
			new Variant
			{
				Name = "Copper",
				Chance = () => NeckColorConfig.CopperChance,
				Target = new Color(0.72f, 0.42f, 0.2f)
			}
		};

		private static readonly string[] NonFurHints = new string[8] { "eye", "pupil", "claw", "tooth", "teeth", "nail", "horn", "tongue" };

		private static readonly Dictionary<long, Texture2D> TintCache = new Dictionary<long, Texture2D>();

		private bool _handled;

		private const int MaxWaitFrames = 120;

		private int _waitedFrames;

		private void Update()
		{
			if (!_handled)
			{
				TryColorize();
			}
		}

		private void TryColorize()
		{
			if (NeckColorConfig.Enabled == null || !NeckColorConfig.Enabled.Value)
			{
				Finish();
				return;
			}
			ZNetView component = ((Component)this).GetComponent<ZNetView>();
			Tameable component2 = ((Component)this).GetComponent<Tameable>();
			BaseAI component3 = ((Component)this).GetComponent<BaseAI>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || !component.IsValid())
			{
				return;
			}
			if (!component2.IsTamed())
			{
				if (_waitedFrames++ >= 120)
				{
					Finish();
				}
				return;
			}
			ZDO zDO = component.GetZDO();
			long num = zDO.GetLong("neck_baby_start", 0L);
			if (num == 0L)
			{
				if (_waitedFrames++ >= 120)
				{
					Log("no neck_baby_start on this neck; treating it as natural (never born as a cub).");
					Finish();
				}
				return;
			}
			double num2 = ((NeckBabySizer.GrowTime != null) ? NeckBabySizer.GrowTime.Value : 1800f);
			if (num2 < 1.0)
			{
				num2 = 1.0;
			}
			bool flag = (ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds < num2;
			int num3 = 0;
			if (NeckColorConfig.ResetColors == null || !NeckColorConfig.ResetColors.Value)
			{
				num3 = zDO.GetInt("neck_variant", 0);
				if (num3 < 0 || num3 > 4)
				{
					Log($"stored variant {num3} is out of range; discarding and re-rolling.");
					num3 = 0;
				}
			}
			Variant? variant = null;
			if (num3 != 4)
			{
				if (num3 > 0)
				{
					variant = Variants[num3 - 1];
				}
				else
				{
					if (!flag)
					{
						Finish();
						return;
					}
					int num4 = RollVariant();
					int num5;
					if (num4 >= 0)
					{
						variant = Variants[num4];
						num5 = num4 + 1;
					}
					else
					{
						num5 = 4;
					}
					zDO.Set("neck_variant", num5);
				}
			}
			string text = ((NeckColorConfig.ForceVariant != null) ? (NeckColorConfig.ForceVariant.Value ?? string.Empty).Trim() : string.Empty);
			if (text.Length > 0)
			{
				bool flag2 = false;
				for (int i = 0; i < Variants.Length; i++)
				{
					if (string.Equals(Variants[i].Name, text, StringComparison.OrdinalIgnoreCase))
					{
						variant = Variants[i];
						flag2 = true;
						break;
					}
				}
				if (!flag2)
				{
					Log("ForceVariant '" + text + "' is not one of Ember/Ash/Copper; rolling normally.");
				}
			}
			if (variant.HasValue)
			{
				ApplyColor(variant.Value);
			}
			Finish();
		}

		private void Finish()
		{
			_handled = true;
			Object.Destroy((Object)(object)this);
		}

		private double Roll()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			ZNetView component = ((Component)this).GetComponent<ZNetView>();
			ZDO val = (((Object)(object)component != (Object)null) ? component.GetZDO() : null);
			if (val == null)
			{
				return -1.0;
			}
			ZDOID uid = val.m_uid;
			ulong userID = (ulong)((ZDOID)(ref uid)).UserID;
			ulong num = ((ZDOID)(ref uid)).ID;
			return (double)(Mix(Mix(Mix(Mix((uint)(-1640531527 ^ (int)userID)) ^ (uint)(int)(userID >> 32)) ^ (uint)(int)num) ^ (uint)(int)(num >> 32)) & 0xFFFFFF) / 16777216.0;
		}

		private static uint Mix(uint x)
		{
			x ^= x >> 16;
			x *= 2146121005;
			x ^= x >> 15;
			x *= 2221713035u;
			x ^= x >> 16;
			return x;
		}

		private int RollVariant()
		{
			double num = Roll();
			if (num < 0.0)
			{
				return -1;
			}
			double num2 = 0.0;
			for (int i = 0; i < Variants.Length; i++)
			{
				ConfigEntry<float> val = Variants[i].Chance();
				if (val != null)
				{
					num2 += Math.Max(0.0, val.Value);
					if (num < num2)
					{
						Log($"roll={num:F6} cumulative={num2:F4} -> {Variants[i].Name}");
						return i;
					}
				}
			}
			Log($"roll={num:F6} above all bands ({num2:F4}) -> natural colour");
			return -1;
		}

		private void ApplyColor(Variant variant)
		{
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<Renderer>(true);
			bool flag = NeckColorConfig.TintEyes == null || NeckColorConfig.TintEyes.Value;
			string a = ((NeckColorConfig.TintMode != null) ? (NeckColorConfig.TintMode.Value ?? "Texture") : "Texture");
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			Renderer[] array = componentsInChildren;
			foreach (Renderer val in array)
			{
				Material[] materials = val.materials;
				bool flag2 = false;
				foreach (Material val2 in materials)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						if (!flag && IsNonFur(val2))
						{
							num3++;
						}
						else if (string.Equals(a, "Flat", StringComparison.OrdinalIgnoreCase))
						{
							val2.color = variant.Target;
							num2++;
							flag2 = true;
						}
						else if (RemapTexture(val2, variant.Target))
						{
							num++;
							flag2 = true;
						}
						else
						{
							num4++;
							Log("could not remap texture on material '" + ((Object)val2).name + "'; falling back to a flat tint, which can only darken toward Ash.");
							val2.color = variant.Target;
							num2++;
							flag2 = true;
						}
					}
				}
				if (flag2)
				{
					val.materials = materials;
				}
			}
			Log($"{variant.Name} cub born! remapped={num} flat={num2} skipped={num3} failed={num4} renderers={componentsInChildren.Length}");
		}

		private static bool IsNonFur(Material material)
		{
			string name = ((Object)material).name;
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			name = name.ToLowerInvariant();
			for (int i = 0; i < NonFurHints.Length; i++)
			{
				if (name.Contains(NonFurHints[i]))
				{
					return true;
				}
			}
			return false;
		}

		private static bool RemapTexture(Material material, Color target)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Expected O, but got Unknown
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			Texture mainTexture = material.mainTexture;
			if ((Object)(object)mainTexture == (Object)null || mainTexture.width <= 0 || mainTexture.height <= 0)
			{
				return false;
			}
			RenderTexture val = null;
			RenderTexture active = RenderTexture.active;
			Texture2D val2 = null;
			long key = (long)(((ulong)(uint)((Object)mainTexture).GetInstanceID() << 24) | ((ulong)(uint)Mathf.RoundToInt(target.r * 255f) << 16) | ((ulong)(uint)Mathf.RoundToInt(target.g * 255f) << 8) | (uint)Mathf.RoundToInt(target.b * 255f));
			if (TintCache.TryGetValue(key, out var value) && (Object)(object)value != (Object)null)
			{
				material.mainTexture = (Texture)(object)value;
				material.color = Color.white;
				return true;
			}
			try
			{
				val = RenderTexture.GetTemporary(mainTexture.width, mainTexture.height, 0, (RenderTextureFormat)0);
				Graphics.Blit(mainTexture, val);
				RenderTexture.active = val;
				val2 = new Texture2D(mainTexture.width, mainTexture.height, (TextureFormat)4, false);
				val2.ReadPixels(new Rect(0f, 0f, (float)mainTexture.width, (float)mainTexture.height), 0, 0);
				val2.Apply(false);
				RenderTexture.active = active;
				Color32[] pixels = val2.GetPixels32();
				float r = target.r;
				float g = target.g;
				float b = target.b;
				for (int i = 0; i < pixels.Length; i++)
				{
					Color32 val3 = pixels[i];
					float num = (0.299f * (float)(int)val3.r + 0.587f * (float)(int)val3.g + 0.114f * (float)(int)val3.b) / 255f;
					float num2 = 0.45f + 0.55f * num;
					pixels[i] = new Color32((byte)Math.Min(255f, r * 255f * num2), (byte)Math.Min(255f, g * 255f * num2), (byte)Math.Min(255f, b * 255f * num2), val3.a);
				}
				Texture2D val4 = new Texture2D(mainTexture.width, mainTexture.height, (TextureFormat)4, false);
				val4.SetPixels32(pixels);
				val4.Apply(true);
				((Object)val4).name = ((Object)mainTexture).name + "_heybooboo_tint";
				TintCache[key] = val4;
				material.mainTexture = (Texture)(object)val4;
				material.color = Color.white;
				return true;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[CharmaKameleon] [Colors] texture remap failed on '" + ((Object)material).name + "': " + ex.Message));
				return false;
			}
			finally
			{
				RenderTexture.active = active;
				if ((Object)(object)val != (Object)null)
				{
					RenderTexture.ReleaseTemporary(val);
				}
				if ((Object)(object)val2 != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)val2);
				}
			}
		}

		private static void Log(string message)
		{
			if (NeckColorConfig.DebugLog != null && NeckColorConfig.DebugLog.Value)
			{
				Debug.Log((object)("[CharmaKameleon] [Colors] " + message));
			}
		}
	}
	internal static class NeckConfig
	{
		public static ConfigEntry<string> TargetPrefab;
	}
	[BepInPlugin("wayfarers.charma_kameleon", "CharmaKameleon", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "wayfarers.charma_kameleon";

		public const string Name = "CharmaKameleon";

		public const string Version = "1.0.0";

		private void Awake()
		{
			NeckConfig.TargetPrefab = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TargetPrefab", "Neck", "Prefab name of the creature to tame. Only change this if necks are not being tamed at all.");
			NeckBabySizer.BabyScale = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "BabyScale", 0.3f, "Spawn scale of newborn (bred) necks. 1.0 = adult size.");
			NeckBabySizer.GrowTime = ((BaseUnityPlugin)this).Config.Bind<float>("Growth", "GrowTime", 1800f, "Seconds for a baby neck to grow to full size. 1800 = one Valheim day.");
			NeckBabySizer.DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Growth", "DebugLog", false, "Log baby-size decisions (newborn detected / finished growing).");
			BreedingConfig.Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Breeding", "Enabled", true, "Enable neck breeding.");
			BreedingConfig.FedDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "FedDuration", 7200f, "Seconds a neck stays 'fed' after eating (breeding is blocked while hungry).");
			BreedingConfig.MaxCreatures = ((BaseUnityPlugin)this).Config.Bind<int>("Breeding", "MaxCreatures", 30, "Max necks (tamed+wild, incl. cubs) allowed within TotalCheckRange before breeding stops.");
			BreedingConfig.TotalCheckRange = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "TotalCheckRange", 20f, "Radius used for the total-neck count.");
			BreedingConfig.PartnerCheckRange = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "PartnerCheckRange", 30f, "Radius a second tamed neck must be within for a pair.");
			BreedingConfig.PregnancyChance = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "PregnancyChance", 0.25f, "Chance per tick to gain a love point (0..1).");
			BreedingConfig.RequiredLovePoints = ((BaseUnityPlugin)this).Config.Bind<int>("Breeding", "RequiredLovePoints", 5, "Love points needed before the neck gets pregnant.");
			BreedingConfig.PregnancyDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "PregnancyDuration", 1800f, "Seconds a neck stays pregnant before giving birth. 1800 = one Valheim day.");
			BreedingConfig.UpdateInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Breeding", "UpdateInterval", 2f, "Seconds between breeding checks.");
			BreedingConfig.Debug = ((BaseUnityPlugin)this).Config.Bind<bool>("Breeding", "DebugLog", false, "Log one breeding status line per tamed neck every ~20 seconds.");
			NeckColorConfig.Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Neck Colors", "Enabled", true, "Cubs born to tamed necks can inherit a rare colour variant: Ember, Ash & Copper.");
			NeckColorConfig.EmberChance = ((BaseUnityPlugin)this).Config.Bind<float>("Neck Colors", "EmberChance", 0.05f, "Chance a newborn cub is Ember (burnt orange), 0..1. 0.05 = 5%.");
			NeckColorConfig.AshChance = ((BaseUnityPlugin)this).Config.Bind<float>("Neck Colors", "AshChance", 0.05f, "Chance a newborn cub is Ash (cool grey), 0..1. 0.05 = 5%.");
			NeckColorConfig.CopperChance = ((BaseUnityPlugin)this).Config.Bind<float>("Neck Colors", "CopperChance", 0.05f, "Chance a newborn cub is Copper (warm bronze), 0..1. 0.05 = 5%.");
			NeckColorConfig.ForceVariant = ((BaseUnityPlugin)this).Config.Bind<string>("Neck Colors", "ForceVariant", "", "Test only: force every newborn cub to Ember, Ash or Copper. Leave empty to roll normally.");
			NeckColorConfig.TintMode = ((BaseUnityPlugin)this).Config.Bind<string>("Neck Colors", "TintMode", "Texture", "Texture = recolour the hide texture, the only mode that reproduces the true Ember hues. Flat = multiply the material colour, which can only darken toward Ash.");
			NeckColorConfig.TintEyes = ((BaseUnityPlugin)this).Config.Bind<bool>("Neck Colors", "TintEyes", false, "Also tint eyes, claws and teeth. Off by default so they keep their natural colour.");
			NeckColorConfig.ResetColors = ((BaseUnityPlugin)this).Config.Bind<bool>("Neck Colors", "ResetColors", false, "Forget every cub's stored colour decision and re-roll from the chances below. Turn this on, load the world, then turn it back off.");
			NeckColorConfig.DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Neck Colors", "DebugLog", false, "Log the roll result and which materials were recoloured.");
			Harmony.CreateAndPatchAll(typeof(Plugin).Assembly, (string)null);
			PrefabManager.OnVanillaPrefabsAvailable += MakeNecksTameable;
		}

		private void OnDestroy()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= MakeNecksTameable;
		}

		private static string DescribePrefabsMatching(string fragment)
		{
			try
			{
				Dictionary<string, Object> prefabs = Cache.GetPrefabs(typeof(GameObject));
				List<string> list = new List<string>();
				if (prefabs != null)
				{
					foreach (KeyValuePair<string, Object> item in prefabs)
					{
						if (!string.IsNullOrEmpty(item.Key) && item.Key.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0)
						{
							list.Add(item.Key);
						}
					}
				}
				list.Sort(StringComparer.OrdinalIgnoreCase);
				return (list.Count > 0) ? string.Join(", ", list.ToArray()) : "(none found)";
			}
			catch (Exception ex)
			{
				return "(lookup failed: " + ex.Message + ")";
			}
		}

		private void MakeNecksTameable()
		{
			GameObject prefab = Cache.GetPrefab<GameObject>("Wolf");
			GameObject val = null;
			string text = null;
			List<string> list = new List<string>();
			string text2 = ((NeckConfig.TargetPrefab != null) ? NeckConfig.TargetPrefab.Value : null);
			if (!string.IsNullOrWhiteSpace(text2))
			{
				list.Add(text2.Trim());
			}
			string[] array = new string[2] { "Neck", "NeckGround" };
			foreach (string item in array)
			{
				if (!list.Contains(item))
				{
					list.Add(item);
				}
			}
			foreach (string item2 in list)
			{
				GameObject prefab2 = Cache.GetPrefab<GameObject>(item2);
				if ((Object)(object)prefab2 != (Object)null && (Object)(object)prefab2.GetComponent<MonsterAI>() != (Object)null)
				{
					val = prefab2;
					text = item2;
					break;
				}
			}
			if ((Object)(object)prefab == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Wolf prefab missing; cannot copy taming or diet settings.");
				return;
			}
			if ((Object)(object)val == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("No tameable neck prefab found. Tried: " + string.Join(", ", list.ToArray()) + "."));
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Vanilla prefabs containing 'neck': " + DescribePrefabsMatching("neck")));
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Set TargetPrefab in the CharmaKameleon config to the correct prefab name.");
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Patching neck prefab '" + text + "'."));
			Tameable component = prefab.GetComponent<Tameable>();
			MonsterAI component2 = prefab.GetComponent<MonsterAI>();
			MonsterAI component3 = val.GetComponent<MonsterAI>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"Missing components (wolfTameable={(Object)(object)component != (Object)null} wolfAI={(Object)(object)component2 != (Object)null} neckAI={(Object)(object)component3 != (Object)null})");
				return;
			}
			component3.m_consumeItems = new List<ItemDrop>(component2.m_consumeItems);
			component3.m_consumeRange = component2.m_consumeRange;
			component3.m_consumeSearchRange = component2.m_consumeSearchRange;
			component3.m_consumeSearchInterval = component2.m_consumeSearchInterval;
			if ((Object)(object)val.GetComponent<Tameable>() != (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Neck already has a Tameable component; nothing to do.");
				return;
			}
			Tameable val2 = val.AddComponent<Tameable>();
			val2.m_tamingTime = component.m_tamingTime;
			val2.m_fedDuration = component.m_fedDuration;
			val2.m_commandable = component.m_commandable;
			val2.m_tameText = component.m_tameText;
			val2.m_nameBeforeText = component.m_nameBeforeText;
			val2.m_tamingSpeedMultiplierRange = component.m_tamingSpeedMultiplierRange;
			val2.m_tamingBoostMultiplier = component.m_tamingBoostMultiplier;
			val2.m_tamedEffect = component.m_tamedEffect;
			val2.m_sootheEffect = component.m_sootheEffect;
			val2.m_petEffect = component.m_petEffect;
			val2.m_randomStartingName = new List<string>(component.m_randomStartingName);
			Procreation component4 = prefab.GetComponent<Procreation>();
			Procreation component5 = val.GetComponent<Procreation>();
			if ((Object)(object)component4 != (Object)null && (Object)(object)component5 == (Object)null)
			{
				component5 = val.AddComponent<Procreation>();
				component5.m_updateInterval = component4.m_updateInterval;
				component5.m_totalCheckRange = component4.m_totalCheckRange;
				component5.m_maxCreatures = component4.m_maxCreatures;
				component5.m_partnerCheckRange = component4.m_partnerCheckRange;
				component5.m_pregnancyChance = component4.m_pregnancyChance;
				component5.m_pregnancyDuration = component4.m_pregnancyDuration;
				component5.m_requiredLovePoints = component4.m_requiredLovePoints;
				component5.m_offspring = val;
				component5.m_minOffspringLevel = component4.m_minOffspringLevel;
				component5.m_spawnOffset = component4.m_spawnOffset;
				component5.m_spawnOffsetMax = component4.m_spawnOffsetMax;
				component5.m_spawnRandomDirection = component4.m_spawnRandomDirection;
				component5.m_birthEffects = component4.m_birthEffects;
				component5.m_loveEffects = component4.m_loveEffects;
				Breeding.TargetPrefabName = text;
				Breeding.ApplyOverrides(val, val2, component5);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Necks can now breed (pairs of fed, tamed necks produce cubs).");
			}
			if ((Object)(object)val.GetComponent<NeckBabySizer>() == (Object)null)
			{
				val.AddComponent<NeckBabySizer>();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Baby necks spawn small and grow to full size (" + text + ")."));
			}
			if ((Object)(object)val.GetComponent<NeckColorizer>() == (Object)null)
			{
				val.AddComponent<NeckColorizer>();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Rare colour variants can appear on cubs (Ember, Ash & Copper).");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Necks are now tameable! Feed them raw meat, drop it near them, and wait (like wolves).");
		}
	}
}