Decompiled source of Gathering v1.0.0

Gathering.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Gathering")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Gathering")]
[assembly: AssemblyTitle("Gathering")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Gathering;

[BepInPlugin("tchernobill.gathering", "Gathering", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "tchernobill.gathering";

	public const string NAME = "Gathering";

	public const string VERSION = "1.0.0";

	internal static ManualLogSource Log;

	internal static ConfigEntry<float> Distance;

	internal static ConfigEntry<float> Angle;

	internal static ConfigEntry<float> HoldTime;

	internal static ConfigEntry<bool> NearestWins;

	internal static ConfigEntry<bool> FaceSource;

	internal static ConfigEntry<bool> Mining;

	internal static ConfigEntry<bool> Fishing;

	internal static ConfigEntry<bool> Chests;

	private void Awake()
	{
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Expected O, but got Unknown
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Expected O, but got Unknown
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Distance = ((BaseUnityPlugin)this).Config.Bind<float>("Gathering", "Distance", 3f, new ConfigDescription("Maximum horizontal distance (m) between the character and a source. Vanilla reaches about 2 m in front of the character and much less on the sides.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
		Angle = ((BaseUnityPlugin)this).Config.Bind<float>("Gathering", "Angle", 180f, new ConfigDescription("Maximum angle (degrees) between where the character faces and the source. 180 = any direction, back included. Vanilla is roughly 60 at 1 m.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 180f), Array.Empty<object>()));
		HoldTime = ((BaseUnityPlugin)this).Config.Bind<float>("Gathering", "Hold Time", 0.2f, new ConfigDescription("How long (s) the interact button must be held to gather (or to open a chest that opens on hold). 0 = on press. Vanilla: 1.25 (3 for some chests).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
		NearestWins = ((BaseUnityPlugin)this).Config.Bind<bool>("Gathering", "Nearest Wins", true, "When a source is among several interactables in reach, the nearest one becomes the active one (vanilla favours what the character faces).");
		FaceSource = ((BaseUnityPlugin)this).Config.Bind<bool>("Gathering", "Face Source", true, "Turn the character toward the source when gathering (or opening) starts, so the animation reaches for it.");
		Mining = ((BaseUnityPlugin)this).Config.Bind<bool>("Sources", "Mining", true, "Apply to mining spots (ore veins, which need a mining pick). Plants and dead wood always are.");
		Fishing = ((BaseUnityPlugin)this).Config.Bind<bool>("Sources", "Fishing", true, "Apply to fishing spots (which need a fishing harpoon).");
		Chests = ((BaseUnityPlugin)this).Config.Bind<bool>("Sources", "Chests", true, "Apply to chests and the other loot containers of the world (TreasureChest: chests, junk piles, stashes...).");
		Harmony val = new Harmony("tchernobill.gathering");
		val.PatchAll(typeof(GatherPatches));
		val.Patch(GatherPatches.InteractCoroutine(), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GatherPatches), "HoldTimes", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
		TestHarness.StartIfRequested(((Component)this).gameObject);
	}
}
internal enum SourceKind
{
	None,
	Plant,
	Mining,
	Fishing,
	Chest
}
[HarmonyPatch]
internal static class GatherPatches
{
	private const float VanillaHoldThreshold = 0.4f;

	private const float TorsoHeight = 0.9f;

	private const float VerticalReach = 2f;

	private const int MiningPick = 2120050;

	private const int FishingHarpoon = 2130130;

	private static readonly Collider[] s_hits = (Collider[])(object)new Collider[256];

	private static readonly Dictionary<InteractionTriggerBase, float> s_found = new Dictionary<InteractionTriggerBase, float>();

	private static readonly FieldRef<Character, InteractionTriggerBase> s_lastInteractable = AccessTools.FieldRefAccess<Character, InteractionTriggerBase>("m_lastInteractable");

	private static readonly FieldRef<InteractionTriggerBase, EventActivator> s_activator = AccessTools.FieldRefAccess<InteractionTriggerBase, EventActivator>("m_defaultTriggerManager");

	private static readonly FieldRef<InteractionTriggerBase, List<Collider>> s_extraColliders = AccessTools.FieldRefAccess<InteractionTriggerBase, List<Collider>>("m_extraInteractionColliders");

	private static readonly Func<Character, Collider, InteractionTriggerBase, bool> s_isBlocked = AccessTools.MethodDelegate<Func<Character, Collider, InteractionTriggerBase, bool>>(AccessTools.Method(typeof(Character), "GetIsBlockedByEnvironment", (Type[])null, (Type[])null), (object)null, true);

	private static readonly Action<Character> s_vanillaPick = AccessTools.MethodDelegate<Action<Character>>(AccessTools.Method(typeof(Character), "FindInteractableItem", (Type[])null, (Type[])null), (object)null, true);

	internal static bool Vanilla;

	internal static SourceKind KindOf(InteractionTriggerBase trigger)
	{
		Item val = (((Object)(object)trigger != (Object)null) ? trigger.ParentItem : null);
		Gatherable val2 = (Gatherable)(object)((val is Gatherable) ? val : null);
		if (val2 != null)
		{
			return (((Object)(object)val2.RequiredItem != (Object)null) ? val2.RequiredItem.ItemID : 0) switch
			{
				2130130 => SourceKind.Fishing, 
				2120050 => SourceKind.Mining, 
				_ => SourceKind.Plant, 
			};
		}
		if (!(val is TreasureChest))
		{
			return SourceKind.None;
		}
		return SourceKind.Chest;
	}

	internal static bool Applies(InteractionTriggerBase trigger)
	{
		if (Vanilla)
		{
			return false;
		}
		return KindOf(trigger) switch
		{
			SourceKind.Plant => true, 
			SourceKind.Mining => Plugin.Mining.Value, 
			SourceKind.Fishing => Plugin.Fishing.Value, 
			SourceKind.Chest => Plugin.Chests.Value, 
			_ => false, 
		};
	}

	[HarmonyPostfix]
	[HarmonyPatch(typeof(Character), "UpdateWorldItemDetection")]
	private static void AfterDetection(Character __instance, List<InteractionTriggerBase> ___m_closeInteractionTriggers, ref int ___m_mostRelevantInteractableIndex, float ___m_lastItemDectionTime)
	{
		if (___m_lastItemDectionTime != Time.time || __instance.IsDead || !__instance.IsLocalPlayer)
		{
			return;
		}
		___m_closeInteractionTriggers.RemoveAll(Applies);
		FindSources(__instance);
		foreach (KeyValuePair<InteractionTriggerBase, float> item in s_found)
		{
			___m_closeInteractionTriggers.Add(item.Key);
		}
		___m_mostRelevantInteractableIndex = -1;
		if (NearestWinsAmong(___m_closeInteractionTriggers))
		{
			___m_mostRelevantInteractableIndex = PickNearest(__instance, ___m_closeInteractionTriggers);
		}
		else
		{
			s_vanillaPick(__instance);
		}
		s_found.Clear();
	}

	internal static void FindSources(Character character)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		s_found.Clear();
		float value = Plugin.Distance.Value;
		float value2 = Plugin.Angle.Value;
		Vector3 val = ((Component)character).transform.position + Vector3.up * 0.9f;
		Vector3 val2 = Flat(((Component)character).transform.forward);
		int num = Physics.OverlapSphereNonAlloc(val, value + 2f, s_hits, Global.WorldItemsMask);
		for (int i = 0; i < num; i++)
		{
			Collider val3 = s_hits[i];
			TriggerColliderFlag component = ((Component)val3).GetComponent<TriggerColliderFlag>();
			InteractionTriggerBase val4 = (InteractionTriggerBase)(((Object)(object)component != (Object)null && component.Trigger != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)((Component)val3).GetComponentInParent<InteractionTriggerBase>()));
			if (!Applies(val4))
			{
				continue;
			}
			Bounds bounds = val3.bounds;
			Vector3 val5 = ((Bounds)(ref bounds)).ClosestPoint(val);
			Vector3 val6 = Flat(val5 - val);
			float magnitude = ((Vector3)(ref val6)).magnitude;
			if (magnitude > value || Mathf.Abs(val5.y - val.y) > 2f || (value2 < 180f && magnitude > 0.3f && Vector3.Angle(val2, val6) > value2))
			{
				continue;
			}
			if (s_found.TryGetValue(val4, out var value3))
			{
				if (magnitude < value3)
				{
					s_found[val4] = magnitude;
				}
			}
			else if (!s_isBlocked(character, val3, val4) && (val4.IsBag || val4.GetBasicCanBeTriggered(character, val4.NotifyOnInvalidConditions) || val4.GetHoldCanBeTriggered(character, val4.NotifyOnInvalidConditions)))
			{
				s_found[val4] = magnitude;
			}
		}
	}

	private static bool NearestWinsAmong(List<InteractionTriggerBase> triggers)
	{
		if (!Plugin.NearestWins.Value || triggers.Count < 2)
		{
			return false;
		}
		foreach (InteractionTriggerBase trigger in triggers)
		{
			if (Applies(trigger))
			{
				return true;
			}
		}
		return false;
	}

	private static int PickNearest(Character character, List<InteractionTriggerBase> triggers)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		Vector3 torso = ((Component)character).transform.position + Vector3.up * 0.9f;
		int result = -1;
		int num = int.MaxValue;
		float num2 = float.PositiveInfinity;
		for (int i = 0; i < triggers.Count; i++)
		{
			InteractionTriggerBase val = triggers[i];
			float value;
			float num3 = (s_found.TryGetValue(val, out value) ? value : DistanceTo(val, torso));
			if (val.DetectionPriority < num || (val.DetectionPriority == num && num3 < num2))
			{
				result = i;
				num = val.DetectionPriority;
				num2 = num3;
			}
		}
		return result;
	}

	internal static float DistanceTo(InteractionTriggerBase trigger, Vector3 torso)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		Collider interactionCollider = trigger.InteractionCollider;
		Vector3 val;
		if (!((Object)(object)interactionCollider != (Object)null) || !interactionCollider.enabled)
		{
			val = ((Component)trigger).transform.position;
		}
		else
		{
			Bounds bounds = interactionCollider.bounds;
			val = ((Bounds)(ref bounds)).ClosestPoint(torso);
		}
		Vector3 val2 = Flat(val - torso);
		return ((Vector3)(ref val2)).magnitude;
	}

	private static Vector3 Flat(Vector3 v)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		return new Vector3(v.x, 0f, v.z);
	}

	internal static MethodBase InteractCoroutine()
	{
		return AccessTools.Method(typeof(Character).GetNestedTypes(AccessTools.all).First((Type t) => t.Name.Contains("InteractButtonCoroutine")), "MoveNext", (Type[])null, (Type[])null);
	}

	internal static IEnumerable<CodeInstruction> HoldTimes(IEnumerable<CodeInstruction> instructions)
	{
		//IL_0196: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Expected O, but got Unknown
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Expected O, but got Unknown
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Expected O, but got Unknown
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Expected O, but got Unknown
		FieldInfo fieldInfo = null;
		MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(InteractionTriggerBase), "HoldActivationTime");
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		foreach (CodeInstruction item in list)
		{
			if (item.opcode == OpCodes.Ldfld && item.operand is FieldInfo fieldInfo2 && fieldInfo2.FieldType == typeof(Character) && fieldInfo2.Name.Contains("4__this"))
			{
				fieldInfo = fieldInfo2;
			}
		}
		if (fieldInfo == null)
		{
			Plugin.Log.LogError((object)"InteractButtonCoroutine: no <>4__this field, hold time unchanged");
			return list;
		}
		int num = 0;
		int num2 = 0;
		for (int i = 0; i < list.Count; i++)
		{
			if (list[i].opcode == OpCodes.Ldc_R4 && (float)list[i].operand == 0.4f)
			{
				CodeInstruction val = CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_0, (object)null), list[i]);
				list[i] = new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(GatherPatches), "HoldThreshold", (Type[])null, (Type[])null));
				list.InsertRange(i, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2]
				{
					val,
					new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo)
				});
				i += 2;
				num++;
			}
			else if (CodeInstructionExtensions.Calls(list[i], methodInfo))
			{
				list[i] = CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(GatherPatches), "HoldActivationTime", (Type[])null, (Type[])null)), list[i]);
				num2++;
			}
		}
		if (num != 4 || num2 != 1)
		{
			Plugin.Log.LogWarning((object)$"InteractButtonCoroutine: replaced {num} thresholds (expected 4) and {num2} hold times (expected 1)");
		}
		return list;
	}

	internal static float HoldThreshold(Character character)
	{
		InteractionTriggerBase val = s_lastInteractable.Invoke(character);
		if (!Applies(val))
		{
			return 0.4f;
		}
		EventActivator val2 = s_activator.Invoke(val);
		if (!((Object)(object)val2 != (Object)null) || !val2.CanDoBasic)
		{
			return 0f;
		}
		return Mathf.Min(0.4f, Plugin.HoldTime.Value * 0.5f);
	}

	internal static float HoldActivationTime(InteractionTriggerBase trigger)
	{
		if (!Applies(trigger))
		{
			return trigger.HoldActivationTime;
		}
		return Plugin.HoldTime.Value;
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(InteractionTriggerBase), "ActivateBasicAction", new Type[]
	{
		typeof(Character),
		typeof(int)
	})]
	private static void FaceOnBasic(InteractionTriggerBase __instance, Character _character)
	{
		Face(__instance, _character);
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(InteractionTriggerBase), "ActivateHoldAction")]
	private static void FaceOnHold(InteractionTriggerBase __instance, Character _character)
	{
		Face(__instance, _character);
	}

	private static void Face(InteractionTriggerBase trigger, Character character)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: 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_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		if (Plugin.FaceSource.Value && !((Object)(object)character == (Object)null) && character.IsLocalPlayer && Applies(trigger))
		{
			Vector3 val = ((Component)character).transform.position + Vector3.up * 0.9f;
			Vector3 val2 = Flat(NearestPoint(trigger, val) - val);
			if (((Vector3)(ref val2)).sqrMagnitude > 0.01f)
			{
				((Component)character).transform.rotation = Quaternion.LookRotation(val2);
			}
		}
	}

	internal static Vector3 NearestPoint(InteractionTriggerBase trigger, Vector3 from)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		Vector3 best = ((Component)trigger).transform.position;
		float bestDistance = float.PositiveInfinity;
		Consider(trigger.InteractionCollider);
		foreach (Collider item in s_extraColliders.Invoke(trigger))
		{
			Consider(item);
		}
		return best;
		void Consider(Collider collider)
		{
			//IL_0020: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)collider == (Object)null) && collider.enabled && ((Component)collider).gameObject.activeInHierarchy)
			{
				Bounds bounds = collider.bounds;
				Vector3 val = ((Bounds)(ref bounds)).ClosestPoint(from);
				Vector3 val2 = Flat(val - from);
				float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
				if (sqrMagnitude < bestDistance)
				{
					best = val;
					bestDistance = sqrMagnitude;
				}
			}
		}
	}
}
public class TestHarness : MonoBehaviour
{
	private static readonly FieldRef<Item, InteractionTriggerBase> s_itemTrigger = AccessTools.FieldRefAccess<Item, InteractionTriggerBase>("m_interactionTrigger");

	private static readonly FieldRef<InteractionTriggerBase, EventActivator> s_activator = AccessTools.FieldRefAccess<InteractionTriggerBase, EventActivator>("m_defaultTriggerManager");

	private static readonly FieldRef<CharacterCamera, Transform> s_cameraPitch = AccessTools.FieldRefAccess<CharacterCamera, Transform>("m_cameraVertHolder");

	private static readonly FieldRef<CharacterUI, InteractionDisplay> s_prompt = AccessTools.FieldRefAccess<CharacterUI, InteractionDisplay>("m_interactionPanel");

	private static Gatherable s_activated;

	private static float s_activatedAt;

	private static Gatherable s_gathered;

	private static float s_gatheredAt;

	private static InteractionTriggerBase s_held;

	private static float s_heldAt;

	private int m_failures;

	private static bool s_driving;

	private static Vector2 s_move;

	private bool m_spotFound;

	private Vector3 m_spotAway;

	private static readonly FieldRef<LocalCharacterControl, Transform> s_horiControl = AccessTools.FieldRefAccess<LocalCharacterControl, Transform>("m_horiControl");

	private bool m_recording;

	public static void StartIfRequested(GameObject host)
	{
		if (UnityEngineExtensions.Contains<string>(Environment.GetCommandLineArgs(), "--gathering-test"))
		{
			host.AddComponent<TestHarness>();
		}
	}

	private void Start()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Expected O, but got Unknown
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Expected O, but got Unknown
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Expected O, but got Unknown
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Expected O, but got Unknown
		new Harmony("tchernobill.gathering.test").Patch((MethodBase)AccessTools.Method(typeof(Gatherable), "OnGatherInteraction", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(TestHarness), "OnGathered", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		new Harmony("tchernobill.gathering.test").Patch((MethodBase)AccessTools.Method(typeof(GatherableInteraction), "Activate", new Type[1] { typeof(Character) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(TestHarness), "OnActivated", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		new Harmony("tchernobill.gathering.test").Patch((MethodBase)AccessTools.Method(typeof(InteractionTriggerBase), "ActivateHoldAction", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(TestHarness), "OnHeld", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		new Harmony("tchernobill.gathering.test").Patch((MethodBase)AccessTools.Method(typeof(LocalCharacterControl), "DetectMovementInputs", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(TestHarness), "DriveMovement", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((MonoBehaviour)this).StartCoroutine(Run());
	}

	private static void DriveMovement(ref Vector2 ___m_moveInput, ref Vector3 ___m_modifMoveInput)
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		if (s_driving)
		{
			___m_moveInput = s_move;
			___m_modifMoveInput = Vector2.op_Implicit(s_move);
		}
	}

	private static void OnGathered(Gatherable __instance)
	{
		s_gathered = __instance;
		s_gatheredAt = Time.realtimeSinceStartup;
	}

	private static void OnActivated(GatherableInteraction __instance)
	{
		s_activated = __instance.Gatherable;
		s_activatedAt = Time.realtimeSinceStartup;
	}

	private static void OnHeld(InteractionTriggerBase __instance)
	{
		s_held = __instance;
		s_heldAt = Time.realtimeSinceStartup;
	}

	private static void Log(string message)
	{
		Plugin.Log.LogMessage((object)("gathering-test: " + message));
	}

	private void Check(bool ok, string what)
	{
		if (ok)
		{
			Log("ok " + what);
			return;
		}
		m_failures++;
		Plugin.Log.LogError((object)("gathering-test: KO " + what));
	}

	private static IEnumerator WaitRealtime(float seconds)
	{
		float end = Time.realtimeSinceStartup + seconds;
		while (Time.realtimeSinceStartup < end)
		{
			yield return null;
		}
	}

	private static string Arg(string name)
	{
		string[] commandLineArgs = Environment.GetCommandLineArgs();
		int num = Array.IndexOf(commandLineArgs, name);
		if (num < 0 || num + 1 >= commandLineArgs.Length)
		{
			return null;
		}
		return commandLineArgs[num + 1];
	}

	private static InteractionTriggerBase TriggerOf(Gatherable g)
	{
		return s_itemTrigger.Invoke((Item)(object)g);
	}

	private static bool Usable(Gatherable g)
	{
		InteractionTriggerBase val = TriggerOf(g);
		EventActivator val2 = (((Object)(object)val != (Object)null) ? s_activator.Invoke(val) : null);
		if (((Behaviour)g).isActiveAndEnabled && (Object)(object)g.RequiredItem == (Object)null && (Object)(object)val2 != (Object)null && val2.CanDoHold)
		{
			return val.CanBeTriggered;
		}
		return false;
	}

	private static Vector3 Flat(Vector3 v)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		return new Vector3(v.x, 0f, v.z);
	}

	private static Vector3 Centre(Gatherable g)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		InteractionTriggerBase obj = TriggerOf(g);
		Collider val = ((obj != null) ? obj.InteractionCollider : null);
		if (!((Object)(object)val != (Object)null))
		{
			return ((Component)g).transform.position;
		}
		Bounds bounds = val.bounds;
		return ((Bounds)(ref bounds)).center;
	}

	private static bool Place(Character character, Vector3 spot, Vector3 facing)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		RaycastHit val = default(RaycastHit);
		if (!Physics.Raycast(spot + Vector3.up * 4f, Vector3.down, ref val, 10f, Global.GeneralEnvironmentMask))
		{
			return false;
		}
		character.Teleport(((RaycastHit)(ref val)).point + Vector3.up * 0.05f, Quaternion.LookRotation(Flat(facing)));
		return true;
	}

	private IEnumerator Settle()
	{
		yield return WaitRealtime(0.6f);
	}

	private static float Measured(Character character, Gatherable g)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		return GatherPatches.DistanceTo(TriggerOf(g), ((Component)character).transform.position + Vector3.up * 0.9f);
	}

	private static string OutputDir()
	{
		string text = Path.Combine(Paths.BepInExRootPath, "GatheringTest");
		Directory.CreateDirectory(text);
		return text;
	}

	private static void Capture(string file)
	{
		Texture2D val = ScreenCapture.CaptureScreenshotAsTexture();
		string text = Path.Combine(OutputDir(), file);
		File.WriteAllBytes(text, ImageConversion.EncodeToPNG(val));
		Log($"capture {((Texture)val).width}x{((Texture)val).height} saved to {text}");
	}

	private static Vector3 PointOf(InteractionTriggerBase trigger)
	{
		//IL_001e: 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)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		Collider interactionCollider = trigger.InteractionCollider;
		if (!((Object)(object)interactionCollider != (Object)null) || !interactionCollider.enabled)
		{
			return ((Component)trigger).transform.position;
		}
		Bounds bounds = interactionCollider.bounds;
		return ((Bounds)(ref bounds)).center;
	}

	private IEnumerator FindSpot(Character character, InteractionTriggerBase trigger, float distance)
	{
		m_spotFound = false;
		Vector3 centre = PointOf(trigger);
		int[] array = new int[8] { 0, 90, 180, 270, 45, 135, 225, 315 };
		foreach (int num in array)
		{
			Vector3 dir = Quaternion.Euler(0f, (float)num, 0f) * Vector3.forward;
			Collider interactionCollider = trigger.InteractionCollider;
			float num2;
			if (!((Object)(object)interactionCollider != (Object)null) || !interactionCollider.enabled)
			{
				num2 = 0f;
			}
			else
			{
				Bounds bounds = interactionCollider.bounds;
				Vector3 val = Flat(centre - ((Bounds)(ref bounds)).ClosestPoint(centre + dir * 10f));
				num2 = ((Vector3)(ref val)).magnitude;
			}
			float num3 = num2;
			if (Place(character, centre + dir * (num3 + distance), dir))
			{
				yield return Settle();
				if ((Object)(object)character.Interactable == (Object)(object)trigger)
				{
					m_spotFound = true;
					m_spotAway = dir;
					break;
				}
			}
		}
	}

	private IEnumerator HoldTest(Character character, InteractionTriggerBase trigger, float hold, string label)
	{
		Plugin.HoldTime.Value = hold;
		s_held = null;
		float pressed = Time.realtimeSinceStartup;
		character.OnInteractButtonDown();
		while ((Object)(object)s_held == (Object)null && Time.realtimeSinceStartup - pressed < 3f)
		{
			yield return null;
		}
		float elapsed = (((Object)(object)s_held != (Object)null) ? (s_heldAt - pressed) : float.NaN);
		yield return WaitRealtime(3f);
		character.OnInteractButtonUp();
		yield return WaitRealtime(0.5f);
		Vector3 val = ((Component)character).transform.position + Vector3.up * 0.9f;
		float num = Vector3.Angle(Flat(((Component)character).transform.forward), Flat(GatherPatches.NearestPoint(trigger, val) - val));
		TestHarness testHarness = this;
		bool ok = (Object)(object)s_held == (Object)(object)trigger && elapsed >= hold - 0.02f && elapsed <= hold + 0.15f;
		object[] obj = new object[4] { label, hold, null, null };
		object obj2;
		if (!((Object)(object)s_held != (Object)null))
		{
			obj2 = "nothing";
		}
		else
		{
			Item parentItem = s_held.ParentItem;
			obj2 = ((parentItem != null) ? parentItem.Name : null);
		}
		obj[2] = obj2;
		obj[3] = elapsed;
		testHarness.Check(ok, string.Format("{0}: hold time {1} s, hold done on {2} after {3:F2} s", obj));
		Check(num < 25f, $"{label}: faces the source after the hold ({num:F0} deg off)");
		Plugin.HoldTime.Value = (float)((ConfigEntryBase)Plugin.HoldTime).DefaultValue;
	}

	private static float MeasuredTo(Character character, Vector3 point)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: 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)
		Vector3 val = Flat(point - (((Component)character).transform.position + Vector3.up * 0.9f));
		return ((Vector3)(ref val)).magnitude;
	}

	private static string Describe(Character character)
	{
		InteractionTriggerBase interactable = character.Interactable;
		if ((Object)(object)interactable == (Object)null)
		{
			return "nothing";
		}
		if (!((Object)(object)interactable.ParentItem != (Object)null))
		{
			return ((Object)interactable).name;
		}
		return interactable.ParentItem.Name + " (" + interactable.ParentItem.UID + ")";
	}

	private static List<Vector3> Walkable(Vector3 from, Vector3 to)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: 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)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: 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_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: 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)
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		List<Vector3> list = new List<Vector3>();
		Vector3 val = Flat(to - from);
		int num = Mathf.CeilToInt(((Vector3)(ref val)).magnitude / 0.5f);
		RaycastHit val2 = default(RaycastHit);
		for (int i = 0; i <= num; i++)
		{
			if (!Physics.Raycast(Vector3.Lerp(from, to, (float)i / (float)num) + Vector3.up * 3f, Vector3.down, ref val2, 8f, Global.GeneralEnvironmentMask) || (list.Count > 0 && Mathf.Abs(((RaycastHit)(ref val2)).point.y - list[0].y) > 0.8f))
			{
				return null;
			}
			if (list.Count > 0 && (Physics.Linecast(list[list.Count - 1] + Vector3.up * 0.5f, ((RaycastHit)(ref val2)).point + Vector3.up * 0.5f, Global.GeneralEnvironmentMask) || Physics.Linecast(list[list.Count - 1] + Vector3.up * 1.6f, ((RaycastHit)(ref val2)).point + Vector3.up * 1.6f, Global.GeneralEnvironmentMask)))
			{
				return null;
			}
			list.Add(((RaycastHit)(ref val2)).point);
		}
		return list;
	}

	private static void Steer(Character character, Vector3 direction)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: 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_0026: Unknown result type (might be due to invalid IL or missing references)
		//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)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: 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_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		FieldRef<LocalCharacterControl, Transform> obj = s_horiControl;
		CharacterControl characterControl = character.CharacterControl;
		Transform val = obj.Invoke((LocalCharacterControl)(object)((characterControl is LocalCharacterControl) ? characterControl : null));
		Vector3 val2 = Flat(direction);
		Vector3 normalized = ((Vector3)(ref val2)).normalized;
		val2 = Flat(val.right);
		float num = Vector3.Dot(normalized, ((Vector3)(ref val2)).normalized);
		val2 = Flat(val.forward);
		s_move = new Vector2(num, Vector3.Dot(normalized, ((Vector3)(ref val2)).normalized));
	}

	private static IEnumerator WaitGame(float seconds)
	{
		float end = Time.time + seconds;
		while (Time.time < end)
		{
			yield return null;
		}
	}

	private IEnumerator Video(Character character, Gatherable target, bool vanilla)
	{
		InteractionTriggerBase trigger = TriggerOf(target);
		Vector3 bush = Centre(target);
		Vector3 away = Vector3.zero;
		Vector3 side = Vector3.zero;
		List<Vector3> list = null;
		int[] array = new int[8] { 0, 45, 90, 135, 180, 225, 270, 315 };
		foreach (int num in array)
		{
			float[] array2 = new float[2] { 1f, -1f };
			foreach (float num2 in array2)
			{
				Vector3 val = Quaternion.Euler(0f, (float)num, 0f) * Vector3.forward;
				Vector3 val2 = Vector3.Cross(Vector3.up, val) * num2;
				Vector3 val3 = bush + val * 2.3f;
				list = Walkable(val3 - val2 * 5f, val3 + val2 * 2f);
				if (list != null && Walkable(val3, bush + val * 0.9f) != null && !Physics.Linecast(val3 + val * 3f + Vector3.up * 2.2f, bush + Vector3.up * 0.6f, Global.GeneralEnvironmentMask))
				{
					away = val;
					side = val2;
					break;
				}
				list = null;
			}
			if (list != null)
			{
				break;
			}
		}
		if (list == null)
		{
			Check(ok: false, "video: no clear path past the bush");
			yield break;
		}
		Vector3 passPoint = bush + away * 2.3f;
		Log(string.Format("video {0}: {1} at {2}, path {3} -> {4} (side {5}), away {6}", vanilla ? "vanilla" : "mod", ((Item)target).Name, bush, list[0], list[list.Count - 1], side, away));
		GatherPatches.Vanilla = vanilla;
		Place(character, list[0], side);
		yield return WaitRealtime(1.5f);
		CharacterCamera camera = character.CharacterCamera;
		Quaternion yaw = Quaternion.LookRotation(-away + side * 0.5f);
		((Component)camera).transform.rotation = yaw;
		((Component)s_prompt.Invoke(character.CharacterUI)).transform.localScale = Vector3.one * 1.5f;
		s_cameraPitch.Invoke(camera).localRotation = Quaternion.Euler(18f, 0f, 0f);
		yield return WaitRealtime(1.5f);
		string text = Path.Combine(OutputDir(), "video_" + (vanilla ? "vanilla" : "mod"));
		if (Directory.Exists(text))
		{
			Directory.Delete(text, recursive: true);
		}
		Directory.CreateDirectory(text);
		Time.captureFramerate = 30;
		m_recording = true;
		((MonoBehaviour)this).StartCoroutine(Record(text));
		float start = Time.time;
		yield return WaitGame(0.7f);
		s_driving = true;
		Steer(character, side);
		while ((Object)(object)character.Interactable != (Object)(object)trigger && Vector3.Dot(((Component)character).transform.position - passPoint, side) < 0f && Time.time - start < 10f)
		{
			KeepCamera();
			yield return null;
		}
		bool onTheWay = (Object)(object)character.Interactable == (Object)(object)trigger;
		while ((Object)(object)character.Interactable != (Object)(object)trigger && Time.time - start < 12f)
		{
			Steer(character, bush - ((Component)character).transform.position);
			KeepCamera();
			yield return null;
		}
		s_move = Vector2.zero;
		float reached = Time.time - start;
		for (float t = 0f; t < 0.3f; t += Time.deltaTime)
		{
			KeepCamera();
			yield return null;
		}
		s_activated = (s_gathered = null);
		float pressed = Time.time;
		character.OnInteractButtonDown();
		while ((Object)(object)s_activated == (Object)null && Time.time - pressed < 4f)
		{
			KeepCamera();
			yield return null;
		}
		float held = Time.time - pressed;
		character.OnInteractButtonUp();
		while ((Object)(object)s_gathered == (Object)null && Time.time - pressed < 8f)
		{
			KeepCamera();
			yield return null;
		}
		float gathered = Time.time - start;
		for (float t = 0f; t < 2f; t += Time.deltaTime)
		{
			KeepCamera();
			yield return null;
		}
		s_driving = false;
		m_recording = false;
		yield return null;
		Time.captureFramerate = 0;
		GatherPatches.Vanilla = false;
		Log(string.Format("video {0}: prompt {1} at {2:F2} s, held {3:F2} s, gathered at {4:F2} s (from the start of the walk at 0.70 s)", vanilla ? "vanilla" : "mod", onTheWay ? "on the way" : "after turning", reached, held, gathered));
		TestHarness testHarness = this;
		bool ok = (Object)(object)s_gathered == (Object)(object)target;
		string[] obj = new string[5]
		{
			"video: ",
			((Item)target).Name,
			" gathered (",
			null,
			null
		};
		Gatherable obj2 = s_gathered;
		obj[3] = ((obj2 != null) ? ((Item)obj2).Name : null) ?? "nothing";
		obj[4] = ")";
		testHarness.Check(ok, string.Concat(obj));
		void KeepCamera()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			((Component)camera).transform.rotation = yaw;
		}
	}

	private IEnumerator Record(string dir)
	{
		int frame = 0;
		WaitForEndOfFrame endOfFrame = new WaitForEndOfFrame();
		while (m_recording)
		{
			yield return endOfFrame;
			Texture2D val = ScreenCapture.CaptureScreenshotAsTexture();
			File.WriteAllBytes(Path.Combine(dir, $"frame_{frame++:D4}.jpg"), ImageConversion.EncodeToJPG(val, 92));
			Object.Destroy((Object)(object)val);
		}
		Log($"video: {frame} frames in {dir}");
	}

	private IEnumerator Run()
	{
		string scene = Arg("--gathering-scene");
		float deadline = Time.realtimeSinceStartup + 900f;
		Character character;
		Scene activeScene;
		while (true)
		{
			if (Time.realtimeSinceStartup > deadline)
			{
				Plugin.Log.LogError((object)"gathering-test: FAILED not in the scene after 900 s");
				yield break;
			}
			try
			{
				CharacterManager instance = CharacterManager.Instance;
				character = ((instance != null) ? instance.GetFirstLocalCharacter() : null);
			}
			catch (NullReferenceException)
			{
				character = null;
			}
			NetworkLevelLoader instance2 = NetworkLevelLoader.Instance;
			if ((Object)(object)character != (Object)null && (Object)(object)instance2 != (Object)null && instance2.IsOverallLoadingDone && !instance2.IsGameplayLoading)
			{
				if (scene == null)
				{
					break;
				}
				activeScene = SceneManager.GetActiveScene();
				if (((Scene)(ref activeScene)).name == scene)
				{
					break;
				}
			}
			yield return WaitRealtime(1f);
		}
		yield return WaitRealtime(3f);
		MenuManager.Instance.MasterCanvas.alpha = 1f;
		MenuManager.Instance.MasterCanvas.interactable = true;
		object[] array = new object[4];
		activeScene = SceneManager.GetActiveScene();
		array[0] = ((Scene)(ref activeScene)).name;
		array[1] = Plugin.Distance.Value;
		array[2] = Plugin.Angle.Value;
		array[3] = Plugin.HoldTime.Value;
		Log(string.Format("in {0}, settings distance {1}, angle {2}, hold {3}", array));
		Gatherable[] array2 = Object.FindObjectsOfType<Gatherable>();
		List<Gatherable> list = array2.Where(Usable).ToList();
		Log($"{array2.Length} gatherables, {list.Count} usable without tool: " + string.Join(", ", from val4 in list
			group val4 by ((Item)val4).Name into grp
			select $"{grp.Key} x{grp.Count()}"));
		List<(Gatherable g, Vector3 at)> sources = new List<(Gatherable, Vector3)>();
		foreach (Gatherable item2 in list)
		{
			CentralGatherable central = (CentralGatherable)(object)((item2 is CentralGatherable) ? item2 : null);
			if (central != null)
			{
				sources.AddRange(central.AccessPoints.Where((CentralGatherableAccessPoint ap) => (Object)(object)ap != (Object)null && (Object)(object)ap.InteractionCollider != (Object)null).Select(delegate(CentralGatherableAccessPoint ap)
				{
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_0014: Unknown result type (might be due to invalid IL or missing references)
					CentralGatherable item = central;
					Bounds bounds2 = ap.InteractionCollider.bounds;
					return ((Gatherable)(object)item, center: ((Bounds)(ref bounds2)).center);
				}));
			}
			else
			{
				sources.Add((item2, Centre(item2)));
			}
		}
		Log($"{sources.Count} sources ({sources.Count(((Gatherable g, Vector3 at) src) => src.g is CentralGatherable)} dead wood access points)");
		Gatherable target = null;
		Vector3 away = Vector3.zero;
		foreach (Gatherable g in from val4 in list
			where !(val4 is CentralGatherable)
			orderby (!((Item)val4).Name.Contains("Gaberry")) ? 1 : 0
			select val4)
		{
			int[] array3 = new int[8] { 0, 90, 180, 270, 45, 135, 225, 315 };
			foreach (int num2 in array3)
			{
				Vector3 dir = Quaternion.Euler(0f, (float)num2, 0f) * Vector3.forward;
				if (Place(character, Centre(g) + dir * 1.5f, dir))
				{
					yield return Settle();
					if ((Object)(object)character.Interactable == (Object)(object)TriggerOf(g))
					{
						target = g;
						away = dir;
						break;
					}
				}
			}
			if ((Object)(object)target != (Object)null)
			{
				break;
			}
		}
		if ((Object)(object)target == (Object)null)
		{
			Plugin.Log.LogError((object)"gathering-test: FAILED no gatherable detected from behind at 1.5 m");
			yield break;
		}
		InteractionTriggerBase targetTrigger = TriggerOf(target);
		object[] obj = new object[5]
		{
			((Item)target).Name,
			((Item)target).UID,
			Centre(target),
			((object)targetTrigger.InteractionCollider)?.GetType().Name,
			null
		};
		Collider interactionCollider = targetTrigger.InteractionCollider;
		Vector3? obj2;
		Bounds bounds;
		if (interactionCollider == null)
		{
			obj2 = null;
		}
		else
		{
			bounds = interactionCollider.bounds;
			obj2 = ((Bounds)(ref bounds)).size;
		}
		obj[4] = obj2;
		Log(string.Format("target {0} {1} at {2}, collider {3} {4}, ", obj) + $"priority {targetTrigger.DetectionPriority}, basic {s_activator.Invoke(targetTrigger).CanDoBasic}");
		float[] array4;
		if (UnityEngineExtensions.Contains<string>(Environment.GetCommandLineArgs(), "--gathering-icon"))
		{
			int num = 0;
			Transform prompt = ((Component)s_prompt.Invoke(character.CharacterUI)).transform;
			array4 = new float[2] { 1f, 2f };
			foreach (float scale in array4)
			{
				float[] array5 = new float[2] { 2.4f, 2.8f };
				foreach (float back in array5)
				{
					float[] array6 = new float[2] { 12f, 18f };
					foreach (float pitch in array6)
					{
						prompt.localScale = Vector3.one * scale;
						Vector3 dir = -away;
						Place(character, Centre(target) - dir * back, dir);
						yield return WaitRealtime(1f);
						CharacterCamera characterCamera = character.CharacterCamera;
						((Component)characterCamera).transform.rotation = Quaternion.LookRotation(dir);
						s_cameraPitch.Invoke(characterCamera).localRotation = Quaternion.Euler(pitch, 0f, 0f);
						yield return WaitRealtime(2f);
						Log($"icon shot {num}: {back} m, pitch {pitch}, prompt x{scale}, active = {Describe(character)}, hold '{character.HoldText}'");
						yield return (object)new WaitForEndOfFrame();
						Capture($"icon_{num++}.png");
					}
				}
			}
			Plugin.Log.LogMessage((object)"gathering-test: done");
			yield break;
		}
		string text = Arg("--gathering-video");
		if (text != null)
		{
			yield return Video(character, target, text == "vanilla");
			Plugin.Log.LogMessage((object)((m_failures == 0) ? "gathering-test: done" : $"gathering-test: FAILED {m_failures} check(s)"));
			yield break;
		}
		Vector3 val = Centre(target);
		bounds = targetTrigger.InteractionCollider.bounds;
		Vector3 val2 = Flat(val - ((Bounds)(ref bounds)).ClosestPoint(Centre(target) + away * 10f));
		float extent = Mathf.Max(0f, ((Vector3)(ref val2)).magnitude);
		array4 = new float[2] { 2.5f, 3.4f };
		foreach (float num6 in array4)
		{
			Place(character, Centre(target) + away * (extent + num6), away);
			yield return Settle();
			float num7 = Measured(character, target);
			bool flag = num7 <= Plugin.Distance.Value;
			Check((Object)(object)character.Interactable == (Object)(object)targetTrigger == flag, string.Format("behind at {0:F2} m (limit {1}): {2}expected, active = {3}", num7, Plugin.Distance.Value, flag ? "" : "not ", Describe(character)));
		}
		Place(character, Centre(target) + away * (extent + 1.5f), away);
		Plugin.Angle.Value = 90f;
		yield return Settle();
		Check((Object)(object)character.Interactable != (Object)(object)targetTrigger, "behind with angle 90: not expected, active = " + Describe(character));
		Place(character, Centre(target) + away * (extent + 1.5f), -away);
		yield return Settle();
		Check((Object)(object)character.Interactable == (Object)(object)targetTrigger, "in front with angle 90: expected, active = " + Describe(character));
		Plugin.Angle.Value = 180f;
		(Gatherable target, Vector3) self = (target: target, Centre(target));
		(Gatherable g, Vector3 at) first = default((Gatherable, Vector3));
		(Gatherable g, Vector3 at) pair = default((Gatherable, Vector3));
		float num8 = float.PositiveInfinity;
		foreach (var x in sources.Where(((Gatherable g, Vector3 at) src) => !(src.g is CentralGatherable)))
		{
			foreach (var item3 in sources.Where(((Gatherable g, Vector3 at) src) => (Object)(object)src.g != (Object)(object)x.g))
			{
				val2 = Flat(item3.at - x.at);
				float magnitude = ((Vector3)(ref val2)).magnitude;
				if (magnitude >= 2f && magnitude <= 9f && Mathf.Abs(magnitude - 4f) < Mathf.Abs(num8 - 4f))
				{
					first = x;
					pair = item3;
					num8 = magnitude;
				}
			}
		}
		if ((Object)(object)pair.g == (Object)null)
		{
			Log("no two gatherables 2 to 9 m apart: nearest test skipped");
		}
		else
		{
			Plugin.Distance.Value = 7f;
			Log($"pair {((Item)first.g).Name} at {first.at} and {((Item)pair.g).Name} at {pair.at}, {num8:F2} m apart, distance set to 7");
			((Gatherable g, Vector3 at), (Gatherable g, Vector3 at), float)[] array7 = new((Gatherable, Vector3), (Gatherable, Vector3), float)[2]
			{
				(first, pair, 0.35f),
				(pair, first, 0.65f)
			};
			for (int num = 0; num < array7.Length; num++)
			{
				(Gatherable g, Vector3 at) near;
				(Gatherable g, Vector3 at) far;
				float num9;
				(near, far, num9) = array7[num];
				Place(character, Vector3.Lerp(first.at, pair.at, num9), far.at - near.at);
				yield return Settle();
				Check((Object)(object)character.Interactable == (Object)(object)TriggerOf(near.g), $"nearest {((Item)near.g).Name} at {MeasuredTo(character, near.at):F2} m (behind) vs {((Item)far.g).Name} at {MeasuredTo(character, far.at):F2} m (in front), active = {Describe(character)}");
			}
			Plugin.Distance.Value = (float)((ConfigEntryBase)Plugin.Distance).DefaultValue;
		}
		Plugin.HoldTime.Value = 1f;
		Place(character, Centre(target) + away * (extent + 1.5f), away);
		yield return Settle();
		s_gathered = null;
		character.OnInteractButtonDown();
		yield return WaitRealtime(0.5f);
		character.OnInteractButtonUp();
		yield return WaitRealtime(1f);
		TestHarness testHarness = this;
		bool ok = (Object)(object)s_gathered == (Object)null;
		Gatherable obj3 = s_gathered;
		testHarness.Check(ok, "hold time 1 s, released at 0.5 s: no gather (gathered " + (((obj3 != null) ? ((Item)obj3).Name : null) ?? "nothing") + ")");
		array4 = new float[2] { 0.2f, 0f };
		int num3;
		foreach (float scale in array4)
		{
			Gatherable g;
			Vector3 dir;
			if (scale > 0f)
			{
				(g, dir) = self;
			}
			else
			{
				(g, dir) = (from src in sources
					where (Object)(object)src.g != (Object)(object)target && Usable(src.g)
					orderby (!(src.g is CentralGatherable)) ? 1 : 0
					select src).ThenBy(delegate((Gatherable g, Vector3 at) src)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_001b: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val4 = Flat(src.at - self.Item2);
					return ((Vector3)(ref val4)).magnitude;
				}).FirstOrDefault();
			}
			if ((Object)(object)g == (Object)null)
			{
				Log("no other usable gatherable: hold 0 test skipped");
				break;
			}
			Plugin.HoldTime.Value = scale;
			if (!(scale > 0f))
			{
				_ = Vector3.forward;
			}
			bool placed = false;
			int[] array3 = new int[4] { 0, 90, 180, 270 };
			foreach (int num10 in array3)
			{
				Vector3 val3 = Quaternion.Euler(0f, (float)num10, 0f) * ((scale > 0f) ? away : Vector3.forward);
				if (Place(character, dir + val3 * 1.5f, val3))
				{
					yield return Settle();
					if ((Object)(object)character.Interactable == (Object)(object)TriggerOf(g))
					{
						placed = true;
						break;
					}
				}
			}
			if (!placed)
			{
				Check(ok: false, $"hold {scale}: {((Item)g).Name} not detected from behind");
				continue;
			}
			num3 = character.Inventory.Pouch.ItemCount;
			s_gathered = (s_activated = null);
			float back = Time.realtimeSinceStartup;
			character.OnInteractButtonDown();
			while ((Object)(object)s_gathered == (Object)null && Time.realtimeSinceStartup - back < 3f)
			{
				yield return null;
			}
			yield return WaitRealtime(0.5f);
			float pitch = (((Object)(object)s_activated != (Object)null) ? (s_activatedAt - back) : float.NaN);
			float animated = (((Object)(object)s_gathered != (Object)null) ? (s_gatheredAt - back) : float.NaN);
			yield return WaitRealtime(2.5f);
			character.OnInteractButtonUp();
			yield return WaitRealtime(0.5f);
			float num11 = Vector3.Angle(Flat(((Component)character).transform.forward), Flat(dir - ((Component)character).transform.position));
			TestHarness testHarness2 = this;
			bool ok2 = (Object)(object)s_activated == (Object)(object)g && (Object)(object)s_gathered == (Object)(object)g && pitch >= scale - 0.02f && pitch <= scale + 0.15f;
			object[] obj4 = new object[4] { scale, null, null, null };
			Gatherable obj5 = s_activated;
			obj4[1] = ((obj5 != null) ? ((Item)obj5).Name : null) ?? "nothing";
			obj4[2] = pitch;
			obj4[3] = animated;
			testHarness2.Check(ok2, string.Format("hold time {0} s: hold done on {1} after {2:F2} s, items after {3:F2} s", obj4));
			Check(num11 < 25f, $"hold time {scale} s: faces {((Item)g).Name} at {dir} after gathering ({num11:F0} deg off)");
			Log($"inventory {num3} -> {character.Inventory.Pouch.ItemCount} items");
		}
		Dictionary<SourceKind, int> tools = new Dictionary<SourceKind, int>
		{
			[SourceKind.Mining] = 2120050,
			[SourceKind.Fishing] = 2130130
		};
		List<Item> items = Resources.FindObjectsOfTypeAll<Item>().Where(delegate(Item i)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			Scene scene2 = ((Component)i).gameObject.scene;
			return ((Scene)(ref scene2)).IsValid() && (i is Gatherable || i is TreasureChest);
		}).ToList();
		(SourceKind, ConfigEntry<bool>)[] array8 = new(SourceKind, ConfigEntry<bool>)[3]
		{
			(SourceKind.Mining, Plugin.Mining),
			(SourceKind.Fishing, Plugin.Fishing),
			(SourceKind.Chest, Plugin.Chests)
		};
		for (int num = 0; num < array8.Length; num++)
		{
			(SourceKind, ConfigEntry<bool>) tuple4 = array8[num];
			SourceKind kind = tuple4.Item1;
			ConfigEntry<bool> option = tuple4.Item2;
			Vector3 here = ((Component)character).transform.position;
			List<InteractionTriggerBase> list2 = (from i in items
				select s_itemTrigger.Invoke(i) into val4
				where (Object)(object)val4 != (Object)null && GatherPatches.KindOf(val4) == kind
				orderby (kind != SourceKind.Chest || !((Object)(object)s_activator.Invoke(val4) != (Object)null) || s_activator.Invoke(val4).CanDoBasic) ? 1 : 0
				select val4).ThenBy(delegate(InteractionTriggerBase val5)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val4 = ((Component)val5).transform.position - here;
				return ((Vector3)(ref val4)).sqrMagnitude;
			}).ToList();
			Log($"{kind}: {list2.Count} sources: " + string.Join(", ", from val4 in list2
				group val4 by val4.ParentItem.Name into grp
				select $"{grp.Key} x{grp.Count()}"));
			InteractionTriggerBase source = null;
			foreach (InteractionTriggerBase t in list2.Take(10))
			{
				if (!((Behaviour)t.ParentItem).isActiveAndEnabled || !t.CanBeTriggered)
				{
					Place(character, PointOf(t) + Vector3.forward * 3f, Vector3.forward);
					yield return WaitRealtime(3f);
					if (!((Behaviour)t.ParentItem).isActiveAndEnabled || !t.CanBeTriggered)
					{
						continue;
					}
				}
				yield return FindSpot(character, t, 1.6f);
				if (m_spotFound)
				{
					source = t;
					break;
				}
			}
			if ((Object)(object)source == (Object)null)
			{
				Check(ok: false, $"{kind}: no source detected from behind at 1.6 m");
				continue;
			}
			EventActivator activator = s_activator.Invoke(source);
			Log($"{kind}: {source.ParentItem.Name} {source.ParentItem.UID} at {PointOf(source)}, basic {activator.CanDoBasic}, hold {activator.HasHoldInteraction} ({source.HoldActivationTime} s vanilla)");
			Check(ok: true, $"{kind}: {source.ParentItem.Name} detected from behind with the option on");
			option.Value = false;
			yield return Settle();
			Check((Object)(object)character.Interactable != (Object)(object)source, $"{kind}: not detected from behind with the option off, active = {Describe(character)}");
			option.Value = true;
			yield return Settle();
			Check((Object)(object)character.Interactable == (Object)(object)source, $"{kind}: detected again with the option back on, active = {Describe(character)}");
			if (tools.TryGetValue(kind, out num3))
			{
				if ((Object)(object)character.Inventory.GetCompatibleGatherableTool(num3) == (Object)null)
				{
					character.Inventory.ReceiveItemReward(num3, 1, false);
				}
				yield return WaitRealtime(0.5f);
				object arg = kind;
				Item compatibleGatherableTool = character.Inventory.GetCompatibleGatherableTool(num3);
				Log(string.Format("{0}: tool {1}", arg, ((compatibleGatherableTool != null) ? compatibleGatherableTool.Name : null) ?? "missing"));
			}
			if (activator.HasHoldInteraction && activator.CanDoHold)
			{
				yield return HoldTest(character, source, 0.2f, kind.ToString());
			}
			else
			{
				Log($"{kind}: opens on press, no hold to test");
			}
			character.CharacterUI.CloseAllMenus((MenuScreens[])null);
			yield return WaitRealtime(0.5f);
		}
		Plugin.HoldTime.Value = (float)((ConfigEntryBase)Plugin.HoldTime).DefaultValue;
		Plugin.Log.LogMessage((object)((m_failures == 0) ? "gathering-test: done" : $"gathering-test: FAILED {m_failures} check(s)"));
	}
}