Decompiled source of AFK Skill Trainer v1.0.0

AfkTraining.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("AfkTraining")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+79f8b5813e354e18e173f3f0c1c576cec2c58265")]
[assembly: AssemblyProduct("AfkTraining")]
[assembly: AssemblyTitle("AfkTraining")]
[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 AfkTraining
{
	internal static class ControlsPatch
	{
		private static readonly string[] ExpectedNames = new string[7] { "movedir", "attack", "attackHold", "secondaryAttack", "secondaryAttackHold", "block", "blockHold" };

		internal static Trainer Trainer;

		internal static bool IsHooked { get; private set; }

		internal static bool CanJump { get; private set; }

		public static void Apply(Harmony harmony, ManualLogSource log)
		{
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Expected O, but got Unknown
			List<MethodInfo> list = (from m in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "SetControls"
				select m).ToList();
			MethodInfo methodInfo = list.FirstOrDefault((MethodInfo m) => HasExpectedTypes(m.GetParameters()));
			if (methodInfo == null)
			{
				log.LogError((object)("Couldn't find Player.SetControls(Vector3, bool, bool, bool, bool, bool, bool, ...), so training can't drive the character. Found: " + ((list.Count == 0) ? "nothing" : string.Join("; ", list.Select(Describe)))));
				return;
			}
			if (!(from p in methodInfo.GetParameters().Take(ExpectedNames.Length)
				select p.Name).SequenceEqual<string>(ExpectedNames))
			{
				log.LogWarning((object)("Player.SetControls has unexpected parameter names, so attack and block may be mixed up: " + Describe(methodInfo)));
			}
			ParameterInfo[] parameters = methodInfo.GetParameters();
			CanJump = parameters.Length > ExpectedNames.Length && parameters[ExpectedNames.Length].ParameterType == typeof(bool) && parameters[ExpectedNames.Length].Name == "jump";
			MethodInfo method = typeof(ControlsPatch).GetMethod(CanJump ? "PrefixWithJump" : "Prefix", BindingFlags.Static | BindingFlags.NonPublic);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			IsHooked = true;
			log.LogInfo((object)("Hooked " + Describe(methodInfo)));
		}

		private static void Prefix(Player __instance, ref Vector3 __0, ref bool __1, ref bool __2, ref bool __3, ref bool __4, ref bool __5, ref bool __6)
		{
			Override(__instance, ref __0, ref __1, ref __2, ref __3, ref __4, ref __5, ref __6);
		}

		private static void PrefixWithJump(Player __instance, ref Vector3 __0, ref bool __1, ref bool __2, ref bool __3, ref bool __4, ref bool __5, ref bool __6, ref bool __7)
		{
			if (Override(__instance, ref __0, ref __1, ref __2, ref __3, ref __4, ref __5, ref __6) && Trainer.Controls.Jump)
			{
				__7 = true;
			}
		}

		private static bool Override(Player player, ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold)
		{
			//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)
			if (Trainer == null || !Trainer.IsDriving || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (((Vector3)(ref movedir)).sqrMagnitude > 0.01f)
			{
				Trainer.OnManualMoveInput();
			}
			DesiredControls controls = Trainer.Controls;
			movedir = controls.LocalMove;
			attack = controls.Attack;
			attackHold = controls.Attack;
			secondaryAttack = false;
			secondaryAttackHold = false;
			block = controls.Block;
			blockHold = controls.Block;
			return true;
		}

		private static bool HasExpectedTypes(ParameterInfo[] parameters)
		{
			if (parameters.Length >= ExpectedNames.Length && parameters[0].ParameterType == typeof(Vector3))
			{
				return parameters.Skip(1).Take(ExpectedNames.Length - 1).All((ParameterInfo p) => p.ParameterType == typeof(bool));
			}
			return false;
		}

		private static string Describe(MethodInfo method)
		{
			IEnumerable<string> values = from p in method.GetParameters()
				select p.ParameterType.Name + " " + p.Name;
			return method.DeclaringType?.Name + "." + method.Name + "(" + string.Join(", ", values) + ")";
		}
	}
	internal static class DummyFinder
	{
		public static List<GameObject> FindAll(Player player, float radius)
		{
			//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_0046: Unknown result type (might be due to invalid IL or missing references)
			List<string> filters = Game.SplitList(AfkTrainingPlugin.DummyNameFilter.Value);
			if (filters.Count == 0)
			{
				return new List<GameObject>();
			}
			Vector3 origin = ((Component)player).transform.position;
			List<Character> list = new List<Character>();
			Character.GetCharactersInRange(origin, radius, list);
			return (from c in list
				where (Object)(object)c != (Object)null && !(c is Player) && Matches(((Component)c).gameObject, c.m_name, filters)
				orderby Vector3.Distance(origin, ((Component)c).transform.position)
				select ((Component)c).gameObject).ToList();
		}

		public static GameObject Find(Player player, float radius)
		{
			//IL_002a: 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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = Game.SplitList(AfkTrainingPlugin.DummyNameFilter.Value);
			if (list.Count == 0)
			{
				return null;
			}
			Vector3 origin = ((Component)player).transform.position;
			GameObject best = null;
			float bestDistance = float.MaxValue;
			List<Character> list2 = new List<Character>();
			Character.GetCharactersInRange(origin, radius, list2);
			foreach (Character item in list2)
			{
				if (!((Object)(object)item == (Object)null) && !(item is Player) && Matches(((Component)item).gameObject, item.m_name, list))
				{
					Consider(((Component)item).gameObject, ((Component)item).transform.position);
				}
			}
			if ((Object)(object)best != (Object)null)
			{
				return best;
			}
			Collider[] array = Physics.OverlapSphere(origin, radius);
			foreach (Collider val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ZNetView componentInParent = ((Component)val).GetComponentInParent<ZNetView>();
				GameObject val2 = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).gameObject : ((Component)((Component)val).transform.root).gameObject);
				if (!((Object)(object)val2 == (Object)(object)((Component)player).gameObject))
				{
					Piece component = val2.GetComponent<Piece>();
					if (Matches(val2, ((Object)(object)component != (Object)null) ? component.m_name : null, list))
					{
						Consider(val2, val.ClosestPoint(origin));
					}
				}
			}
			return best;
			void Consider(GameObject candidate, Vector3 point)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				float num = Vector3.Distance(origin, point);
				if (num <= radius && num < bestDistance)
				{
					best = candidate;
					bestDistance = num;
				}
			}
		}

		private static bool Matches(GameObject go, string token, List<string> filters)
		{
			string name = ((Object)go).name.ToLowerInvariant();
			string raw = token?.ToLowerInvariant() ?? "";
			string localized = ((token != null) ? Game.Localize(token).ToLowerInvariant() : "");
			return filters.Any((string f) => name.Contains(f) || raw.Contains(f) || localized.Contains(f));
		}
	}
	internal static class FoodPicker
	{
		public enum Result
		{
			NotHungry,
			Ate,
			OutOfFood,
			Failed
		}

		private const int FoodSlots = 3;

		private static int _mostEaten;

		public static void Reset()
		{
			_mostEaten = 0;
		}

		public static Result EatIfHungry(Player player, ManualLogSource log)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return Result.NotHungry;
			}
			int count = player.GetFoods().Count;
			_mostEaten = Math.Max(_mostEaten, count);
			List<string> blacklist = Game.SplitList(AfkTrainingPlugin.FoodBlacklist.Value);
			ItemData val = (from item in inventory.GetAllItems().Where(IsFood)
				where !IsBlacklisted(item, blacklist)
				where CanEat(player, item)
				orderby item.m_shared.m_foodStamina descending, item.m_shared.m_food descending
				select item).FirstOrDefault();
			if (val != null)
			{
				log.LogInfo((object)("Eating " + Game.Localize(val.m_shared.m_name)));
				player.UseItem(inventory, val, false);
				return Result.Ate;
			}
			if (count != 0 && count >= _mostEaten)
			{
				return Result.NotHungry;
			}
			return Result.OutOfFood;
		}

		public static bool IsFood(ItemData item)
		{
			if (item != null && item.m_shared != null)
			{
				if (!(item.m_shared.m_food > 0f) && !(item.m_shared.m_foodStamina > 0f))
				{
					return item.m_shared.m_foodEitr > 0f;
				}
				return true;
			}
			return false;
		}

		private static bool CanEat(Player player, ItemData item)
		{
			return Game.Guard("Player.CanEat", () => player.CanEat(item, false), delegate
			{
				List<Food> foods = player.GetFoods();
				return foods.Count < 3 && foods.All((Food f) => f.m_item.m_shared.m_name != item.m_shared.m_name);
			});
		}

		private static bool IsBlacklisted(ItemData item, List<string> blacklist)
		{
			if (blacklist.Count == 0)
			{
				return false;
			}
			return new string[3]
			{
				item.m_shared.m_name,
				Game.Localize(item.m_shared.m_name),
				((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null
			}.Any((string n) => n != null && blacklist.Contains(n.ToLowerInvariant()));
		}
	}
	internal static class Game
	{
		internal static ManualLogSource Log;

		private static readonly HashSet<string> Reported = new HashSet<string>();

		private static readonly HashSet<string> Broken = new HashSet<string>();

		private static readonly Dictionary<string, FieldInfo> Fields = new Dictionary<string, FieldInfo>();

		private static readonly Dictionary<string, MethodInfo> Methods = new Dictionary<string, MethodInfo>();

		public static bool TakesInput(Player player)
		{
			return Guard("Player.TakeInput", () => ((Character)player).TakeInput(), fallback: true);
		}

		public static void Message(Player player, MessageType type, string text)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player != (Object)null)
			{
				Guard("Character.Message", delegate
				{
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					((Character)player).Message(type, text, 0, (Sprite)null, false);
					return true;
				}, fallback: false);
			}
		}

		public static string Localize(string token)
		{
			return Guard("Localization.Localize", () => (Localization.instance == null) ? token : Localization.instance.Localize(token), token);
		}

		public static float Stamina(Player player)
		{
			return Guard("Player.GetStamina", () => player.GetStamina(), float.NaN);
		}

		public static float MaxStamina(Player player)
		{
			return Guard("Player.GetMaxStamina", () => player.GetMaxStamina(), float.NaN);
		}

		public static float Health(Player player)
		{
			return (Call(player, "GetHealth") as float?) ?? float.NaN;
		}

		public static float MaxHealth(Player player)
		{
			return (Call(player, "GetMaxHealth") as float?) ?? float.NaN;
		}

		public static ItemData CurrentWeapon(Player player)
		{
			return Guard("Humanoid.GetCurrentWeapon", (Func<ItemData>)(() => player.GetCurrentWeapon()), (ItemData)null);
		}

		public static bool InAttack(Player player)
		{
			return Guard("Humanoid.InAttack", () => player.InAttack(), fallback: false);
		}

		public static bool InPlaceMode(Player player)
		{
			return Guard("Player.InPlaceMode", () => player.InPlaceMode(), fallback: false);
		}

		public static bool IsBroken(ItemData weapon)
		{
			if (weapon != null)
			{
				return Guard("ItemData.m_durability", () => weapon.m_shared.m_useDurability && weapon.m_durability <= 0f, fallback: false);
			}
			return false;
		}

		public static bool CanParry(Player player, out string blocker)
		{
			blocker = "Your shield";
			object obj = Field(player, "m_leftItem");
			ItemData val = (ItemData)(((obj is ItemData) ? obj : null) ?? CurrentWeapon(player));
			if (val?.m_shared == null)
			{
				return true;
			}
			blocker = Localize(val.m_shared.m_name);
			if (Field(val.m_shared, "m_timedBlockBonus") is float num)
			{
				return num > 1f;
			}
			return true;
		}

		public static float SwingCost(ItemData weapon)
		{
			if (weapon != null)
			{
				return Guard("Attack.m_attackStamina", () => (weapon.m_shared.m_attack == null) ? 0f : weapon.m_shared.m_attack.m_attackStamina, 0f);
			}
			return 0f;
		}

		public static T Guard<T>(string what, Func<T> call, T fallback)
		{
			return Guard(what, call, () => fallback);
		}

		public static T Guard<T>(string what, Func<T> call, Func<T> fallback)
		{
			if (!Broken.Contains(what))
			{
				try
				{
					return call();
				}
				catch (Exception ex)
				{
					if (ex is MemberAccessException || ex is TypeLoadException || ex is InvalidProgramException)
					{
						Broken.Add(what);
					}
					Warn(what, $"{what} failed: {ex}");
				}
			}
			return fallback();
		}

		public static object Field(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			string key = type.FullName + "." + name;
			if (!Fields.TryGetValue(key, out var value))
			{
				Type type2 = type;
				while (type2 != null && value == null)
				{
					value = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					type2 = type2.BaseType;
				}
				Fields[key] = value;
			}
			return value?.GetValue(obj);
		}

		public static float Number(object obj, string name, float fallback)
		{
			object obj2 = Field(obj, name);
			if (obj2 is float)
			{
				return (float)obj2;
			}
			return fallback;
		}

		public static string Text(object obj, string name)
		{
			return Field(obj, name) as string;
		}

		public static object Call(object obj, string name)
		{
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			string text = type.FullName + "." + name;
			if (Broken.Contains(text))
			{
				return null;
			}
			if (!Methods.TryGetValue(text, out var value))
			{
				Type type2 = type;
				while (type2 != null && value == null)
				{
					value = type2.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
					type2 = type2.BaseType;
				}
				Methods[text] = value;
			}
			if (value == null)
			{
				return null;
			}
			try
			{
				return value.Invoke(obj, null);
			}
			catch (Exception arg)
			{
				Broken.Add(text);
				Warn(text, $"{type.Name}.{name} failed: {arg}");
				return null;
			}
		}

		public static void Warn(string key, string message)
		{
			if (Reported.Add(key))
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)message);
				}
			}
		}

		public static List<string> SplitList(string value)
		{
			return (from s in (value ?? "").Split(new char[1] { ',' })
				select s.Trim().ToLowerInvariant() into s
				where s.Length > 0
				select s).ToList();
		}
	}
	[BepInPlugin("austin.valheim.afktraining", "AFK Skill Trainer", "1.0.0")]
	public class AfkTrainingPlugin : BaseUnityPlugin
	{
		public const string Guid = "austin.valheim.afktraining";

		public const string ModName = "AFK Skill Trainer";

		public const string Version = "1.0.0";

		internal static ConfigEntry<KeyboardShortcut> ToggleKey;

		internal static ConfigEntry<KeyboardShortcut> ModeKey;

		internal static ConfigEntry<float> SearchRadius;

		internal static ConfigEntry<float> RangedSearchRadius;

		internal static ConfigEntry<string> DummyNameFilter;

		internal static ConfigEntry<float> EmptyStaminaPercent;

		internal static ConfigEntry<float> ResumeAtPercent;

		internal static ConfigEntry<float> DriftDistance;

		internal static ConfigEntry<float> MaxDistance;

		internal static ConfigEntry<float> DodgeMaxDistance;

		internal static ConfigEntry<bool> StopOnManualMove;

		internal static ConfigEntry<string> FoodBlacklist;

		internal static ConfigEntry<bool> DrinkStaminaPotions;

		private Trainer _trainer;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Expected O, but got Unknown
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "ToggleKey", new KeyboardShortcut((KeyCode)39, Array.Empty<KeyCode>()), "Starts and stops training.");
			ModeKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keys", "ModeKey", new KeyboardShortcut((KeyCode)59, Array.Empty<KeyCode>()), "Switches the training mode: attack, block, parry, dodge, then back to attack.");
			SearchRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Target", "SearchRadius", 4f, "How close (metres) the T.W.I.G. must be when you start. Training then walks you in close enough for the mode. Parry and dodge mode can also start further off, up to the 12 m a T.W.I.G. throws rocks from, and then stay there to train against its rocks.");
			RangedSearchRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Target", "RangedSearchRadius", 20f, "How close (metres) the T.W.I.G. must be when you start attack mode with a bow, crossbow or staff in your hands. Ranged weapons train from where you stand.");
			DummyNameFilter = ((BaseUnityPlugin)this).Config.Bind<string>("Target", "DummyNameFilter", "trainingdummy,t.w.i.g", "Comma-separated, case-insensitive text used to recognise the T.W.I.G. (its prefab is piece_TrainingDummy).");
			EmptyStaminaPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Stamina", "EmptyStaminaPercent", 5f, "Stamina counts as empty below this percent of max, or below the cost of your next swing.");
			ResumeAtPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Stamina", "ResumeAtPercent", 50f, "After running out, rest until stamina is back to this percent of max.");
			DriftDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "DriftDistance", 0.3f, "Walk back to the start spot when you drift further than this (metres).");
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "MaxDistance", 3f, "Stop training if you end up further than this from the start spot (metres).");
			DodgeMaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "DodgeMaxDistance", 6f, "In dodge mode, stop training if you end up further than this from the start spot (metres). Rolls carry you a few metres.");
			StopOnManualMove = ((BaseUnityPlugin)this).Config.Bind<bool>("Position", "StopOnManualMove", true, "Stop training when you press a movement key yourself.");
			FoodBlacklist = ((BaseUnityPlugin)this).Config.Bind<string>("Food", "FoodBlacklist", "", "Comma-separated food names or prefab names never to eat (for example: Honey).");
			DrinkStaminaPotions = ((BaseUnityPlugin)this).Config.Bind<bool>("Potions", "DrinkStaminaPotions", true, "When stamina runs out, drink the stamina potion worth the most stamina as soon as the game allows it. Without one, training rests as usual. Dodge mode always rests, since a roll costs little stamina.");
			Game.Log = ((BaseUnityPlugin)this).Logger;
			_trainer = new Trainer(((BaseUnityPlugin)this).Logger);
			ControlsPatch.Trainer = _trainer;
			_harmony = new Harmony("austin.valheim.afktraining");
			try
			{
				ControlsPatch.Apply(_harmony, ((BaseUnityPlugin)this).Logger);
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Could not hook Player.SetControls, so the mod cannot drive the character: {arg}");
			}
			try
			{
				SwingTimer.Apply(_harmony, ((BaseUnityPlugin)this).Logger);
			}
			catch (Exception arg2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not watch the T.W.I.G.'s swings, so parry mode will hold block and dodge mode will roll on a steady beat: {arg2}");
			}
			try
			{
				Roll.Find(((BaseUnityPlugin)this).Logger);
			}
			catch (Exception arg3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not set up rolling for dodge mode: {arg3}");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Press {2} next to a T.W.I.G. to start.", "AFK Skill Trainer", "1.0.0", ToggleKey.Value));
		}

		private void Update()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && Game.TakesInput(localPlayer))
			{
				KeyboardShortcut value = ToggleKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					_trainer.Toggle();
				}
				value = ModeKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					_trainer.SwitchMode();
				}
			}
			_trainer.Tick(Time.deltaTime);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PotionPicker
	{
		public enum Result
		{
			NoPotion,
			Drank,
			Failed
		}

		private const float BaseStaminaRegen = 6f;

		private const float RefusedRetrySeconds = 60f;

		private static readonly Dictionary<string, float> RefusedUntil = new Dictionary<string, float>();

		private static bool _listed;

		public static void Reset()
		{
			RefusedUntil.Clear();
			_listed = false;
		}

		public static Result DrinkBest(Player player, ManualLogSource log)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return Result.NoPotion;
			}
			var list = (from item in inventory.GetAllItems()
				where item != null && item.m_shared != null && !FoodPicker.IsFood(item) && (Object)(object)item.m_shared.m_consumeStatusEffect != (Object)null
				select new
				{
					Item = item,
					Effect = item.m_shared.m_consumeStatusEffect
				} into p
				select new
				{
					Item = p.Item,
					Effect = p.Effect,
					Stamina = StaminaValue(p.Effect)
				} into p
				orderby p.Stamina descending
				select p).ToList();
			if (!_listed)
			{
				_listed = true;
				log.LogInfo((object)((list.Count == 0) ? "No potions in your bag" : ("Potions in your bag, by stamina: " + string.Join(", ", list.Select(p => $"{Game.Localize(p.Item.m_shared.m_name)} {p.Stamina:0}").Distinct().ToArray()))));
			}
			List<StatusEffect> active = ActiveEffects(player);
			var anon = list.FirstOrDefault(p => p.Stamina > 0f && !IsActive(active, p.Effect) && !WasRefused(p.Item));
			if (anon == null)
			{
				return Result.NoPotion;
			}
			string name = anon.Item.m_shared.m_name;
			int num = inventory.CountItems(name, -1, false);
			player.UseItem(inventory, anon.Item, false);
			if (inventory.CountItems(name, -1, false) >= num)
			{
				RefusedUntil[name] = Time.time + 60f;
				log.LogInfo((object)("The game wouldn't let you drink " + Game.Localize(name) + " yet"));
				return Result.NoPotion;
			}
			log.LogInfo((object)$"Drank {Game.Localize(name)} (about {anon.Stamina:0} stamina)");
			return Result.Drank;
		}

		private static float StaminaValue(StatusEffect effect)
		{
			float num = Game.Number(effect, "m_staminaUpFront", 0f) + Game.Number(effect, "m_staminaOverTime", 0f);
			float num2 = Game.Number(effect, "m_staminaRegenMultiplier", 1f);
			if (num2 > 1f)
			{
				num += (num2 - 1f) * 6f * Game.Number(effect, "m_ttl", 0f);
			}
			return num;
		}

		private static bool IsActive(List<StatusEffect> active, StatusEffect effect)
		{
			if (active == null)
			{
				return false;
			}
			string name = effect.m_name;
			string category = Game.Text(effect, "m_category");
			return active.Any((StatusEffect a) => (Object)(object)a != (Object)null && ((!string.IsNullOrEmpty(name) && a.m_name == name) || (!string.IsNullOrEmpty(category) && Game.Text(a, "m_category") == category)));
		}

		private static List<StatusEffect> ActiveEffects(Player player)
		{
			return Game.Guard("SEMan.GetStatusEffects", (Func<List<StatusEffect>>)(() => ((Character)player).GetSEMan().GetStatusEffects()), (List<StatusEffect>)null);
		}

		private static bool WasRefused(ItemData item)
		{
			if (RefusedUntil.TryGetValue(item.m_shared.m_name, out var value))
			{
				return Time.time < value;
			}
			return false;
		}
	}
	internal static class Reach
	{
		private class SwingRange
		{
			public string Kind;

			public float Range;

			public float MinRange;

			public float Reach;

			public bool Thrown;
		}

		private const float WeaponReachShare = 0.75f;

		private const float SwingRangeShare = 0.7f;

		private const float DefaultStandWithin = 1.2f;

		private const float DefaultDummyRadius = 0.4f;

		private const float DefaultBowDrawSeconds = 2.5f;

		public const float DefaultRockRange = 12f;

		public static bool IsRanged(ItemData weapon)
		{
			return (Game.Field(weapon?.m_shared?.m_attack, "m_attackType")?.ToString() ?? "").Contains("Projectile");
		}

		public static bool IsBow(ItemData weapon)
		{
			return Game.Field(weapon?.m_shared?.m_attack, "m_bowDraw") as bool? == true;
		}

		public static float BowDrawSeconds(ItemData weapon)
		{
			float num = Game.Number(weapon?.m_shared?.m_attack, "m_drawDurationMin", 0f);
			if (!(num > 0f))
			{
				return 2.5f;
			}
			return num;
		}

		public static float? StandWithin(TrainingMode mode, Player player, GameObject dummy, out string why)
		{
			if (mode == TrainingMode.Attack)
			{
				ItemData val = Game.CurrentWeapon(player);
				Attack val2 = val?.m_shared?.m_attack;
				string text = ((val?.m_shared != null) ? Game.Localize(val.m_shared.m_name) : "Your weapon");
				if (val2 == null)
				{
					why = "couldn't read your weapon's reach";
					return null;
				}
				if (IsRanged(val))
				{
					why = text + " is a ranged weapon";
					return null;
				}
				float num = Game.Number(val2, "m_attackRange", 0f);
				if (num <= 0f)
				{
					why = "couldn't read the reach of " + text;
					return null;
				}
				why = $"{text} reaches {num:0.0} m";
				return Radius(dummy) + 0.75f * num;
			}
			float num2 = (from s in SwingRanges(dummy)
				where !s.Thrown
				select s.Range).DefaultIfEmpty(0f).Min();
			if (num2 <= 0f)
			{
				why = "couldn't read the T.W.I.G.'s swings";
				return 1.2f;
			}
			why = $"its shortest swing range is {num2:0.0} m";
			return 0.7f * num2;
		}

		public static float RockRange(GameObject dummy)
		{
			List<SwingRange> list = SwingRanges(dummy);
			if (list.Count != 0)
			{
				return (from s in list
					where s.Thrown
					select s.Range).DefaultIfEmpty(0f).Max();
			}
			return 12f;
		}

		public static string DescribeSwings(GameObject dummy)
		{
			List<SwingRange> list = SwingRanges(dummy);
			if (list.Count != 0)
			{
				return string.Join("; ", list.Select(Describe).ToArray());
			}
			return "couldn't read them";
		}

		private static string Describe(SwingRange swing)
		{
			string text = swing.Kind;
			if (swing.MinRange > 0f)
			{
				text += $", only from {swing.MinRange:0.#} m";
			}
			if (swing.Thrown)
			{
				text += ", thrown";
			}
			else if (swing.Reach > 0f)
			{
				text += $", reaches {swing.Reach:0.#} m";
			}
			return text;
		}

		private static List<SwingRange> SwingRanges(GameObject dummy)
		{
			List<SwingRange> list = new List<SwingRange>();
			Humanoid humanoid = (((Object)(object)dummy != (Object)null) ? dummy.GetComponent<Humanoid>() : null);
			if ((Object)(object)humanoid == (Object)null)
			{
				return list;
			}
			List<ItemData> list2 = Game.Guard("T.W.I.G. inventory", (Func<List<ItemData>>)delegate
			{
				Inventory inventory = humanoid.GetInventory();
				return (inventory == null) ? null : inventory.GetAllItems();
			}, (List<ItemData>)null);
			if (list2 == null)
			{
				return list;
			}
			foreach (ItemData item in list2)
			{
				if (item?.m_shared != null && Game.Call(item, "IsWeapon") as bool? != false)
				{
					float num = Game.Number(item.m_shared, "m_aiAttackRange", 0f);
					if (num > 0f)
					{
						list.Add(new SwingRange
						{
							Kind = SwingTimer.KindOf(item),
							Range = num,
							MinRange = Game.Number(item.m_shared, "m_aiAttackRangeMin", 0f),
							Reach = Game.Number(item.m_shared.m_attack, "m_attackRange", 0f),
							Thrown = SwingTimer.IsThrow(item)
						});
					}
				}
			}
			return list;
		}

		private static float Radius(GameObject dummy)
		{
			CapsuleCollider component = dummy.GetComponent<CapsuleCollider>();
			if (!((Object)(object)component != (Object)null) || !(component.radius > 0f))
			{
				return 0.4f;
			}
			return component.radius;
		}
	}
	internal static class Roll
	{
		private const float DefaultStaminaCost = 15f;

		private static MethodInfo _queue;

		private static MethodInfo _inDodge;

		private static MethodInfo _invulnerable;

		private static FieldInfo _staminaCost;

		public static bool CanRoll
		{
			get
			{
				if (!(_queue != null))
				{
					return ControlsPatch.CanJump;
				}
				return true;
			}
		}

		public static bool CanTellInvulnerable => _invulnerable != null;

		public static void Find(ManualLogSource log)
		{
			_queue = FindMethod("Dodge", typeof(void), typeof(Vector3));
			_inDodge = FindMethod("InDodge", typeof(bool));
			_invulnerable = FindMethod("IsDodgeInvincible", typeof(bool));
			FieldInfo field = typeof(Player).GetField("m_dodgeStaminaUsage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_staminaCost = ((field != null && field.FieldType == typeof(float)) ? field : null);
			if (_queue != null)
			{
				log.LogInfo((object)"Dodge mode rolls with Player.Dodge(Vector3)");
			}
			else if (ControlsPatch.CanJump)
			{
				log.LogInfo((object)"Couldn't find Player.Dodge(Vector3), so dodge mode rolls with block and jump");
			}
			else
			{
				log.LogWarning((object)"Couldn't find Player.Dodge(Vector3) or a jump control, so dodge mode can't roll");
			}
			if (_inDodge == null || _invulnerable == null)
			{
				log.LogWarning((object)"Couldn't find Player.InDodge and Player.IsDodgeInvincible, so dodge mode can't tell whether hits catch you while invulnerable and won't adjust its timing");
			}
			if (_staminaCost == null)
			{
				Game.Warn("m_dodgeStaminaUsage", $"Couldn't find Player.m_dodgeStaminaUsage, so dodge mode counts each roll as {15f:0} stamina");
			}
		}

		public static bool TryQueue(Player player, Vector3 direction)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (_queue == null)
			{
				return false;
			}
			try
			{
				_queue.Invoke(player, new object[1] { direction });
				return true;
			}
			catch (Exception arg)
			{
				Game.Warn("Player.Dodge", $"Player.Dodge failed, so dodge mode rolls with block and jump from now on: {arg}");
				_queue = null;
				return false;
			}
		}

		public static bool? InDodge(Player player)
		{
			return Ask(ref _inDodge, player);
		}

		public static bool? IsInvulnerable(Player player)
		{
			return Ask(ref _invulnerable, player);
		}

		public static float StaminaCost(Player player)
		{
			if (_staminaCost == null || (Object)(object)player == (Object)null)
			{
				return 15f;
			}
			return (float)_staminaCost.GetValue(player);
		}

		private static bool? Ask(ref MethodInfo method, Player player)
		{
			if (method == null || (Object)(object)player == (Object)null)
			{
				return null;
			}
			try
			{
				return (bool)method.Invoke(player, null);
			}
			catch (Exception arg)
			{
				Game.Warn(method.Name, $"Player.{method.Name} failed, so dodge mode stops checking it: {arg}");
				method = null;
				return null;
			}
		}

		private static MethodInfo FindMethod(string name, Type returns, params Type[] parameters)
		{
			Type type = typeof(Player);
			while (type != null)
			{
				MethodInfo method = type.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null);
				if (method != null)
				{
					if (!(method.ReturnType == returns))
					{
						return null;
					}
					return method;
				}
				type = type.BaseType;
			}
			return null;
		}
	}
	internal static class SkillLevels
	{
		public const float Max = 100f;

		public static string TrainedBy(TrainingMode mode, ItemData weapon)
		{
			switch (mode)
			{
			case TrainingMode.Block:
			case TrainingMode.Parry:
				return "Blocking";
			case TrainingMode.Dodge:
				return "Dodge";
			default:
				if (weapon?.m_shared == null)
				{
					return null;
				}
				return Game.Field(weapon.m_shared, "m_skillType")?.ToString();
			}
		}

		public static float? BaseLevel(Player player, string skill)
		{
			if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(skill))
			{
				return null;
			}
			object obj = Game.Call(player, "GetSkills") ?? Game.Field(player, "m_skills");
			IEnumerable enumerable = (Game.Call(obj, "GetSkillList") as IEnumerable) ?? (Game.Field(obj, "m_skillData") as IDictionary)?.Values;
			if (enumerable == null)
			{
				return null;
			}
			foreach (object item in enumerable)
			{
				if (Game.Field(Game.Field(item, "m_info"), "m_skill")?.ToString() == skill)
				{
					return Game.Field(item, "m_level") as float?;
				}
			}
			return 0f;
		}

		public static string DisplayName(string skill)
		{
			if (string.IsNullOrEmpty(skill))
			{
				return "your skill";
			}
			string text = Game.Localize("$skill_" + skill.ToLowerInvariant());
			if (!string.IsNullOrEmpty(text) && !text.StartsWith("[") && !text.StartsWith("$"))
			{
				return text;
			}
			return skill;
		}
	}
	internal static class SwingTimer
	{
		internal class Swing
		{
			public GameObject Source;

			public string Kind;

			public bool Thrown;

			public float Distance;

			public float Start;

			public float? ExpectedHit;

			public bool TimedToYou;

			public float? Struck;

			public float? Hit;

			public bool? InvulnerableWhenStruck;

			public bool? Blocking;

			public float? BlockTimer;

			public bool? Invulnerable;

			public bool? Dodgeable;

			public float Flight
			{
				get
				{
					if (!Thrown)
					{
						return 0.6f;
					}
					return 2f;
				}
			}

			public float MaxSeconds
			{
				get
				{
					if (!Thrown)
					{
						return 3f;
					}
					return 4f;
				}
			}

			public string Name
			{
				get
				{
					if (!Thrown)
					{
						return "swing " + Kind;
					}
					return $"rock {Kind} from {Distance:0.0} m";
				}
			}

			public bool IsOver(float now)
			{
				if (!Hit.HasValue && !(now > (Struck ?? float.PositiveInfinity) + Flight))
				{
					return now > Start + MaxSeconds;
				}
				return true;
			}
		}

		private class Timing
		{
			public float Delay;

			public float Distance;

			public bool ToYou;
		}

		public const float MaxSwingSeconds = 3f;

		public const float MaxThrowSeconds = 4f;

		public const float FlightSeconds = 0.6f;

		public const float RockFlightSeconds = 2f;

		private const float RockDistanceSlack = 0.5f;

		private const int RockSpotsKept = 8;

		private const float ReachSlack = 1f;

		private static readonly Dictionary<string, Timing> SwingTimings = new Dictionary<string, Timing>();

		private static readonly Dictionary<GameObject, Dictionary<string, List<Timing>>> RockTimings = new Dictionary<GameObject, Dictionary<string, List<Timing>>>();

		private static readonly HashSet<GameObject> Watched = new HashSet<GameObject>();

		private static readonly Dictionary<GameObject, Swing> Latest = new Dictionary<GameObject, Swing>();

		private static ManualLogSource _log;

		private static GameObject _trained;

		private static MethodInfo _getAttacker;

		public static Action<Swing> Landed;

		public static bool IsHooked { get; private set; }

		public static bool SeesHits { get; private set; }

		public static IEnumerable<Swing> Swings => Latest.Values;

		public static float LastSwingSeenAt { get; private set; } = float.NegativeInfinity;

		public static int PlayerSwings { get; private set; }

		public static int PlayerHits { get; private set; }

		public static void Apply(Harmony harmony, ManualLogSource log)
		{
			_log = log;
			MethodInfo methodInfo = FindMethod("StartAttack", typeof(bool));
			MethodInfo methodInfo2 = FindMethod("OnAttackTrigger", typeof(void));
			if (methodInfo == null || methodInfo2 == null)
			{
				log.LogWarning((object)"Couldn't find Humanoid.StartAttack and Humanoid.OnAttackTrigger, so the mod can't time the T.W.I.G.'s swings: parry mode will hold block and dodge mode will roll on a steady beat.");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, Patch("StartAttackPostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, Patch("AttackTriggerPostfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				IsHooked = true;
				log.LogInfo((object)("Timing the T.W.I.G.'s swings with " + Describe(methodInfo) + " and " + Describe(methodInfo2)));
			}
			try
			{
				WatchHits(harmony, log);
			}
			catch (Exception arg)
			{
				log.LogWarning((object)$"Could not watch hits reach you, so swings are timed to the T.W.I.G.'s hit frame: {arg}");
			}
		}

		private static void WatchHits(Harmony harmony, ManualLogSource log)
		{
			List<MethodInfo> list = (from m in typeof(Character).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "RPC_Damage" && m.ReturnType == typeof(void)
				where (from p in m.GetParameters()
					select p.ParameterType.Name).SequenceEqual(new string[2] { "Int64", "HitData" })
				select m).ToList();
			MethodInfo methodInfo = ((list.Count == 1) ? list[0].GetParameters()[1].ParameterType.GetMethod("GetAttacker", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null) : null);
			if (methodInfo == null || !typeof(Character).IsAssignableFrom(methodInfo.ReturnType))
			{
				log.LogWarning((object)"Couldn't find Character.RPC_Damage and HitData.GetAttacker, so swings are timed to the T.W.I.G.'s hit frame. Its thrown rock lands a little later, so parry mode blocks it instead of parrying it.");
				return;
			}
			_getAttacker = methodInfo;
			harmony.Patch((MethodBase)list[0], Patch("DamagePrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			SeesHits = true;
			log.LogInfo((object)("Seeing T.W.I.G. hits reach you with " + Describe(list[0])));
		}

		public static void Watch(GameObject trained)
		{
			_trained = trained;
			Watched.Clear();
			Latest.Clear();
			if ((Object)(object)trained != (Object)null)
			{
				Watched.Add(trained);
			}
		}

		public static List<GameObject> WatchAlso(IEnumerable<GameObject> others)
		{
			HashSet<GameObject> keep = new HashSet<GameObject>(others.Where((GameObject o) => (Object)(object)o != (Object)null));
			if ((Object)(object)_trained != (Object)null)
			{
				keep.Add(_trained);
			}
			foreach (GameObject item in Watched.Where((GameObject o) => !keep.Contains(o)).ToList())
			{
				Watched.Remove(item);
				Latest.Remove(item);
			}
			List<GameObject> list = keep.Where((GameObject o) => !Watched.Contains(o)).ToList();
			foreach (GameObject item2 in list)
			{
				Watched.Add(item2);
			}
			return list;
		}

		public static string KindOf(ItemData weapon)
		{
			SharedData val = weapon?.m_shared;
			if (val == null)
			{
				return "swing";
			}
			List<string> list = new List<string>();
			string text = Game.Text(val.m_attack, "m_attackAnimation");
			if (!string.IsNullOrEmpty(text))
			{
				list.Add(text);
			}
			float num = Game.Number(val, "m_aiAttackInterval", 0f);
			if (num > 0f)
			{
				list.Add($"every {num:0.#} s");
			}
			float num2 = Game.Number(val, "m_aiAttackRange", 0f);
			if (num2 > 0f)
			{
				list.Add($"within {num2:0.#} m");
			}
			string text2 = val.m_name ?? "swing";
			if (list.Count != 0)
			{
				return text2 + " (" + string.Join(", ", list.ToArray()) + ")";
			}
			return text2;
		}

		public static bool IsThrow(ItemData weapon)
		{
			if (!Reach.IsRanged(weapon))
			{
				return (Game.Text(weapon?.m_shared?.m_attack, "m_attackAnimation") ?? "").Contains("throw");
			}
			return true;
		}

		private static void StartAttackPostfix(Humanoid __instance, bool __result)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!__result || (Object)(object)_trained == (Object)null || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)__instance == (Object)(object)localPlayer)
				{
					PlayerSwings++;
					return;
				}
				GameObject gameObject = ((Component)__instance).gameObject;
				if (!Watched.Contains(gameObject))
				{
					return;
				}
				ItemData weapon = Game.Guard("T.W.I.G. weapon", (Func<ItemData>)(() => __instance.GetCurrentWeapon()), (ItemData)null);
				float num = (LastSwingSeenAt = Time.time);
				Swing obj = new Swing
				{
					Source = gameObject,
					Kind = Game.Guard("T.W.I.G. swing kind", () => KindOf(weapon), "swing"),
					Thrown = Game.Guard("T.W.I.G. throw", () => IsThrow(weapon), fallback: false)
				};
				float distance;
				if (!((Object)(object)localPlayer != (Object)null))
				{
					distance = 0f;
				}
				else
				{
					Vector3 val = Flat(gameObject.transform.position - ((Component)localPlayer).transform.position);
					distance = ((Vector3)(ref val)).magnitude;
				}
				obj.Distance = distance;
				obj.Start = num;
				Swing swing = obj;
				if (!swing.Thrown && (Object)(object)gameObject != (Object)(object)_trained)
				{
					float num2 = Game.Number(weapon?.m_shared?.m_attack, "m_attackRange", 0f);
					if (num2 > 0f && swing.Distance > num2 + 1f)
					{
						return;
					}
				}
				Timing timing = TimingOf(swing);
				if (timing != null)
				{
					swing.ExpectedHit = num + timing.Delay;
					swing.TimedToYou = timing.ToYou;
				}
				Latest[gameObject] = swing;
			}
			catch (Exception arg)
			{
				Game.Warn("StartAttackPostfix", $"Timing a T.W.I.G. swing failed: {arg}");
			}
		}

		private static void AttackTriggerPostfix(Character __instance)
		{
			try
			{
				if (!((Object)(object)__instance == (Object)null) && Latest.TryGetValue(((Component)__instance).gameObject, out var value) && !value.Struck.HasValue)
				{
					float time = Time.time;
					value.Struck = time;
					if (!value.Thrown)
					{
						value.InvulnerableWhenStruck = Roll.IsInvulnerable(Player.m_localPlayer);
					}
					Timing timing = TimingOf(value);
					if (timing == null || !timing.ToYou)
					{
						Learn(value, time, toYou: false);
					}
					if (!SeesHits && !value.Hit.HasValue)
					{
						Land(value, time);
					}
				}
			}
			catch (Exception arg)
			{
				Game.Warn("AttackTriggerPostfix", $"Timing a T.W.I.G. hit failed: {arg}");
			}
		}

		private static void DamagePrefix(Character __instance, object[] __args)
		{
			//IL_00e8: 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_00fd: 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)
			try
			{
				if (!SeesHits || (Object)(object)_trained == (Object)null || (Object)(object)__instance == (Object)null)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				bool flag = (Object)(object)__instance == (Object)(object)localPlayer;
				if (!flag && (Object)(object)((Component)__instance).gameObject != (Object)(object)_trained)
				{
					return;
				}
				object obj = __args[1];
				object? obj2 = _getAttacker.Invoke(obj, null);
				Character val = (Character)((obj2 is Character) ? obj2 : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				if (!flag)
				{
					if ((Object)(object)val == (Object)(object)localPlayer)
					{
						PlayerHits++;
					}
					return;
				}
				GameObject gameObject = ((Component)val).gameObject;
				if (Watched.Contains(gameObject))
				{
					float time = Time.time;
					if (!Latest.TryGetValue(gameObject, out var value) || value.IsOver(time))
					{
						Swing obj3 = new Swing
						{
							Source = gameObject,
							Kind = "(untimed)"
						};
						Vector3 val2 = Flat(gameObject.transform.position - ((Component)localPlayer).transform.position);
						obj3.Distance = ((Vector3)(ref val2)).magnitude;
						obj3.Start = time;
						value = obj3;
						ReadHit(value, localPlayer, obj);
						Land(value, time);
					}
					else
					{
						ReadHit(value, localPlayer, obj);
						Learn(value, time, toYou: true);
						Land(value, time);
					}
				}
			}
			catch (Exception arg)
			{
				SeesHits = false;
				Game.Warn("DamagePrefix", $"Seeing a T.W.I.G. hit reach you failed, so swings are timed to their hit frame from now on: {arg}");
			}
		}

		private static void ReadHit(Swing swing, Player player, object hit)
		{
			swing.Blocking = Game.Call(player, "IsBlocking") as bool?;
			swing.BlockTimer = Game.Field(player, "m_blockTimer") as float?;
			swing.Invulnerable = Roll.IsInvulnerable(player);
			swing.Dodgeable = Game.Field(hit, "m_dodgeable") as bool?;
		}

		private static Timing TimingOf(Swing swing)
		{
			if (!swing.Thrown)
			{
				if (!SwingTimings.TryGetValue(swing.Kind, out var value))
				{
					return null;
				}
				return value;
			}
			return (from t in RockTimingsOf(swing)
				where Math.Abs(t.Distance - swing.Distance) <= 0.5f
				orderby Math.Abs(t.Distance - swing.Distance)
				select t).FirstOrDefault();
		}

		private static List<Timing> RockTimingsOf(Swing swing)
		{
			if (!RockTimings.TryGetValue(swing.Source, out var value))
			{
				value = (RockTimings[swing.Source] = new Dictionary<string, List<Timing>>());
			}
			if (!value.TryGetValue(swing.Kind, out var value2))
			{
				value2 = (value[swing.Kind] = new List<Timing>());
			}
			return value2;
		}

		private static void Learn(Swing swing, float now, bool toYou)
		{
			float num = now - swing.Start;
			if (num <= 0.05f || num >= swing.MaxSeconds)
			{
				return;
			}
			Timing timing = TimingOf(swing);
			Timing timing2 = new Timing
			{
				Delay = num,
				Distance = swing.Distance,
				ToYou = toYou
			};
			if (swing.Thrown)
			{
				List<Timing> list = RockTimingsOf(swing);
				list.Remove(timing);
				list.Add(timing2);
				if (list.Count > 8)
				{
					list.RemoveAt(0);
				}
			}
			else
			{
				SwingTimings[swing.Kind] = timing2;
			}
			string text = string.Format("T.W.I.G. {0} {1} {2:0.00} s after it starts", swing.Name, toYou ? "reaches you" : (swing.Thrown ? "is let go" : "strikes"), num);
			if (timing == null || timing.ToYou != toYou)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)text);
				}
			}
			else if (Math.Abs(timing.Delay - num) > 0.05f)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"{text} (was {timing.Delay:0.00} s)");
				}
			}
		}

		private static void Land(Swing swing, float now)
		{
			swing.Hit = now;
			Landed?.Invoke(swing);
		}

		private static Vector3 Flat(Vector3 v)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			v.y = 0f;
			return v;
		}

		private static HarmonyMethod Patch(string name)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return new HarmonyMethod(typeof(SwingTimer).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic));
		}

		private static MethodInfo FindMethod(string name, Type returns)
		{
			List<MethodInfo> list = (from m in typeof(Humanoid).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == name && m.ReturnType == returns && !m.IsAbstract
				select m).ToList();
			if (list.Count != 1)
			{
				return null;
			}
			return list[0];
		}

		private static string Describe(MethodInfo method)
		{
			IEnumerable<string> values = from p in method.GetParameters()
				select p.ParameterType.Name + " " + p.Name;
			return method.DeclaringType?.Name + "." + method.Name + "(" + string.Join(", ", values) + ")";
		}
	}
	internal enum TrainingMode
	{
		Attack,
		Block,
		Parry,
		Dodge
	}
	internal struct DesiredControls
	{
		public Vector3 LocalMove;

		public bool Attack;

		public bool Block;

		public bool Jump;

		public static readonly DesiredControls Idle = default(DesiredControls);

		public static readonly DesiredControls HoldBlock = new DesiredControls
		{
			Block = true
		};
	}
	internal class Trainer
	{
		private enum State
		{
			Off,
			Working,
			Resting
		}

		private const float EatInterval = 1.5f;

		private const float DrinkInterval = 1.5f;

		private const float ArriveDistance = 0.15f;

		private const float StaleTickSeconds = 0.5f;

		private const string SeeLog = "Details are in BepInEx/LogOutput.log";

		private const string CantRoll = "Dodge training can't make your character roll in this game version. Details are in BepInEx/LogOutput.log";

		private const float ParryWindowSeconds = 0.25f;

		private const float ParryLeadSeconds = 0.12f;

		private const float ShieldDownAfterHitSeconds = 0.1f;

		private const float LateHitSeconds = 0.5f;

		private const float ShieldDownSeconds = 0.06f;

		private const float FreshShieldSlack = 0.05f;

		private const float NoSwingFallbackSeconds = 12f;

		private const float RollStartSeconds = 0.3f;

		private const float RollSeconds = 0.9f;

		private const float RollMaxSeconds = 1.5f;

		private const float RollInputSeconds = 0.06f;

		private const float RollBeatSeconds = 3f;

		private const int FailedRollsBeforeStop = 3;

		private const float RollLeadStep = 0.04f;

		private const float MinRollLead = 0.04f;

		private const float MaxRollLead = 0.6f;

		private const int ResultsLogged = 6;

		private const float WalkInSeconds = 5f;

		private const float WalkInSecondsPerMetre = 2f;

		private const float WalkInStuckSeconds = 0.75f;

		private const float WalkInCloseEnough = 0.4f;

		private const float BowLetGoSeconds = 0.1f;

		private const float BowDrawSpareSeconds = 0.2f;

		private const float SkillCheckSeconds = 1f;

		private const float WatchSeconds = 1f;

		private const float WatchSlack = 1f;

		private static readonly FieldInfo LookYawField = typeof(Player).GetField("m_lookYaw", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static float _rollLead = 0.12f;

		private readonly ManualLogSource _log;

		private State _state;

		private TrainingMode _mode;

		private Vector3 _anchor;

		private GameObject _dummy;

		private GameObject _describedDummy;

		private bool _walkingBack;

		private float _modeStartedAt;

		private int _playerSwingsBefore;

		private int _playerHitsBefore;

		private bool _walkingIn;

		private bool _atRange;

		private float _rockRange = 12f;

		private float _watchTimer;

		private readonly HashSet<GameObject> _announced = new HashSet<GameObject>();

		private float _walkInTime;

		private float _walkInLimit;

		private float _walkInBest;

		private float _walkInSinceBest;

		private float _bowHeld;

		private float _bowLetGoUntil;

		private float _skillCheckTimer;

		private bool _manualMoveRequested;

		private float _eatTimer;

		private float _drinkTimer;

		private float _lastTickTime = float.NegativeInfinity;

		private Quaternion _facing = Quaternion.identity;

		private float _startedAt;

		private bool _noSwingNoted;

		private bool _noFoodNoted;

		private bool _backFromDodge;

		private bool _canParry = true;

		private bool _shieldUp;

		private float _shieldUpSince;

		private float _shieldDownUntil;

		private int _swingsLanded;

		private int _parried;

		private int _blocked;

		private int _hitsTaken;

		private bool _parriesByOwnTiming;

		private int _rocksLanded;

		private int _rocksParried;

		private int _rocksBlocked;

		private int _fromBehind;

		private int _swingResultsLogged;

		private int _rockResultsLogged;

		private SwingTimer.Swing _rolledFor;

		private float _rollStartedAt = float.NegativeInfinity;

		private Vector3 _rollFrom;

		private DesiredControls _rollControls;

		private float _rollStamina;

		private bool _rollSeen;

		private bool _rollChecked;

		private bool _rolledForSettled;

		private float _leadChange;

		private string _leadChangeWhy;

		private int _failedRolls;

		private float _nextBeatRoll;

		private int _rolls;

		private int _rollsPassedThrough;

		private int _rollsPassedUnseen;

		private int _rollsCaught;

		private int _rollsMissed;

		private int _hitsBeforeRoll;

		private int _hitsWhileResting;

		private int _rocksRolledFor;

		private int _passedThroughOther;

		private int _caughtDuringOther;

		public bool IsActive => _state != State.Off;

		public bool IsDriving
		{
			get
			{
				if (IsActive)
				{
					return Time.time - _lastTickTime < 0.5f;
				}
				return false;
			}
		}

		public DesiredControls Controls { get; private set; }

		private string ModeName => _mode switch
		{
			TrainingMode.Attack => "attack", 
			TrainingMode.Block => "block", 
			TrainingMode.Parry => "parry", 
			_ => "dodge", 
		};

		public Trainer(ManualLogSource log)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			_log = log;
			SwingTimer.Landed = OnSwingLanded;
		}

		public void Toggle()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				if (IsActive)
				{
					Stop(localPlayer, "Training stopped");
				}
				else
				{
					Start(localPlayer);
				}
			}
		}

		public void SwitchMode()
		{
			LogSummary();
			_backFromDodge |= _mode == TrainingMode.Dodge;
			_mode = (TrainingMode)((int)(_mode + 1) % 4);
			ResetModeState();
			Player localPlayer = Player.m_localPlayer;
			_log.LogInfo((object)("Mode switched to " + ModeName));
			if (IsActive && _mode == TrainingMode.Dodge && !Roll.CanRoll)
			{
				Stop(localPlayer, "Dodge training can't make your character roll in this game version. Details are in BepInEx/LogOutput.log");
				return;
			}
			string text = (IsActive ? MaxedSkill(localPlayer, Game.CurrentWeapon(localPlayer)) : null);
			if (text != null)
			{
				Stop(localPlayer, "Training stopped: your " + text + " skill is already 100");
				return;
			}
			string text2 = (IsActive ? CheckParry(localPlayer) : "");
			if (IsActive)
			{
				_log.LogInfo((object)("Start spot for " + ModeName + " mode: " + PlanWalkIn(localPlayer)));
				LogSkill(localPlayer, Game.CurrentWeapon(localPlayer));
			}
			Game.Message(localPlayer, (MessageType)2, "Training mode: " + ModeName + (_atRange ? " against rocks" : "") + text2);
		}

		public void OnManualMoveInput()
		{
			if (AfkTrainingPlugin.StopOnManualMove.Value)
			{
				_manualMoveRequested = true;
			}
		}

		public void Tick(float dt)
		{
			Controls = DesiredControls.Idle;
			_lastTickTime = Time.time;
			if (_state != State.Off)
			{
				Train(dt);
			}
			if (!Controls.Attack)
			{
				_bowHeld = 0f;
			}
			if (Controls.Block && !_shieldUp)
			{
				_shieldUpSince = Time.time;
			}
			_shieldUp = Controls.Block;
		}

		private void Train(float dt)
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_037a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			Player player = Player.m_localPlayer;
			if ((Object)(object)player == (Object)null || ((Character)player).IsDead())
			{
				Stop(player, "Training stopped");
				return;
			}
			_eatTimer -= dt;
			_drinkTimer -= dt;
			if (!Game.TakesInput(player))
			{
				return;
			}
			if (_manualMoveRequested)
			{
				Stop(player, "Training stopped: you moved");
				return;
			}
			ItemData weapon = Game.CurrentWeapon(player);
			if (_mode == TrainingMode.Attack && Game.IsBroken(weapon))
			{
				Stop(player, "Training stopped: your weapon broke");
				return;
			}
			_skillCheckTimer -= dt;
			if (_skillCheckTimer <= 0f)
			{
				_skillCheckTimer = 1f;
				string text = MaxedSkill(player, weapon);
				if (text != null)
				{
					Stop(player, "Training stopped: your " + text + " skill reached 100");
					return;
				}
			}
			Vector3 val = Flat(_anchor - ((Component)player).transform.position);
			if (((Vector3)(ref val)).magnitude <= AfkTrainingPlugin.MaxDistance.Value)
			{
				_backFromDodge = false;
			}
			float num = ((_mode == TrainingMode.Dodge || _backFromDodge) ? AfkTrainingPlugin.DodgeMaxDistance.Value : AfkTrainingPlugin.MaxDistance.Value);
			if (((Vector3)(ref val)).magnitude > num && !_walkingIn)
			{
				Stop(player, "Training stopped: you were pushed too far away");
				return;
			}
			if ((Object)(object)_dummy == (Object)null)
			{
				_dummy = DummyFinder.Find(player, SearchRadius(player));
				if ((Object)(object)_dummy != (Object)null)
				{
					_rockRange = Reach.RockRange(_dummy);
					SwingTimer.Watch(_dummy);
					_watchTimer = 0f;
				}
			}
			if ((Object)(object)_dummy == (Object)null)
			{
				Stop(player, "Training stopped: the T.W.I.G. is gone");
				return;
			}
			_watchTimer -= dt;
			if (_watchTimer <= 0f)
			{
				WatchOthers(player);
			}
			SettleRolledSwing(player);
			if (RollInProgress(player))
			{
				return;
			}
			if (_mode == TrainingMode.Dodge && _failedRolls >= 3)
			{
				Stop(player, "Training stopped: your character won't roll. You can't roll while over-encumbered.");
				return;
			}
			if ((_eatTimer <= 0f || BarTooSmallToRoll(player)) && !Game.InAttack(player))
			{
				_eatTimer = 1.5f;
				string why;
				FoodPicker.Result result = (WantsFood(player, out why) ? Game.Guard("auto-eat", () => FoodPicker.EatIfHungry(player, _log), FoodPicker.Result.Failed) : FoodPicker.Result.NotHungry);
				switch (result)
				{
				case FoodPicker.Result.Failed:
					Stop(player, "Training stopped: auto-eat failed. Details are in BepInEx/LogOutput.log");
					return;
				case FoodPicker.Result.OutOfFood:
					if (_mode != TrainingMode.Dodge)
					{
						Stop(player, "Training stopped: out of food");
						return;
					}
					break;
				}
				if (result == FoodPicker.Result.OutOfFood && !_noFoodNoted)
				{
					_noFoodNoted = true;
					_log.LogInfo((object)("Dodge mode would eat because " + why + ", but there's nothing it can eat"));
				}
				if (result == FoodPicker.Result.Ate)
				{
					if (why != null)
					{
						_log.LogInfo((object)("Dodge mode ate because " + why));
					}
					return;
				}
			}
			Face(player, _dummy.transform.position);
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (!UpdateWalkIn(player, magnitude, dt))
			{
				return;
			}
			if ((_walkingIn || magnitude > AfkTrainingPlugin.DriftDistance.Value || (_walkingBack && magnitude > 0.15f)) && !SwingComing())
			{
				_walkingBack = true;
				Controls = new DesiredControls
				{
					LocalMove = Quaternion.Inverse(_facing) * ((Vector3)(ref val)).normalized,
					Block = ShieldWhileWalking()
				};
				return;
			}
			_walkingBack = false;
			float num2 = Game.Stamina(player);
			float num3 = Game.MaxStamina(player);
			if (float.IsNaN(num2) || float.IsNaN(num3))
			{
				Stop(player, "Training stopped: couldn't read your stamina. Details are in BepInEx/LogOutput.log");
				return;
			}
			num3 = Mathf.Max(1f, num3);
			float num4 = EmptyThreshold(player, num3, weapon);
			if (num4 > num3)
			{
				Stop(player, (_mode == TrainingMode.Dodge) ? "Training stopped: one roll costs more stamina than you have" : "Training stopped: one swing costs more stamina than you have");
				return;
			}
			float num5 = Mathf.Max(num3 * AfkTrainingPlugin.ResumeAtPercent.Value / 100f, num4);
			if ((num2 < num4 || (_state == State.Resting && num2 < num5)) && _mode != TrainingMode.Dodge && TryDrinkPotion(player))
			{
				return;
			}
			if (_state == State.Resting)
			{
				if (num2 < num5)
				{
					return;
				}
				SetState(player, State.Working, "Back to training");
			}
			if (num2 < num4)
			{
				SetState(player, State.Resting, "Out of stamina, resting");
				return;
			}
			switch (_mode)
			{
			case TrainingMode.Attack:
				Controls = new DesiredControls
				{
					Attack = AttackHeld(player, weapon, dt)
				};
				break;
			case TrainingMode.Block:
				Controls = DesiredControls.HoldBlock;
				break;
			case TrainingMode.Parry:
				Controls = Parry();
				break;
			default:
				Controls = Dodge(player);
				break;
			}
		}

		private bool AttackHeld(Player player, ItemData weapon, float dt)
		{
			if (!Reach.IsBow(weapon))
			{
				return true;
			}
			float time = Time.time;
			if (time < _bowLetGoUntil)
			{
				return false;
			}
			_bowHeld += dt;
			if (Game.Call(player, "GetAttackDrawPercentage") as float? >= 1f || _bowHeld > Reach.BowDrawSeconds(weapon) + 0.2f)
			{
				_bowLetGoUntil = time + 0.1f;
				return false;
			}
			return true;
		}

		private DesiredControls Parry()
		{
			if (!_canParry || !CanTimeSwings())
			{
				return DesiredControls.HoldBlock;
			}
			Player localPlayer = Player.m_localPlayer;
			float time = Time.time;
			if (time < _shieldDownUntil)
			{
				return DesiredControls.Idle;
			}
			bool flag = false;
			float? num = null;
			foreach (SwingTimer.Swing swing in SwingTimer.Swings)
			{
				if (Behind(localPlayer, swing.Source))
				{
					continue;
				}
				if (swing.Hit.HasValue)
				{
					if (time < swing.Hit.Value + (SwingTimer.SeesHits ? 0.1f : swing.Flight))
					{
						flag = true;
					}
				}
				else if (!swing.IsOver(time))
				{
					if (!IsTimed(swing))
					{
						return DesiredControls.HoldBlock;
					}
					float value = swing.ExpectedHit.Value;
					if (time >= value - 0.12f && time < value + 0.5f && (!num.HasValue || value < num.Value))
					{
						num = value;
					}
				}
			}
			if (!num.HasValue)
			{
				if (!flag)
				{
					return DesiredControls.Idle;
				}
				return DesiredControls.HoldBlock;
			}
			if (SwingTimer.SeesHits && _shieldUp && num.Value - _shieldUpSince >= 0.2f && time + 0.06f < num.Value - 0.05f)
			{
				_shieldDownUntil = time + 0.06f;
				return DesiredControls.Idle;
			}
			return DesiredControls.HoldBlock;
		}

		private static bool IsTimed(SwingTimer.Swing swing)
		{
			if (swing.ExpectedHit.HasValue)
			{
				if (!swing.TimedToYou && swing.Thrown)
				{
					return !SwingTimer.SeesHits;
				}
				return true;
			}
			return false;
		}

		private DesiredControls Dodge(Player player)
		{
			float time = Time.time;
			if (!CanTimeSwings())
			{
				if (time < _nextBeatRoll)
				{
					return DesiredControls.Idle;
				}
				_nextBeatRoll = time + 3f;
				return StartRoll(player, null);
			}
			bool flag = false;
			SwingTimer.Swing swing = null;
			foreach (SwingTimer.Swing swing2 in SwingTimer.Swings)
			{
				if (swing2 == _rolledFor || swing2.IsOver(time))
				{
					continue;
				}
				if (!IsTimed(swing2))
				{
					flag |= !Behind(player, swing2.Source);
					continue;
				}
				float value = swing2.ExpectedHit.Value;
				if (time >= value - _rollLead && time <= value && (swing == null || value < swing.ExpectedHit.Value))
				{
					swing = swing2;
				}
			}
			if (swing != null)
			{
				return StartRoll(player, swing);
			}
			if (!flag)
			{
				return DesiredControls.Idle;
			}
			return DesiredControls.HoldBlock;
		}

		private DesiredControls StartRoll(Player player, SwingTimer.Swing swing)
		{
			//IL_0002: 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_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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = RollDirection(player);
			_rolledFor = swing;
			_rolledForSettled = false;
			_rollStartedAt = Time.time;
			_rollFrom = ((Component)player).transform.position;
			_rollStamina = Game.Stamina(player);
			_rollSeen = false;
			_rollChecked = false;
			_leadChange = 0f;
			if (Roll.TryQueue(player, val))
			{
				_rollControls = DesiredControls.Idle;
				return DesiredControls.Idle;
			}
			_rollControls = new DesiredControls
			{
				LocalMove = Quaternion.Inverse(_facing) * val,
				Block = true,
				Jump = true
			};
			return _rollControls;
		}

		private Vector3 RollDirection(Player player)
		{
			//IL_000b: 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_0047: 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)
			Vector3 val = Flat(_dummy.transform.position - ((Component)player).transform.position);
			if (!(((Vector3)(ref val)).sqrMagnitude > 0.0001f))
			{
				return _facing * Vector3.forward;
			}
			return ((Vector3)(ref val)).normalized;
		}

		private bool Behind(Player player, GameObject source)
		{
			//IL_003b: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: 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)
			if ((Object)(object)player == (Object)null || (Object)(object)source == (Object)null || (Object)(object)_dummy == (Object)null || (Object)(object)source == (Object)(object)_dummy)
			{
				return false;
			}
			return Vector3.Dot(Flat(_dummy.transform.position - ((Component)player).transform.position), Flat(source.transform.position - ((Component)player).transform.position)) < 0f;
		}

		private bool RollInProgress(Player player)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: 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)
			//IL_0054: 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)
			float num = Time.time - _rollStartedAt;
			if (num >= 1.5f)
			{
				return false;
			}
			bool? flag = Roll.InDodge(player);
			if (!_rollSeen)
			{
				if (flag != true)
				{
					Vector3 val = Flat(((Component)player).transform.position - _rollFrom);
					if (!(((Vector3)(ref val)).magnitude > 1f) && !(Game.Stamina(player) < _rollStamina - Roll.StaminaCost(player) / 2f))
					{
						goto IL_0099;
					}
				}
				_rollSeen = true;
				_rolls++;
			}
			goto IL_0099;
			IL_0099:
			if (num < 0.3f)
			{
				if (num < 0.06f)
				{
					Controls = _rollControls;
				}
				return true;
			}
			if ((!flag.HasValue) ? (num < 0.9f) : flag.Value)
			{
				return true;
			}
			if (!_rollChecked)
			{
				_rollChecked = true;
				_failedRolls = ((!_rollSeen) ? (_failedRolls + 1) : 0);
				if (!_rollSeen)
				{
					_log.LogInfo((object)"A roll didn't happen");
				}
				else if (_leadChange != 0f)
				{
					ChangeRollLead();
				}
			}
			return false;
		}

		private void ChangeRollLead()
		{
			_rollLead = Mathf.Clamp(_rollLead + _leadChange, 0.04f, 0.6f);
			_leadChange = 0f;
			_log.LogInfo((object)$"{_leadChangeWhy}; rolling {_rollLead:0.00} s before each hit from now on");
		}

		private bool SwingComing()
		{
			if (_mode != TrainingMode.Dodge || !CanTimeSwings())
			{
				return false;
			}
			float now = Time.time;
			return SwingTimer.Swings.Any((SwingTimer.Swing swing) => swing != _rolledFor && !swing.IsOver(now));
		}

		private bool CanTimeSwings()
		{
			if (!SwingTimer.IsHooked)
			{
				return false;
			}
			if (SwingTimer.LastSwingSeenAt >= _startedAt || Time.time - _startedAt < 12f)
			{
				return true;
			}
			if (!_noSwingNoted)
			{
				_noSwingNoted = true;
				string text = ((_mode == TrainingMode.Dodge) ? "rolling on a steady beat" : "holding block");
				if (_atRange)
				{
					Game.Message(Player.m_localPlayer, (MessageType)1, "No rock from the T.W.I.G. yet, so " + text + " until one comes");
					_log.LogWarning((object)($"No T.W.I.G. rock seen in {12f:0} s of training at range, so {ModeName} mode is {text} " + "until one comes. If none does, start closer to the T.W.I.G. This also happens if another player's game runs it."));
				}
				else
				{
					Game.Message(Player.m_localPlayer, (MessageType)1, "Can't see the T.W.I.G.'s swings, so " + text);
					_log.LogWarning((object)($"No T.W.I.G. swing seen in {12f:0} s of training, so {ModeName} mode is {text} instead. " + "This happens if another player's game runs the T.W.I.G."));
				}
			}
			return false;
		}

		private void OnSwingLanded(SwingTimer.Swing swing)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!IsActive || (Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			_swingsLanded++;
			if (swing.Thrown)
			{
				_rocksLanded++;
			}
			if (_mode == TrainingMode.Block || _mode == TrainingMode.Parry)
			{
				CountBlock(localPlayer, swing);
			}
			if (_mode == TrainingMode.Dodge && swing != _rolledFor)
			{
				if ((swing.Invulnerable ?? Roll.IsInvulnerable(localPlayer)) == true && swing.Dodgeable != false)
				{
					_passedThroughOther++;
				}
				else if (Roll.InDodge(localPlayer) == true)
				{
					_caughtDuringOther++;
				}
				else if (_state == State.Resting)
				{
					_hitsWhileResting++;
				}
				else if (IsTimed(swing))
				{
					_hitsBeforeRoll++;
				}
			}
			if (_mode != TrainingMode.Dodge || swing != _rolledFor)
			{
				return;
			}
			_rolledForSettled = true;
			if (swing.Thrown)
			{
				_rocksRolledFor++;
			}
			bool? flag = swing.Invulnerable ?? Roll.IsInvulnerable(localPlayer);
			if (!flag.HasValue)
			{
				return;
			}
			if (flag.Value && swing.Dodgeable == false)
			{
				Game.Warn("undodgeable", "T.W.I.G. " + swing.Name + " can't be dodged, the game says, so it hits you even mid-roll");
				return;
			}
			if (flag.Value)
			{
				_rollsPassedThrough++;
				LogResult(swing, $"{Capital(swing.Name)} passed through your roll {Time.time - _rollStartedAt:0.00} s into it");
				return;
			}
			_rollsCaught++;
			LogResult(swing, null);
			bool flag2 = !_rollSeen && Roll.InDodge(localPlayer) != true;
			_leadChange = (flag2 ? 0.04f : (-0.04f));
			_leadChangeWhy = (flag2 ? (Capital(swing.Name) + " caught you before the roll started") : $"{Capital(swing.Name)} caught you {Time.time - _rollStartedAt:0.00} s into the roll, after its invulnerable part");
			if (_rollChecked && _rollSeen)
			{
				ChangeRollLead();
			}
		}

		private void CountBlock(Player player, SwingTimer.Swing swing)
		{
			bool flag = swing.Blocking ?? _shieldUp;
			float num = Time.time - _shieldUpSince;
			bool flag2 = Behind(player, swing.Source);
			string text;
			if (flag2)
			{
				_fromBehind++;
				text = "came from behind you, where a shield can't stop it";
			}
			else
			{
				if (flag)
				{
					if (_mode == TrainingMode.Parry && _canParry)
					{
						bool num2;
						if (!swing.BlockTimer.HasValue)
						{
							num2 = num < 0.25f;
						}
						else
						{
							if (!(swing.BlockTimer.Value >= 0f))
							{
								goto IL_0112;
							}
							num2 = swing.BlockTimer.Value < 0.25f;
						}
						if (num2)
						{
							_parried++;
							if (swing.Thrown)
							{
								_rocksParried++;
							}
							_parriesByOwnTiming |= !swing.BlockTimer.HasValue;
							text = "was parried";
							goto IL_013c;
						}
					}
					goto IL_0112;
				}
				_hitsTaken++;
				text = "hit you with the shield down";
			}
			goto IL_013c;
			IL_013c:
			if (_mode == TrainingMode.Parry)
			{
				string text2 = (flag2 ? "" : ((swing.BlockTimer.HasValue && swing.BlockTimer.Value >= 0f) ? $"; the game's block timer read {swing.BlockTimer.Value:0.00} s, and a parry needs under {0.25f} s" : (flag ? $"; the shield went up {num:0.00} s before, and a parry needs under {0.25f} s" : "")));
				LogResult(swing, Capital(swing.Name) + " " + text + text2);
			}
			return;
			IL_0112:
			_blocked++;
			if (swing.Thrown)
			{
				_rocksBlocked++;
			}
			text = "was blocked";
			goto IL_013c;
		}

		private void SettleRolledSwing(Player player)
		{
			SwingTimer.Swing rolledFor = _rolledFor;
			if (rolledFor == null || _rolledForSettled)
			{
				return;
			}
			float time = Time.time;
			if (rolledFor.Thrown && !rolledFor.Hit.HasValue && !rolledFor.InvulnerableWhenStruck.HasValue && rolledFor.ExpectedHit <= time)
			{
				rolledFor.InvulnerableWhenStruck = Roll.IsInvulnerable(player);
			}
			if (!rolledFor.IsOver(time))
			{
				return;
			}
			_rolledForSettled = true;
			if (!rolledFor.Hit.HasValue)
			{
				if (rolledFor.Thrown)
				{
					_rocksRolledFor++;
				}
				if (rolledFor.InvulnerableWhenStruck == true)
				{
					_rollsPassedUnseen++;
					LogResult(rolledFor, rolledFor.Thrown ? (Capital(rolledFor.Name) + " was due while you were invulnerable, and didn't reach you") : (Capital(rolledFor.Name) + " struck while you were invulnerable, and no hit reached you"));
				}
				else
				{
					_rollsMissed++;
					LogResult(rolledFor, Capital(rolledFor.Name) + " missed you");
				}
			}
		}

		private void LogResult(SwingTimer.Swing swing, string text)
		{
			if ((swing.Thrown ? (++_rockResultsLogged) : (++_swingResultsLogged)) <= 6 && text != null)
			{
				_log.LogInfo((object)text);
			}
		}

		private static string Capital(string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				return char.ToUpperInvariant(text[0]) + text.Substring(1);
			}
			return text;
		}

		private void Start(Player player)
		{
			//IL_00cb: 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_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			if (!ControlsPatch.IsHooked)
			{
				Game.Message(player, (MessageType)2, "AFK training can't control your character. Details are in BepInEx/LogOutput.log");
				return;
			}
			if (Game.InPlaceMode(player))
			{
				Game.Message(player, (MessageType)2, "Put the building tool away before training");
				return;
			}
			if (_mode == TrainingMode.Dodge && !Roll.CanRoll)
			{
				Game.Message(player, (MessageType)2, "Dodge training can't make your character roll in this game version. Details are in BepInEx/LogOutput.log");
				return;
			}
			float num = SearchRadius(player);
			_dummy = DummyFinder.Find(player, num);
			if ((Object)(object)_dummy == (Object)null)
			{
				Game.Message(player, (MessageType)2, $"No T.W.I.G. within {num:0.#} m");
				return;
			}
			_rockRange = Reach.RockRange(_dummy);
			ItemData val = Game.CurrentWeapon(player);
			string text = MaxedSkill(player, val);
			if (text != null)
			{
				Game.Message(player, (MessageType)2, "Your " + text + " skill is already 100");
				_dummy = null;
				return;
			}
			_anchor = ((Component)player).transform.position;
			_walkingBack = false;
			_walkingIn = false;
			_manualMoveRequested = false;
			_eatTimer = 0f;
			_drinkTimer = 0f;
			_startedAt = Time.time;
			_noSwingNoted = false;
			_backFromDodge = false;
			_rollStartedAt = float.NegativeInfinity;
			ResetModeState();
			FoodPicker.Reset();
			PotionPicker.Reset();
			SwingTimer.Watch(_dummy);
			_announced.Clear();
			_state = State.Working;
			string text2 = PlanWalkIn(player);
			string text3 = _mode switch
			{
				TrainingMode.Block => "blocking", 
				TrainingMode.Parry => _atRange ? "parrying rocks" : "parrying", 
				TrainingMode.Dodge => _atRange ? "dodging rocks" : "dodging", 
				_ => (val != null) ? Game.Localize(val.m_shared.m_name) : "unarmed", 
			};
			string text4 = CheckParry(player);
			Game.Message(player, (MessageType)2, "Training started: " + text3 + text4);
			Vector3 val2 = Flat(_dummy.transform.position - ((Component)player).transform.position);
			float magnitude = ((Vector3)(ref val2)).magnitude;
			_log.LogInfo((object)$"Training started ({ModeName}, {text3}) at {((Component)player).transform.position}, {magnitude:0.0} m from {((Object)_dummy).name}");
			if ((Object)(object)_dummy != (Object)(object)_describedDummy)
			{
				_describedDummy = _dummy;
				_log.LogInfo((object)("The T.W.I.G.'s swings: " + Reach.DescribeSwings(_dummy)));
			}
			_log.LogInfo((object)("Start spot: " + text2));
			WatchOthers(player);
			LogSkill(player, val);
			if (_mode == TrainingMode.Attack && Reach.IsBow(val))
			{
				_log.LogInfo((object)$"Drawing each arrow fully, then letting go. A full draw takes up to {Reach.BowDrawSeconds(val):0.0} s.");
			}
		}

		private float SearchRadius(Player player)
		{
			float value = AfkTrainingPlugin.SearchRadius.Value;
			if (_mode == TrainingMode.Attack && Reach.IsRanged(Game.CurrentWeapon(player)))
			{
				return Mathf.Max(value, AfkTrainingPlugin.RangedSearchRadius.Value);
			}
			if (_mode == TrainingMode.Parry || _mode == TrainingMode.Dodge)
			{
				return Mathf.Max(value, 12f);
			}
			return value;
		}

		private void WatchOthers(Player player)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			_watchTimer = 1f;
			foreach (GameObject item in SwingTimer.WatchAlso((_mode == TrainingMode.Parry || _mode == TrainingMode.Dodge) ? (from other in DummyFinder.FindAll(player, Mathf.Max(_rockRange, AfkTrainingPlugin.SearchRadius.Value) + 1f)
				where (Object)(object)other != (Object)(object)_dummy
				select other).ToList() : new List<GameObject>()))
			{
				if (_announced.Add(item))
				{
					Vector3 val = Flat(item.transform.position - ((Component)player).transform.position);
					float magnitude = ((Vector3)(ref val)).magnitude;
					_log.LogInfo((object)($"Also watching a T.W.I.G. {magnitude:0.0} m away, " + (Behind(player, item) ? "behind you, where a shield can't stop its rocks" : "in front of you")));
				}
			}
		}

		private string MaxedSkill(Player player, ItemData weapon)
		{
			string text = SkillLevels.TrainedBy(_mode, weapon);
			if (text == null)
			{
				Game.Warn("weapon skill", "Couldn't tell which skill your weapon trains, so training won't stop when it reaches 100");
				return null;
			}
			float? num = SkillLevels.BaseLevel(player, text);
			if (!num.HasValue)
			{
				Game.Warn("skill level", "Couldn't read your skill levels, so training won't stop when a skill reaches 100");
				return null;
			}
			if (!(num.Value >= 100f))
			{
				return null;
			}
			return SkillLevels.DisplayName(text);
		}

		private void LogSkill(Player player, ItemData weapon)
		{
			string skill = SkillLevels.TrainedBy(_mode, weapon);
			float? num = SkillLevels.BaseLevel(player, skill);
			if (num.HasValue)
			{
				_log.LogInfo((object)$"Your {SkillLevels.DisplayName(skill)} skill is {num.Value:0.##} without gear or food. Training stops at 100.");
			}
		}

		private string PlanWalkIn(Player player)
		{
			//IL_0006: 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_002f: 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_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = Flat(((Component)player).transform.position - _dummy.transform.position);
			float magnitude;
			if (!_walkingIn)
			{
				Vector3 val2 = Flat(_anchor - _dummy.transform.position);
				magnitude = ((Vector3)(ref val2)).magnitude;
			}
			else
			{
				magnitude = ((Vector3)(ref val)).magnitude;
			}
			float num = magnitude;
			_atRange = (_mode == TrainingMode.Parry || _mode == TrainingMode.Dodge) && num > AfkTrainingPlugin.SearchRadius.Value && num <= _rockRange;
			if (_atRange)
			{
				if (_walkingIn)
				{
					_anchor = ((Component)player).transform.position;
				}
				_walkingIn = false;
				return $"where you stand, {num:0.0} m from the T.W.I.G., training against its rocks " + $"(it throws from up to {_rockRange:0.#} m)";
			}
			string why;
			float? num2 = Reach.StandWithin(_mode, player, _dummy, out why);
			if (!num2.HasValue || ((Vector3)(ref val)).magnitude <= num2.Value + 0.15f)
			{
				if (_walkingIn)
				{
					_anchor = ((Component)player).transform.position;
				}
				_walkingIn = false;
				if (num2.HasValue)
				{
					return $"where you stand, {((Vector3)(ref val)).magnitude:0.0} m from the T.W.I.G., close enough to {num2.Value:0.0} m ({why})";
				}
				return $"where you stand, {((Vector3)(ref val)).magnitude:0.0} m from the T.W.I.G. ({why})";
			}
			_anchor = _dummy.transform.position + ((Vector3)(ref val)).normalized * num2.Value;
			_walkingIn = true;
			_walkInTime = 0f;
			_walkInLimit = Mathf.Max(5f, 2f * (((Vector3)(ref val)).magnitude - num2.Value));
			_walkInBest = float.MaxValue;
			_walkInSinceBest = 0f;
			return $"walking in from {((Vector3)(ref val)).magnitude:0.0} m to {num2.Value:0.0} m from the T.W.I.G. ({why})";
		}

		private bool UpdateWalkIn(Player player, float drift, float dt)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			if (!_walkingIn)
			{
				return true;
			}
			if (drift <= 0.15f)
			{
				Arrive(player, "Walked in");
				return true;
			}
			if (drift < _walkInBest - 0.05f)
			{
				_walkInBest = drift;
				_walkInSinceBest = 0f;
			}
			if (SwingComing())
			{
				return true;
			}
			_walkInTime += dt;
			_walkInSinceBest += dt;
			bool num = _walkInSinceBest > 0.75f;
			bool flag = _walkInTime > _walkInLimit;
			if ((num || flag) && drift <= 0.4f)
			{
				_anchor = ((Component)player).transform.position;
				Arrive(player, "Walked in as close as you could get");
				return true;
			}
			if (!flag)
			{
				return true;
			}
			Stop(player, "Training stopped: couldn't walk you close enough to the T.W.I.G.");
			return false;
		}

		private void Arrive(Player player, string how)
		{
			//IL_0012: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			_walkingIn = false;
			Vector3 val = Flat(_dummy.transform.position - ((Component)player).transform.position);
			float magnitude = ((Vector3)(ref val)).magnitude;
			_log.LogInfo((object)$"{how}: {magnitude:0.0} m from the T.W.I.G.");
		}

		private bool ShieldWhileWalking()
		{
			if (_state != State.Working)
			{
				return false;
			}
			if (_mode == TrainingMode.Block)
			{
				return true;
			}
			if (_mode == TrainingMode.Parry)
			{
				return Parry().Block;
			}
			return false;
		}

		private string CheckParry(Player player)
		{
			_canParry = true;
			if (_mode != TrainingMode.Parry || (Object)(object)player == (Object)null)
			{
				return "";
			}
			_canParry = Game.CanParry(player, out var blocker);
			if (_canParry)
			{
				return "";
			}
			_log.LogInfo((object)(blocker + " has no parry bonus, so parry mode holds block"));
			return ". " + blocker + " can't parry, so this holds block";
		}

		private void ResetModeState()
		{
			_canParry = true;
			_modeStartedAt = Time.time;
			_noFoodNoted = false;
			_swingsLanded = 0;
			_parried = 0;
			_blocked = 0;
			_hitsTaken = 0;
			_parriesByOwnTiming = false;
			_rocksLanded = 0;
			_rocksParried = 0;
			_rocksBlocked = 0;
			_fromBehind = 0;
			_swingResultsLogged = 0;
			_rockResultsLogged = 0;
			_rolledFor = null;
			_rollControls = DesiredControls.Idle;
			_failedRolls = 0;
			_nextBeatRoll = Time.time + 1f;
			_rolls = 0;
			_rollsPassedThrough = 0;
			_rollsPassedUnseen = 0;
			_rollsCaught = 0;
			_rollsMissed = 0;
			_hitsBeforeRoll = 0;
			_hitsWhileResting = 0;
			_rocksRolledFor = 0;
			_passedThroughOther = 0;
			_caughtDuringOther = 0;
			_watchTimer = 0f;
			_bowHeld = 0f;
			_bowLetGoUntil = Time.time + 0.1f;
			_skillCheckTimer = 0f;
			_playerSwingsBefore = SwingTimer.PlayerSwings;
			_playerHitsBefore = SwingTimer.PlayerHits;
		}

		private void LogSummary()
		{
			if (!IsActive || Time.time - _modeStartedAt < 1f)
			{
				return;
			}
			string text = $"{(Time.time - _modeStartedAt) / 60f:0.0} min";
			switch (_mode)
			{
			case TrainingMode.Attack:
				if (SwingTimer.IsHooked)
				{
					int num = SwingTimer.PlayerSwings - _playerSwingsBefore;
					int num2 = SwingTimer.PlayerHits - _playerHitsBefore;
					bool flag2 = SwingTimer.SeesHits && (num2 > 0 || SwingTimer.LastSwingSeenAt >= _modeStartedAt);
					_log.LogInfo((object)($"Attack mode, {text}: {num} swings" + (flag2 ? $", {num2} of them hit the T.W.I.G." : "")));
				}
				break;
			case TrainingMode.Block:
			case TrainingMode.Parry:
			{
				bool flag3 = _mode == TrainingMode.Parry && _canParry;
				string text3 = (flag3 ? string.Format("{0} parried{1}, ", _parried, _parriesByOwnTiming ? " (by the mod's timing, not the game's)" : "") : "");
				string text4 = ((_fromBehind > 0) ? $", {_fromBehind} from behind you, where a shield can't stop them" : "");
				string text5 = ((_rocksLanded > 0) ? (string.Format(". {0} of them {1}: ", _rocksLanded, (_rocksLanded == 1) ? "was a rock" : "were rocks") + string.Format("{0}{1} blocked, {2} not stopped", flag3 ? $"{_rocksParried} parried, " : "", _rocksBlocked, _rocksLanded - _rocksParried - _rocksBlocked)) : "");
				_log.LogInfo((object)(string.Format("{0} mode, {1}: {2} T.W.I.G. hits reached you: ", (_mode == TrainingMode.Parry) ? "Parry" : "Block", text, _swingsLanded) + $"{text3}{_blocked} blocked, {_hitsTaken} with the shield down{text4}{text5}"));
				break;
			}
			default:
				if (_rolls != 0)
				{
					string arg = ((_passedThroughOther > 0) ? $" {_passedThroughOther} more hits passed through a roll not made for them." : "") + ((_caughtDuringOther > 0) ? $" {_caughtDuringOther} more hits caught you in a roll not made for them, after its invulnerable part." : "") + ((_hitsBeforeRoll > 0) ? $" {_hitsBeforeRoll} more hits came before their roll." : "") + ((_hitsWhileResting > 0) ? $" {_hitsWhileResting} hits landed while it rested for stamina." : "");
					bool flag = _rollsPassedThrough + _rollsPassedUnseen + _rollsCaught + _rollsMissed > 0;
					string text2 = ((_rocksRolledFor > 0) ? $" ({_rocksRolledFor} of them rocks)" : "");
					_log.LogInfo((object)((Roll.CanTellInvulnerable && flag) ? ($"Dodge mode, {text}: {_rolls} rolls. Of the swings rolled for{text2}, {_rollsPassedThrough} reached you while you were " + $"invulnerable, {_rollsPassedUnseen} struck while you were invulnerable without reaching you, " + $"{_rollsCaught} caught you outside the invulnerable part, and {_rollsMissed} missed.{arg}") : $"Dodge mode, {text}: {_rolls} rolls.{arg}"));
				}
				break;
			}
		}

		private void Stop(Player player, string reason)
		{
			LogSummary();
			_state = State.Off;
			_dummy = null;
			_atRange = false;
			SwingTimer.Watch(null);
			Controls = DesiredControls.Idle;
			Game.Message(player, (MessageType)2, reason);
			_log.LogInfo((object)reason);
		}

		private bool WantsFood(Player player, out string why)
		{
			why = null;
			if (_mode != TrainingMode.Dodge)
			{
				return true;
			}
			if (BarTooSmallToRoll(player))
			{
				why = $"one roll costs {Roll.StaminaCost(player):0} stamina and your stamina bar holds {Game.MaxStamina(player):0}";
				return true;
			}
			float num = Game.Health(player);
			float num2 = Game.MaxHealth(player);
			if (num < num2 / 2f && Game.Guard("Player.GetFoods", () => player.GetFoods().Count == 0, fallback: false))
			{
				why = $"your health is {num:0} of {num2:0} with no food eaten to heal it";
				return true;
			}
			return false;
		}

		private bool BarTooSmallToRoll(Player player)
		{
			if (_mode == TrainingMode.Dodge)
			{
				return Game.MaxStamina(player) < Roll.StaminaCost(player);
			}
			return false;
		}

		private bool TryDrinkPotion(Player player)
		{
			if (!AfkTrainingPlugin.DrinkStaminaPotions.Value || _drinkTimer > 0f || Game.InAttack(player))
			{
				return false;
			}
			_drinkTimer = 1.5f;
			return Game.Guard("auto-drink", () => PotionPicker.DrinkBest(player, _log), PotionPicker.Result.Failed) == PotionPicker.Result.Drank;
		}

		private void SetState(Player player, State state, string message)
		{
			_state = state;
			Game.Message(player, (MessageType)1, message);
		}

		private float EmptyThreshold(Player player, float maxStamina, ItemData weapon)
		{
			float num = maxStamina * AfkTrainingPlugin.EmptyStaminaPercent.Value / 100f;
			if (_mode == TrainingMode.Attack)
			{
				num = Mathf.Max(num, Game.SwingCost(weapon));
			}
			else if (_mode == TrainingMode.Dodge)
			{
				num = Mathf.Max(num, Roll.StaminaCost(player));
			}
			return num;
		}

		private void Face(Player player, 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_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_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_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = Flat(point - ((Component)player).transform.position);
			if (!(((Vector3)(ref val)).sqrMagnitude < 0.0001f))
			{
				_facing = Quaternion.LookRotation(((Vector3)(ref val)).normalized);
				((Component)player).transform.rotation = _facing;
				if (LookYawField != null && LookYawField.FieldType == typeof(Quaternion))
				{
					LookYawField.SetValue(player, _facing);
				}
				else
				{
					Game.Warn("m_lookYaw", "Couldn't find Player.m_lookYaw, so your character may not keep facing the T.W.I.G.");
				}
			}
		}

		private static Vector3 Flat(Vector3 v)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			v.y = 0f;
			return v;
		}
	}
}