Decompiled source of POV LMTY BeastAI Adapter v0.3.3

plugins/POVLMTYBeastAIAdapter/POVLMTYBeastAIAdapter.dll

Decompiled 3 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.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("POVLMTYBeastAIAdapter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("POVLMTYBeastAIAdapter")]
[assembly: AssemblyTitle("POVLMTYBeastAIAdapter")]
[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 POVLMTYBeastAIAdapter
{
	[BepInPlugin("com.rehyun.povlmtybeastaiadapter", "POV LMTY BeastAI Adapter", "0.3.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.rehyun.povlmtybeastaiadapter";

		public const string PluginName = "POV LMTY BeastAI Adapter";

		public const string PluginVersion = "0.3.3";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("com.rehyun.povlmtybeastaiadapter");
			MethodBase methodBase = AdapterPatch.TargetMethod();
			if (methodBase == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Could not find LetMeTameYou.TameManager.AddTame. Adapter was not installed.");
				return;
			}
			_harmony.Patch(methodBase, new HarmonyMethod(typeof(AdapterPatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"POV LMTY BeastAI Adapter 0.3.3 loaded. Generic compatibility + adaptive taming fallback enabled; no prefab allowlist.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class AdapterPatch
	{
		private static readonly BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		internal static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("LetMeTameYou.TameManager");
			if (type == null)
			{
				return null;
			}
			MethodInfo[] methods = type.GetMethods(AnyStatic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != "AddTame"))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length == 2 && parameters[0].ParameterType == typeof(GameObject))
					{
						return methodInfo;
					}
				}
			}
			return null;
		}

		public static bool Prefix(object[] __args)
		{
			try
			{
				if (__args == null || __args.Length < 2)
				{
					return true;
				}
				object obj = __args[0];
				GameObject val = (GameObject)((obj is GameObject) ? obj : null);
				object obj2 = __args[1];
				if ((Object)(object)val == (Object)null || obj2 == null)
				{
					return true;
				}
				string text = CleanPrefabName(((Object)val).name);
				Plugin.Log.LogInfo((object)("[LMTY Trace] BEGIN prefab=" + text + "; AI=" + GetBaseAIType(val) + "; " + $"MonsterAI={(Object)(object)val.GetComponent<MonsterAI>() != (Object)null}; AnimalAI={(Object)(object)val.GetComponent<AnimalAI>() != (Object)null}; " + $"Character={(Object)(object)val.GetComponent<Character>() != (Object)null}; Humanoid={(Object)(object)val.GetComponent<Humanoid>() != (Object)null}; " + $"Tameable={(Object)(object)val.GetComponent<Tameable>() != (Object)null}; Procreation={(Object)(object)val.GetComponent<Procreation>() != (Object)null}"));
				MonsterAI component = val.GetComponent<MonsterAI>();
				Character component2 = val.GetComponent<Character>();
				Humanoid component3 = val.GetComponent<Humanoid>();
				AnimalAI component4 = val.GetComponent<AnimalAI>();
				Tameable component5 = val.GetComponent<Tameable>();
				Procreation component6 = val.GetComponent<Procreation>();
				string baseAIType = GetBaseAIType(val);
				if (!((Object)(object)component != (Object)null) || !((Object)(object)component2 != (Object)null) || !((Object)(object)component3 == (Object)null) || !((Object)(object)component4 == (Object)null))
				{
					return true;
				}
				if (!IsHighConfidencePovAI(component, out var evidence))
				{
					Plugin.Log.LogWarning((object)("[Generic Compatibility] Suspicious LMTY target detected but auto-repair was skipped: prefab=" + text + "; AI=" + baseAIType + "; reason=unknown/non-POV AI. This prevents the adapter from modifying unrelated custom-AI mods."));
					return true;
				}
				Plugin.Log.LogWarning((object)("[Generic Compatibility] AUTO-REPAIR triggered: prefab=" + text + "; AI=" + baseAIType + "; shape=MonsterAI+Character/no-Humanoid/no-AnimalAI; POV=" + evidence));
				if (HasSpecialEggConfig(obj2, out var description))
				{
					Plugin.Log.LogWarning((object)("[Generic Compatibility] prefab=" + text + " has special egg config (" + description + "). v0.3.x will not auto-repair this special reproduction path; original LMTY will handle it."));
					return true;
				}
				if (GetBool(obj2, "removeTameable", fallback: false))
				{
					return true;
				}
				ConfigureCharacter(val, component2, obj2);
				component5 = ConfigureTameable(val, component5, obj2);
				ConfigureFood(component, obj2);
				if (GetBool(obj2, "procreation", fallback: true))
				{
					component6 = ConfigureProcreation(val, component6, obj2);
					ConfigureOffspring(val, component6, obj2);
				}
				else if ((Object)(object)component6 != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)component6);
					Plugin.Log.LogInfo((object)"[BeastAI Adapter] procreation=false; removed Procreation.");
				}
				Procreation component7 = val.GetComponent<Procreation>();
				Plugin.Log.LogInfo((object)("[Generic Compatibility] SUCCESS: LMTY creature settings applied without replacing POV " + baseAIType + ". " + $"Tameable={(Object)(object)val.GetComponent<Tameable>() != (Object)null}; " + $"Procreation={(Object)(object)component7 != (Object)null}; " + "offspring=" + (((Object)(object)component7 != (Object)null && (Object)(object)component7.m_offspring != (Object)null) ? ((Object)component7.m_offspring).name : "<none>")));
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[BeastAI Adapter] Adapter failed. Falling back to original LMTY AddTame.\n" + ex));
				return true;
			}
		}

		private static void ConfigureCharacter(GameObject go, Character character, object tb)
		{
			string text = GetString(tb, "specialGroup", "");
			bool flag = GetBool(tb, "hostileToSelf", fallback: false);
			if (!string.IsNullOrEmpty(text))
			{
				character.m_group = text;
			}
			else if (!flag)
			{
				character.m_group = CleanPrefabName(((Object)go).name);
			}
			else
			{
				character.m_group = "";
			}
		}

		private static Tameable ConfigureTameable(GameObject go, Tameable tameable, object tb)
		{
			if ((Object)(object)tameable == (Object)null)
			{
				tameable = go.AddComponent<Tameable>();
				Plugin.Log.LogInfo((object)"[BeastAI Adapter] Added missing Tameable.");
			}
			tameable.m_commandable = GetBool(tb, "commandable", fallback: true);
			tameable.m_tamingTime = GetFloat(tb, "tamingTime", 1800f);
			tameable.m_fedDuration = GetFloat(tb, "fedDuration", 600f);
			return tameable;
		}

		private static void ConfigureFood(MonsterAI ai, object tb)
		{
			ai.m_consumeRange = GetFloat(tb, "consumeRange", 2f);
			ai.m_consumeSearchInterval = GetFloat(tb, "consumeSearchInterval", 10f);
			ai.m_consumeSearchRange = GetFloat(tb, "consumeSearchRange", 10f);
			ai.m_consumeItems = new List<ItemDrop>();
			string text = GetString(tb, "consumeItems", "");
			if (string.IsNullOrWhiteSpace(text))
			{
				Plugin.Log.LogWarning((object)"[BeastAI Adapter] consumeItems is empty.");
				return;
			}
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[BeastAI Adapter] ObjectDB.instance is null while applying food config.");
				return;
			}
			char[] separator = new char[3] { ',', ':', ';' };
			string[] array = text.Split(separator, StringSplitOptions.RemoveEmptyEntries);
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text2 = array2[i].Trim();
				if (text2.Length != 0)
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text2);
					ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
					if ((Object)(object)val != (Object)null)
					{
						ai.m_consumeItems.Add(val);
						list.Add(((Object)itemPrefab).name);
					}
					else
					{
						list2.Add(text2);
					}
				}
			}
			Plugin.Log.LogInfo((object)("[BeastAI Adapter] Food config: raw=\"" + text + "\"; resolved=[" + string.Join(", ", list.ToArray()) + "]; missing=[" + string.Join(", ", list2.ToArray()) + "]; count=" + ai.m_consumeItems.Count + "; consumeRange=" + ai.m_consumeRange + "; searchRange=" + ai.m_consumeSearchRange + "; searchInterval=" + ai.m_consumeSearchInterval));
			EnsureConsumeBridge(((Component)ai).gameObject);
		}

		private static void EnsureConsumeBridge(GameObject go)
		{
			if (!((Object)(object)go == (Object)null) && (Object)(object)go.GetComponent<BeastAIConsumeBridge>() == (Object)null)
			{
				go.AddComponent<BeastAIConsumeBridge>();
				Plugin.Log.LogInfo((object)("[BeastAI Adapter] Added BeastAIConsumeBridge to " + CleanPrefabName(((Object)go).name)));
			}
		}

		private static Procreation ConfigureProcreation(GameObject go, Procreation proc, object tb)
		{
			bool flag = false;
			if ((Object)(object)proc == (Object)null)
			{
				proc = go.AddComponent<Procreation>();
				flag = true;
			}
			proc.m_maxCreatures = GetInt(tb, "maxCreatures", 5);
			float num = GetFloat(tb, "pregnancyChance", 0.66f);
			if (num > 1f)
			{
				num *= 0.01f;
			}
			if (num > 1f)
			{
				num = 0.66f;
			}
			proc.m_pregnancyChance = num;
			proc.m_pregnancyDuration = GetFloat(tb, "pregnancyDuration", 60f);
			if (proc.m_updateInterval < 20f)
			{
				proc.m_updateInterval = 20f;
			}
			float num2 = GetFloat(tb, "size", 1f);
			proc.m_partnerCheckRange = 4f * num2;
			proc.m_totalCheckRange = 10f * num2;
			ClearProcreationCaches(proc);
			Plugin.Log.LogInfo((object)($"[BeastAI Adapter] Procreation configured: added={flag}; max={proc.m_maxCreatures}; " + $"chance={proc.m_pregnancyChance}; pregnancy={proc.m_pregnancyDuration}s; " + $"partnerRange={proc.m_partnerCheckRange}; totalRange={proc.m_totalCheckRange}"));
			return proc;
		}

		private static void ConfigureOffspring(GameObject go, Procreation proc, object tb)
		{
			bool flag = GetBool(tb, "offspringOverwrite", fallback: false);
			float num = GetFloat(tb, "growTime", 1800f);
			if ((Object)(object)proc.m_offspring != (Object)null && !flag)
			{
				ApplyGrowTime(proc.m_offspring, num);
				Plugin.Log.LogInfo((object)("[Generic Compatibility] Keeping existing offspring: " + ((Object)proc.m_offspring).name));
				return;
			}
			object member = GetMember(tb, "offspringPrefab");
			GameObject val = (GameObject)((member is GameObject) ? member : null);
			string text = (((Object)(object)val != (Object)null) ? CleanPrefabName(((Object)val).name) : ((member != null) ? member.ToString().Trim() : ""));
			if (!string.IsNullOrEmpty(text))
			{
				if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null)
				{
					val = ZNetScene.instance.GetPrefab(text);
				}
				if ((Object)(object)val == (Object)null)
				{
					throw new InvalidOperationException("Configured offspringPrefab could not be resolved from ZNetScene: " + text);
				}
				proc.m_offspring = val;
				ApplyGrowTime(val, num);
				ClearProcreationCaches(proc);
				Plugin.Log.LogInfo((object)("[Generic Compatibility] Explicit LMTY offspringPrefab assigned: " + ((Object)val).name));
				return;
			}
			Type type = AccessTools.TypeByName("LetMeTameYou.TameManager");
			MethodInfo obj = ((type != null) ? AccessTools.Method(type, "SpawnMini", new Type[2]
			{
				typeof(GameObject),
				typeof(float)
			}, (Type[])null) : null);
			if (obj == null)
			{
				throw new MissingMethodException("LetMeTameYou.TameManager.SpawnMini(GameObject,float) not found.");
			}
			object? obj2 = obj.Invoke(null, new object[2] { go, num });
			GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null);
			if ((Object)(object)val2 == (Object)null)
			{
				throw new InvalidOperationException("LMTY SpawnMini returned null.");
			}
			proc.m_offspring = val2;
			ClearProcreationCaches(proc);
			Plugin.Log.LogInfo((object)("[Generic Compatibility] LMTY offspring assigned: " + ((Object)val2).name));
		}

		private static void ApplyGrowTime(GameObject offspring, float growTime)
		{
			if ((Object)(object)offspring == (Object)null)
			{
				return;
			}
			Growup component = offspring.GetComponent<Growup>();
			if ((Object)(object)component != (Object)null)
			{
				component.m_growTime = growTime;
				return;
			}
			EggGrow component2 = offspring.GetComponent<EggGrow>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)component2.m_grownPrefab != (Object)null)
			{
				Growup component3 = component2.m_grownPrefab.GetComponent<Growup>();
				if ((Object)(object)component3 != (Object)null)
				{
					component3.m_growTime = growTime;
				}
			}
		}

		private static void ClearProcreationCaches(Procreation proc)
		{
			AccessTools.Field(typeof(Procreation), "m_offspringPrefab")?.SetValue(proc, null);
			AccessTools.Field(typeof(Procreation), "m_myPrefab")?.SetValue(proc, null);
		}

		private static bool HasSpecialEggConfig(object tb, out string description)
		{
			description = "";
			object member = GetMember(tb, "egg");
			if (member == null)
			{
				return false;
			}
			Type type = member.GetType();
			string text;
			if (member is string)
			{
				text = ((string)member).Trim();
			}
			else
			{
				if (!type.IsEnum)
				{
					return false;
				}
				text = member.ToString().Trim();
			}
			if (string.IsNullOrEmpty(text) || string.Equals(text, "None", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "False", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "0", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			description = text;
			return true;
		}

		private static bool IsHighConfidencePovAI(MonsterAI ai, out string evidence)
		{
			evidence = "<none>";
			if ((Object)(object)ai == (Object)null)
			{
				return false;
			}
			Type type = ((object)ai).GetType();
			Type type2 = AccessTools.TypeByName("PassiveTree.BeastAI");
			if (type2 != null && (type == type2 || type2.IsAssignableFrom(type)))
			{
				evidence = "PassiveTree.BeastAI type match";
				return true;
			}
			if ((type.FullName ?? type.Name ?? "").StartsWith("PassiveTree.", StringComparison.Ordinal))
			{
				evidence = "PassiveTree namespace";
				return true;
			}
			if (((type.Assembly != null) ? (type.Assembly.GetName().Name ?? "") : "").IndexOf("PathOfValheiman", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				evidence = "PathOfValheiman assembly";
				return true;
			}
			return false;
		}

		private static string GetBaseAIType(GameObject go)
		{
			BaseAI[] components = go.GetComponents<BaseAI>();
			if (components == null || components.Length == 0)
			{
				return "<none>";
			}
			List<string> list = new List<string>();
			BaseAI[] array = components;
			foreach (BaseAI val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					list.Add(((object)val).GetType().FullName ?? ((object)val).GetType().Name);
				}
			}
			return string.Join(",", list);
		}

		private static string CleanPrefabName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length);
		}

		private static object GetMember(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			PropertyInfo property = type.GetProperty(name, AnyInstance);
			if (property != null)
			{
				return property.GetValue(obj, null);
			}
			FieldInfo field = type.GetField(name, AnyInstance);
			if (field != null)
			{
				return field.GetValue(obj);
			}
			return null;
		}

		private static bool GetBool(object obj, string name, bool fallback)
		{
			object member = GetMember(obj, name);
			try
			{
				return (member == null) ? fallback : Convert.ToBoolean(member);
			}
			catch
			{
				return fallback;
			}
		}

		private static int GetInt(object obj, string name, int fallback)
		{
			object member = GetMember(obj, name);
			try
			{
				return (member == null) ? fallback : Convert.ToInt32(member);
			}
			catch
			{
				return fallback;
			}
		}

		private static float GetFloat(object obj, string name, float fallback)
		{
			object member = GetMember(obj, name);
			try
			{
				return (member == null) ? fallback : Convert.ToSingle(member);
			}
			catch
			{
				return fallback;
			}
		}

		private static string GetString(object obj, string name, string fallback)
		{
			return GetMember(obj, name)?.ToString() ?? fallback;
		}
	}
	internal sealed class BeastAIConsumeBridge : MonoBehaviour
	{
		private MonsterAI _ai;

		private Tameable _tameable;

		private ZNetView _nview;

		private float _timer;

		private float _nextDiagnostic;

		private float _nextProgressDiagnostic;

		private float _progressTickTimer;

		private float _searchTimer;

		private ItemDrop _targetFood;

		private float _lastObservedTimeLeft = -1f;

		private float _nativeObservationElapsed;

		private bool _nativeProgressConfirmed;

		private bool _fallbackActivated;

		private bool _observationLogged;

		private const float NativeObservationWindow = 6f;

		private static readonly MethodInfo MoveToMethod = AccessTools.Method(typeof(BaseAI), "MoveTo", new Type[4]
		{
			typeof(float),
			typeof(Vector3),
			typeof(float),
			typeof(bool)
		}, (Type[])null);

		private static readonly MethodInfo DecreaseRemainingTimeMethod = AccessTools.Method(typeof(Tameable), "DecreaseRemainingTime", new Type[1] { typeof(float) }, (Type[])null);

		private static readonly MethodInfo GetRemainingTimeMethod = AccessTools.Method(typeof(Tameable), "GetRemainingTime", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo TameMethod = AccessTools.Method(typeof(Tameable), "Tame", Type.EmptyTypes, (Type[])null);

		private void Awake()
		{
			_ai = ((Component)this).GetComponent<MonsterAI>();
			_tameable = ((Component)this).GetComponent<Tameable>();
			_nview = ((Component)this).GetComponent<ZNetView>();
			if ((Object)(object)_ai != (Object)null && (Object)(object)_tameable != (Object)null)
			{
				Plugin.Log.LogInfo((object)("[BeastAI Feed] Runtime bridge awake: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; AI=" + ((object)_ai).GetType().FullName + "; foods=" + ((_ai.m_consumeItems != null) ? _ai.m_consumeItems.Count : 0)));
			}
		}

		private void Update()
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_ai == (Object)null || (Object)(object)_tameable == (Object)null)
			{
				return;
			}
			_timer -= Time.deltaTime;
			if (_timer > 0f)
			{
				return;
			}
			_timer = 0.35f;
			if (_ai.m_consumeItems == null || _ai.m_consumeItems.Count == 0 || ((Object)(object)_nview != (Object)null && _nview.IsValid() && !_nview.IsOwner()))
			{
				return;
			}
			bool flag;
			try
			{
				flag = _tameable.IsHungry();
			}
			catch
			{
				return;
			}
			if (!flag)
			{
				_targetFood = null;
				_searchTimer = 0f;
				RunTamingFallbackTick();
				LogTamingProgress();
				return;
			}
			_progressTickTimer = 0f;
			_lastObservedTimeLeft = -1f;
			_nativeObservationElapsed = 0f;
			_nativeProgressConfirmed = false;
			_fallbackActivated = false;
			_observationLogged = false;
			if (!IsValidTarget(_targetFood))
			{
				_targetFood = null;
				_searchTimer -= 0.35f;
				if (_searchTimer <= 0f)
				{
					_searchTimer = Mathf.Max(1f, _ai.m_consumeSearchInterval);
					_targetFood = FindNearestAllowedFood(out var _);
				}
			}
			if ((Object)(object)_targetFood == (Object)null)
			{
				return;
			}
			float num = Vector3.Distance(((Component)this).transform.position, ((Component)_targetFood).transform.position);
			float num2 = Mathf.Max(_ai.m_consumeRange, _ai.m_consumeSearchRange);
			if (num > num2)
			{
				_targetFood = null;
				return;
			}
			float num3 = Mathf.Max(0.5f, _ai.m_consumeRange);
			if (num > num3)
			{
				TryMoveTo(((Component)_targetFood).transform.position, num3 * 0.8f);
				if (Time.time >= _nextDiagnostic)
				{
					_nextDiagnostic = Time.time + 3f;
					Plugin.Log.LogInfo((object)("[BeastAI Feed] Food detected: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; food=" + CleanName(((Object)((Component)_targetFood).gameObject).name) + "; distance=" + num.ToString("0.00") + "; eatRange=" + num3.ToString("0.00") + "; movingTowardFood=True"));
				}
			}
			else
			{
				ItemDrop targetFood = _targetFood;
				_targetFood = null;
				Consume(targetFood, num);
			}
		}

		private bool IsValidTarget(ItemDrop drop)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)drop == (Object)null || !((Component)drop).gameObject.activeInHierarchy || drop.m_itemData == null || drop.m_itemData.m_shared == null)
			{
				return false;
			}
			if (!IsAllowed(drop))
			{
				return false;
			}
			float num = Mathf.Max(_ai.m_consumeRange, _ai.m_consumeSearchRange);
			return Vector3.Distance(((Component)this).transform.position, ((Component)drop).transform.position) <= num;
		}

		private void RunTamingFallbackTick()
		{
			if (_tameable.IsTamed())
			{
				return;
			}
			_progressTickTimer += 0.35f;
			if (_progressTickTimer < 3f)
			{
				return;
			}
			float progressTickTimer = _progressTickTimer;
			_progressTickTimer = 0f;
			if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner())
			{
				return;
			}
			bool flag = false;
			try
			{
				flag = ((BaseAI)_ai).IsAlerted();
			}
			catch
			{
				flag = false;
			}
			ZDO zDO = _nview.GetZDO();
			if (zDO == null)
			{
				return;
			}
			float num = zDO.GetFloat("TameTimeLeft", -1f);
			if (num < 0f)
			{
				long num2 = zDO.GetLong("TameLastFeeding", 0L);
				if (num2 <= 0)
				{
					if (Time.time >= _nextDiagnostic)
					{
						_nextDiagnostic = Time.time + 3f;
						Plugin.Log.LogWarning((object)("[Adaptive Taming] Fed-state has no TameTimeLeft and no TameLastFeeding; countdown not initialized: prefab=" + CleanName(((Object)((Component)this).gameObject).name)));
					}
					return;
				}
				num = Mathf.Max(0f, _tameable.m_tamingTime);
				zDO.Set("TameTimeLeft", num);
				_lastObservedTimeLeft = num;
				_nativeObservationElapsed = 0f;
				_nativeProgressConfirmed = false;
				_fallbackActivated = false;
				_observationLogged = false;
				Plugin.Log.LogWarning((object)("[Adaptive Taming] Fed creature had no TameTimeLeft; initialized countdown: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; TameTimeLeft=" + num.ToString("0.0") + "; TameLastFeeding=" + num2));
			}
			if (flag)
			{
				if (Time.time >= _nextDiagnostic)
				{
					_nextDiagnostic = Time.time + 3f;
					Plugin.Log.LogInfo((object)("[BeastAI Feed] Taming paused: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; reason=alerted; TameTimeLeft=" + num.ToString("0.0")));
				}
				_lastObservedTimeLeft = num;
				return;
			}
			if (_lastObservedTimeLeft >= 0f && num < _lastObservedTimeLeft - 0.01f)
			{
				if (!_nativeProgressConfirmed)
				{
					Plugin.Log.LogInfo((object)("[Adaptive Taming] Native progress detected; fallback remains inactive: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; previous=" + _lastObservedTimeLeft.ToString("0.0") + "; current=" + num.ToString("0.0")));
				}
				_nativeProgressConfirmed = true;
				_fallbackActivated = false;
				_nativeObservationElapsed = 0f;
				_lastObservedTimeLeft = num;
				return;
			}
			if (_nativeProgressConfirmed)
			{
				_lastObservedTimeLeft = num;
				return;
			}
			_nativeObservationElapsed += progressTickTimer;
			if (!_fallbackActivated && _nativeObservationElapsed < 6f)
			{
				if (!_observationLogged)
				{
					_observationLogged = true;
					Plugin.Log.LogInfo((object)("[Adaptive Taming] Observing native timer before fallback: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; window=" + 6f.ToString("0") + "s; TameTimeLeft=" + num.ToString("0.0")));
				}
				_lastObservedTimeLeft = num;
				return;
			}
			if (!_fallbackActivated)
			{
				_fallbackActivated = true;
				Plugin.Log.LogWarning((object)("[Adaptive Taming] Native timer did not advance; fallback activated: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; observed=" + _nativeObservationElapsed.ToString("0.0") + "s; TameTimeLeft=" + num.ToString("0.0")));
			}
			try
			{
				if (DecreaseRemainingTimeMethod != null)
				{
					DecreaseRemainingTimeMethod.Invoke(_tameable, new object[1] { progressTickTimer });
				}
				else
				{
					zDO.Set("TameTimeLeft", Mathf.Max(0f, num - progressTickTimer));
				}
				float num3 = (_lastObservedTimeLeft = ((GetRemainingTimeMethod != null) ? ((float)GetRemainingTimeMethod.Invoke(_tameable, null)) : zDO.GetFloat("TameTimeLeft", -1f)));
				Plugin.Log.LogInfo((object)("[BeastAI Feed] Fallback taming tick: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; before=" + num.ToString("0.0") + "; after=" + num3.ToString("0.0") + "; alerted=False"));
				if (num3 <= 0f && !_tameable.IsTamed())
				{
					if (TameMethod != null)
					{
						TameMethod.Invoke(_tameable, null);
					}
					Plugin.Log.LogInfo((object)("[BeastAI Feed] Taming completed by fallback: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; tamed=" + _tameable.IsTamed()));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[BeastAI Feed] Fallback taming tick failed on " + CleanName(((Object)((Component)this).gameObject).name) + ": " + ex));
			}
		}

		private void LogTamingProgress()
		{
			if (_tameable.IsTamed() || Time.time < _nextProgressDiagnostic)
			{
				return;
			}
			_nextProgressDiagnostic = Time.time + 15f;
			if (!((Object)(object)_nview == (Object)null) && _nview.IsValid())
			{
				ZDO zDO = _nview.GetZDO();
				if (zDO != null)
				{
					float num = zDO.GetFloat("TameTimeLeft", -1f);
					float tamingTime = _tameable.m_tamingTime;
					float num2 = ((num >= 0f && tamingTime > 0f) ? (Mathf.Clamp01(1f - num / tamingTime) * 100f) : (-1f));
					Plugin.Log.LogInfo((object)("[BeastAI Feed] Taming progress: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; hungry=False; TameTimeLeft=" + num.ToString("0.0") + "; tamingTime=" + tamingTime.ToString("0.0") + "; approxPercent=" + ((num2 >= 0f) ? num2.ToString("0.0") : "unknown") + "; tamed=" + _tameable.IsTamed()));
				}
			}
		}

		private ItemDrop FindNearestAllowedFood(out float nearestDistance)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			nearestDistance = float.MaxValue;
			ItemDrop result = null;
			float num = Mathf.Max(_ai.m_consumeRange, _ai.m_consumeSearchRange);
			Vector3 position = ((Component)this).transform.position;
			ItemDrop[] array = Object.FindObjectsByType<ItemDrop>((FindObjectsSortMode)0);
			foreach (ItemDrop val in array)
			{
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && val.m_itemData != null && val.m_itemData.m_shared != null && IsAllowed(val))
				{
					float num2 = Vector3.Distance(position, ((Component)val).transform.position);
					if (!(num2 > num) && !(num2 >= nearestDistance))
					{
						result = val;
						nearestDistance = num2;
					}
				}
			}
			return result;
		}

		private bool IsAllowed(ItemDrop drop)
		{
			string name = drop.m_itemData.m_shared.m_name;
			string text = CleanName(((Object)((Component)drop).gameObject).name);
			foreach (ItemDrop consumeItem in _ai.m_consumeItems)
			{
				if (!((Object)(object)consumeItem == (Object)null) && consumeItem.m_itemData != null && consumeItem.m_itemData.m_shared != null)
				{
					if (consumeItem.m_itemData.m_shared.m_name == name)
					{
						return true;
					}
					if (CleanName(((Object)((Component)consumeItem).gameObject).name) == text)
					{
						return true;
					}
				}
			}
			return false;
		}

		private void Consume(ItemDrop drop, float distance)
		{
			try
			{
				if ((Object)(object)drop == (Object)null || !drop.RemoveOne())
				{
					return;
				}
				if (_ai.m_onConsumedItem != null)
				{
					_ai.m_onConsumedItem(null);
				}
				if ((Object)(object)_nview != (Object)null && _nview.IsValid())
				{
					ZDO zDO = _nview.GetZDO();
					if (zDO != null)
					{
						long num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTime().Ticks : DateTime.UtcNow.Ticks);
						zDO.Set("TameLastFeeding", num);
						float num2 = zDO.GetFloat("TameTimeLeft", -1f);
						bool flag = false;
						if (num2 < 0f)
						{
							num2 = _tameable.m_tamingTime;
							zDO.Set("TameTimeLeft", num2);
							flag = true;
						}
						_lastObservedTimeLeft = num2;
						_nativeObservationElapsed = 0f;
						_nativeProgressConfirmed = false;
						_fallbackActivated = false;
						_observationLogged = false;
						Plugin.Log.LogInfo((object)("[BeastAI Feed] Feeding state updated: TameLastFeeding=" + num + "; TameTimeLeft=" + num2.ToString("0.0") + "; initialized=" + flag + "; tamingTime=" + _tameable.m_tamingTime.ToString("0.0")));
					}
				}
				Plugin.Log.LogInfo((object)("[BeastAI Feed] CONSUMED: prefab=" + CleanName(((Object)((Component)this).gameObject).name) + "; food=" + CleanName(((Object)((Component)drop).gameObject).name) + "; distance=" + distance.ToString("0.00") + "; tamed=" + _tameable.IsTamed()));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("[BeastAI Feed] Consume failed on " + CleanName(((Object)((Component)this).gameObject).name) + ": " + ex));
			}
		}

		private void TryMoveTo(Vector3 point, float stopDistance)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (MoveToMethod == null)
			{
				return;
			}
			try
			{
				MoveToMethod.Invoke(_ai, new object[4] { 0.35f, point, stopDistance, false });
			}
			catch
			{
			}
		}

		private static string CleanName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			int num = name.IndexOf('(');
			int num2 = name.IndexOf(' ');
			int num3 = -1;
			if (num >= 0)
			{
				num3 = num;
			}
			if (num2 >= 0 && (num3 < 0 || num2 < num3))
			{
				num3 = num2;
			}
			if (num3 < 0)
			{
				return name;
			}
			return name.Substring(0, num3);
		}
	}
}