Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CharmaKameleon v1.0.0
CharmaKameleon/CharmaKameleon.dll
Decompiled 2 days agousing 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)."); } } }