Decompiled source of Gatling v2.0.0

plugins/Gatling.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
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.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("Gatling")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyInformationalVersion("2.0.0+e7f857617e46532b9026f1997f6f1ad3eb9fac9a")]
[assembly: AssemblyProduct("Gatling")]
[assembly: AssemblyTitle("Gatling")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.0.0.0")]
[module: UnverifiableCode]
[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 Oddhatter.MachineArbalest
{
	internal static class AutoFire
	{
		internal struct InputOverride
		{
			public bool Active;

			public bool Attack;

			public bool AttackHold;
		}

		private enum ShotResult
		{
			Fired,
			Blocked,
			OutOfAmmo
		}

		private const float TimeEpsilon = 0.0001f;

		private static Player s_player;

		private static ItemData s_weapon;

		private static bool s_firing;

		private static bool s_latchedUntilRelease;

		private static bool s_toggledOn;

		private static float s_nextShotTime;

		private static int s_burstShots;

		public static bool BeforeAttackInput(Player player, out InputOverride inputOverride)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			inputOverride = default(InputOverride);
			ItemData val = (((Character)player).InPlaceMode() ? null : ((Humanoid)player).GetCurrentWeapon());
			bool flag = WeaponRules.IsAffected(val);
			if ((Object)(object)player == (Object)(object)Player.m_localPlayer)
			{
				Diag.Log("weapon", (val == null) ? "Weapon: none" : ($"Weapon: {val.m_shared.m_name} (skill {val.m_shared.m_skillType}) " + $"[attack stamina/eitr {val.m_shared.m_attack.m_attackStamina}/{val.m_shared.m_attack.m_attackEitr}, " + $"reload drain {val.m_shared.m_attack.m_reloadStaminaDrain}/{val.m_shared.m_attack.m_reloadEitrDrain} per s, " + $"draw drain {val.m_shared.m_attack.m_drawStaminaDrain}/{val.m_shared.m_attack.m_drawEitrDrain} per s] -> " + (flag ? "affected" : "ignored (see the Weapons toggles and OnlyArbalest)")));
			}
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !flag)
			{
				ResetState(null, null);
				return true;
			}
			if ((Object)(object)player != (Object)(object)s_player || val != s_weapon)
			{
				ResetState(player, val);
			}
			bool attack = ((Character)player).m_attack;
			bool flag2 = ReadTrigger(player);
			Diag.Log("trigger", $"Trigger: held={((Character)player).m_attackHold} want={flag2} latched={s_latchedUntilRelease} " + "mode=" + (WeaponRules.UsesVanillaReload(val) ? "vanilla-reload" : "machine"));
			if (WeaponRules.UsesVanillaReload(val))
			{
				s_firing = false;
				if (!MachineArbalestPlugin.RequireHold.Value)
				{
					inputOverride = new InputOverride
					{
						Active = true,
						Attack = ((Character)player).m_attack,
						AttackHold = ((Character)player).m_attackHold
					};
					((Character)player).m_attackHold = flag2;
					if (!flag2 && attack)
					{
						((Character)player).m_attack = false;
					}
					if (flag2 && !HasAmmo(player, val))
					{
						StopFiring(player, val);
						((Character)player).m_attackHold = false;
					}
				}
				return true;
			}
			MachineTick(player, val, flag2);
			return false;
		}

		public static void AfterAttackInput(Player player, InputOverride inputOverride)
		{
			if (inputOverride.Active)
			{
				((Character)player).m_attack = inputOverride.Attack;
				((Character)player).m_attackHold = inputOverride.AttackHold;
			}
		}

		private static bool ReadTrigger(Player player)
		{
			if (MachineArbalestPlugin.RequireHold.Value)
			{
				s_toggledOn = false;
				if (!((Character)player).m_attackHold)
				{
					s_latchedUntilRelease = false;
					return false;
				}
				return !s_latchedUntilRelease;
			}
			if (((Character)player).m_attack)
			{
				s_toggledOn = !s_toggledOn;
			}
			if (s_toggledOn && IsUiOpen())
			{
				s_toggledOn = false;
			}
			return s_toggledOn;
		}

		private static void MachineTick(Player player, ItemData weapon, bool wantFire)
		{
			KeepLoaded(player, weapon);
			if (!wantFire)
			{
				if (s_firing)
				{
					Diag.Log("burst", $"Burst ended: {s_burstShots} shots");
					Diag.Forget("shot");
				}
				s_firing = false;
				return;
			}
			float fixedTime = Time.fixedTime;
			if (!s_firing)
			{
				s_burstShots = 0;
				Diag.Forget("burst");
				s_firing = true;
				s_nextShotTime = fixedTime;
			}
			if (!(fixedTime + 0.0001f < s_nextShotTime))
			{
				string blockedBy;
				ShotResult shotResult = TryFireShot(player, weapon, out blockedBy);
				Diag.Log("shot", (shotResult == ShotResult.Blocked) ? ("Shot blocked: " + blockedBy) : $"Shot: {shotResult}");
				switch (shotResult)
				{
				case ShotResult.Fired:
					s_burstShots++;
					s_nextShotTime = Mathf.Max(s_nextShotTime + WeaponRules.FireInterval(weapon), fixedTime);
					break;
				case ShotResult.Blocked:
					s_nextShotTime = fixedTime;
					break;
				case ShotResult.OutOfAmmo:
					StopFiring(player, weapon);
					break;
				}
			}
		}

		private static void KeepLoaded(Player player, ItemData weapon)
		{
			if (weapon.m_shared.m_attack.m_requiresReload)
			{
				player.CancelReloadAction();
				player.SetWeaponLoaded(weapon);
			}
		}

		private static ShotResult TryFireShot(Player player, ItemData weapon, out string blockedBy)
		{
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_030e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			blockedBy = (((Character)player).InAttack() ? "attack animation" : (((Character)player).InDodge() ? "dodging" : ((!((Character)player).CanMove()) ? "can't move" : (((Character)player).IsKnockedBack() ? "knocked back" : (((Character)player).IsStaggering() ? "staggering" : (((Character)player).InMinorAction() ? "minor action (equip/reload/eat animation)" : ((!weapon.HavePrimaryAttack()) ? "weapon has no primary attack" : (VolleyInProgress(player) ? "previous volley still firing" : null))))))));
			if (blockedBy != null)
			{
				return ShotResult.Blocked;
			}
			Attack val = weapon.m_shared.m_attack.Clone();
			val.m_character = (Humanoid)(object)player;
			val.m_weapon = weapon;
			val.m_requiresReload = false;
			val.m_loopingAttack = false;
			WeaponRules.ApplyMultipliers(val);
			if (val.m_cantUseInDungeon && ((Character)player).InInterior())
			{
				((Character)player).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null, false);
				blockedBy = "can't use in dungeon";
				return ShotResult.Blocked;
			}
			float attackStamina = val.GetAttackStamina();
			float attackEitr = val.GetAttackEitr();
			GetCycleCost(player, weapon, out var stamina, out var eitr);
			float num = attackStamina + stamina;
			float num2 = attackEitr + eitr;
			if (num > 0f && !((Character)player).HaveStamina(num + 0.1f))
			{
				Hud instance = Hud.instance;
				if (instance != null)
				{
					instance.StaminaBarEmptyFlash();
				}
				blockedBy = $"not enough stamina (needs {num:0.#})";
				return ShotResult.Blocked;
			}
			if (!HasAmmo(player, weapon))
			{
				return ShotResult.OutOfAmmo;
			}
			if (!((Character)player).TryUseEitr(num2))
			{
				blockedBy = "not enough eitr";
				return ShotResult.Blocked;
			}
			float attackHealth = val.GetAttackHealth();
			if (attackHealth > 0f && val.m_attackHealthLowBlockUse && !((Character)player).HaveHealth(attackHealth + 0.1f))
			{
				Hud instance2 = Hud.instance;
				if (instance2 != null)
				{
					instance2.FlashHealthBar();
				}
			}
			Attack.EquipAmmoItem((Humanoid)(object)player, weapon);
			((Component)player).transform.rotation = ((Character)player).GetLookYaw();
			((Character)player).m_body.rotation = ((Component)player).transform.rotation;
			if (((Humanoid)player).m_currentAttack != null)
			{
				((Humanoid)player).m_currentAttack.Stop();
				((Humanoid)player).m_previousAttack = ((Humanoid)player).m_currentAttack;
			}
			((Humanoid)player).m_currentAttack = val;
			((Humanoid)player).m_currentAttackIsSecondary = false;
			((Humanoid)player).m_lastCombatTimer = 0f;
			val.m_zanim = ((Character)player).m_zanim;
			val.m_animEvent = ((Character)player).m_animEvent;
			float num3 = (val.m_bowDraw ? 1f : 0f);
			val.StartWithoutAnimation((Humanoid)(object)player, ((Character)player).m_body, ((Humanoid)player).m_visEquipment, weapon, num3);
			bool flag = !val.m_perBurstResourceUsage;
			((Character)player).UseStamina(stamina + (flag ? attackStamina : 0f));
			((Character)player).UseEitr(eitr + (flag ? attackEitr : 0f));
			if (flag)
			{
				((Character)player).UseHealth(Mathf.Min(((Character)player).GetHealth() - 1f, attackHealth));
			}
			Diag.Log("cost", $"Shot cost: stamina {attackStamina:0.##} attack + {stamina:0.##} reload/draw, " + $"eitr {attackEitr:0.##} attack + {eitr:0.##} reload/draw");
			Transform attackOrigin = val.GetAttackOrigin();
			weapon.m_shared.m_startEffect.Create(attackOrigin.position, ((Component)player).transform.rotation, attackOrigin, 1f, -1, ((Character)player).GetZDOID());
			val.m_startEffect.Create(attackOrigin.position, ((Component)player).transform.rotation, attackOrigin, 1f, -1, ((Character)player).GetZDOID());
			((Character)player).AddNoise(val.m_attackStartNoise);
			return ShotResult.Fired;
		}

		private static void GetCycleCost(Player player, ItemData weapon, out float stamina, out float eitr)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			Attack attack = weapon.m_shared.m_attack;
			stamina = 0f;
			eitr = 0f;
			if (attack.m_requiresReload)
			{
				float weaponLoadingTime = weapon.GetWeaponLoadingTime();
				stamina += Mathf.Max(0f, attack.m_reloadStaminaDrain) * weaponLoadingTime;
				eitr += Mathf.Max(0f, attack.m_reloadEitrDrain) * weaponLoadingTime;
			}
			if (attack.m_bowDraw)
			{
				float skillFactor = ((Character)player).GetSkillFactor(weapon.m_shared.m_skillType);
				float num = Mathf.Lerp(attack.m_drawDurationMin, attack.m_drawDurationMin * 0.2f, skillFactor);
				float num2 = weapon.GetDrawStaminaDrain() * num;
				num2 += num2 * ((Character)player).GetEquipmentAttackStaminaModifier();
				((Character)player).m_seman.ModifyAttackStaminaUsage(num2, ref num2, true);
				stamina += num2;
				eitr += weapon.GetDrawEitrDrain() * num;
			}
			stamina *= MachineArbalestPlugin.StaminaCostMultiplier.Value;
			eitr *= MachineArbalestPlugin.EitrCostMultiplier.Value;
		}

		private static bool VolleyInProgress(Player player)
		{
			Attack currentAttack = ((Humanoid)player).m_currentAttack;
			if (currentAttack != null && !currentAttack.m_attackDone && currentAttack.m_projectileAttackStarted)
			{
				return currentAttack.m_projectileBurstsFired < currentAttack.m_projectileBursts;
			}
			return false;
		}

		private static bool HasAmmo(Player player, ItemData weapon)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			if (string.IsNullOrEmpty(weapon.m_shared.m_ammoType))
			{
				return true;
			}
			ItemData val = Attack.FindAmmo((Humanoid)(object)player, weapon);
			if (val == null)
			{
				return false;
			}
			if ((int)val.m_shared.m_itemType == 2)
			{
				return ((Humanoid)player).CanConsumeItem(val, false);
			}
			return true;
		}

		private static void StopFiring(Player player, ItemData weapon)
		{
			s_firing = false;
			s_toggledOn = false;
			s_latchedUntilRelease = true;
			((Character)player).Message((MessageType)2, "$msg_outof " + weapon.m_shared.m_ammoType, 0, (Sprite)null, false);
		}

		private static void ResetState(Player player, ItemData weapon)
		{
			s_player = player;
			s_weapon = weapon;
			s_firing = false;
			s_latchedUntilRelease = false;
			s_toggledOn = false;
			s_nextShotTime = 0f;
		}

		private static bool IsUiOpen()
		{
			if (!GameCamera.InFreeFly() && (!((Object)(object)Chat.instance != (Object)null) || !Chat.instance.HasFocus()) && !Menu.IsVisible() && !Console.IsVisible() && !TextInput.IsVisible() && !Minimap.InTextInput() && !Minimap.IsOpen() && !InventoryGui.IsVisible() && !StoreGui.IsVisible() && !Hud.IsPieceSelectionVisible())
			{
				return Hud.InRadial();
			}
			return true;
		}
	}
	internal static class Diag
	{
		private static readonly Dictionary<string, string> s_last = new Dictionary<string, string>();

		public static void Log(string channel, string message)
		{
			if (MachineArbalestPlugin.DebugLogging.Value && (!s_last.TryGetValue(channel, out var value) || !(value == message)))
			{
				s_last[channel] = message;
				MachineArbalestPlugin.Log.LogInfo((object)message);
			}
		}

		public static void Forget(string channel)
		{
			s_last.Remove(channel);
		}
	}
	[BepInPlugin("com.oddhatter.machinearbalest", "Gatling", "2.0.0")]
	[BepInProcess("valheim.exe")]
	public sealed class MachineArbalestPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.oddhatter.machinearbalest";

		public const string Name = "Gatling";

		public const string Version = "2.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> FireInterval;

		internal static ConfigEntry<float> BowFireInterval;

		internal static ConfigEntry<float> StaffFireInterval;

		internal static ConfigEntry<bool> RequireHold;

		internal static ConfigEntry<float> StaminaCostMultiplier;

		internal static ConfigEntry<float> EitrCostMultiplier;

		internal static ConfigEntry<float> AmmoCostMultiplier;

		internal static ConfigEntry<float> DamageMultiplier;

		internal static ConfigEntry<bool> Crossbows;

		internal static ConfigEntry<bool> Bows;

		internal static ConfigEntry<bool> ElementalStaffs;

		internal static ConfigEntry<bool> OnlyArbalest;

		internal static ConfigEntry<float> RecoilMultiplier;

		internal static ConfigEntry<float> DurabilityMultiplier;

		internal static ConfigEntry<bool> UseVanillaReload;

		internal static ConfigEntry<bool> DebugLogging;

		private const float ConfigReloadDelay = 0.5f;

		private Harmony _harmony;

		private FileSystemWatcher _configWatcher;

		private volatile bool _configFileChanged;

		private float _configReloadAt = -1f;

		private bool _configLockedLogged;

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected O, but got Unknown
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Expected O, but got Unknown
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Expected O, but got Unknown
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Expected O, but got Unknown
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Expected O, but got Unknown
			//IL_0336: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. When false the mod changes nothing.");
			FireInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FireInterval", 0.1f, new ConfigDescription("Crossbows: seconds between automatic shots (0.10 = 10 shots/second). Ignored when UseVanillaReload is true. Shots are evaluated once per physics step (~0.02s), so values below that fire once per step (~50/s).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 10f), Array.Empty<object>()));
			BowFireInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "BowFireInterval", 0.2f, new ConfigDescription("Bows: seconds between automatic shots (0.20 = 5 arrows/second). Every arrow flies as if fully drawn.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 10f), Array.Empty<object>()));
			StaffFireInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StaffFireInterval", 0.3f, new ConfigDescription("Elemental staffs (Dundr, Staff of Embers, Staff of Frost, Staff of Fracturing): seconds between automatic casts (0.30 = about 3/second). Each cast costs eitr; see EitrCostMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 10f), Array.Empty<object>()));
			RequireHold = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RequireHold", true, "true: fire only while primary attack is held; releasing stops immediately. false: a click toggles automatic fire on/off (also stops on weapon change, no bolts, death, or opening any menu).");
			StaminaCostMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "StaminaCostMultiplier", 1f, new ConfigDescription("Multiplier on the stamina each shot costs. Auto-fire skips the reload (crossbows) and the draw (bows), so each shot is charged what that reload or draw would have cost, plus the attack itself. 1 = vanilla, 0.5 = half, 0 = free.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			EitrCostMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "EitrCostMultiplier", 1f, new ConfigDescription("Multiplier on the eitr each shot costs (staffs), including any reload or draw eitr a weapon uses. 1 = vanilla, 0.5 = half, 0 = free.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			AmmoCostMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "AmmoCostMultiplier", 1f, new ConfigDescription("Bolts/arrows used per shot. 1 = vanilla (one per shot), 0.1 = one every 10 shots, 0 = never used up (you still need at least one in your inventory; its type still sets the damage and effects).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			DamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "DamageMultiplier", 1f, new ConfigDescription("Multiplier on projectile damage. 1 = unchanged.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
			RecoilMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "RecoilMultiplier", 1f, new ConfigDescription("Multiplier on the pushback the shooter receives per shot. 1 = vanilla, 0 = none.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			DurabilityMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Balance", "DurabilityMultiplier", 1f, new ConfigDescription("Multiplier on weapon durability lost per shot. 1 = vanilla, 0.1 = a tenth (offsets a 10x fire rate), 0 = no wear.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			Crossbows = ((BaseUnityPlugin)this).Config.Bind<bool>("Weapons", "Crossbows", true, "Automatic fire for crossbows (Arbalest, Ripper, gold crossbows, modded ones). See also OnlyArbalest.");
			Bows = ((BaseUnityPlugin)this).Config.Bind<bool>("Weapons", "Bows", true, "Automatic fire for bows. Arrows fly as if fully drawn, and each one costs the stamina of a full draw.");
			ElementalStaffs = ((BaseUnityPlugin)this).Config.Bind<bool>("Weapons", "ElementalStaffs", true, "Automatic fire for elemental magic staffs (Dundr, Staff of Embers, Staff of Frost, Staff of Fracturing). Blood magic staffs (summons, shields) are never affected.");
			OnlyArbalest = ((BaseUnityPlugin)this).Config.Bind<bool>("Weapons", "OnlyArbalest", false, "Crossbows only. false: every crossbow (every weapon using the Crossbows skill) is affected. true: only the vanilla Arbalest.");
			UseVanillaReload = ((BaseUnityPlugin)this).Config.Bind<bool>("Weapons", "UseVanillaReload", false, "Crossbows only (bows and staffs always use machine mode). false: machine-gun mode, shots are paced by FireInterval and no reload occurs. true: the normal reload (including FastArbalest's) paces the shots; holding still auto-fires and the multipliers above still apply.");
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DebugLogging", false, "Write what the mod sees (equipped weapon, trigger state, why a shot was or wasn't fired) to LogOutput.log. Only logs when something changes.");
			_harmony = new Harmony("com.oddhatter.machinearbalest");
			_harmony.PatchAll(typeof(MachineArbalestPlugin).Assembly);
			foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
			{
				Log.LogInfo((object)("Patched " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name));
			}
			WatchConfigFile();
			Log.LogInfo((object)"Gatling 2.0.0 loaded.");
		}

		private void Update()
		{
			if (_configFileChanged)
			{
				_configFileChanged = false;
				_configReloadAt = Time.realtimeSinceStartup + 0.5f;
			}
			if (_configReloadAt >= 0f && Time.realtimeSinceStartup >= _configReloadAt)
			{
				_configReloadAt = -1f;
				ReloadConfigFromDisk();
			}
		}

		private void OnDestroy()
		{
			_configWatcher?.Dispose();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void WatchConfigFile()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			_configWatcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath))
			{
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
				IncludeSubdirectories = false
			};
			_configWatcher.Changed += OnConfigFileEvent;
			_configWatcher.Created += OnConfigFileEvent;
			_configWatcher.Renamed += OnConfigFileEvent;
			_configWatcher.EnableRaisingEvents = true;
		}

		private void OnConfigFileEvent(object sender, FileSystemEventArgs e)
		{
			_configFileChanged = true;
		}

		private void ReloadConfigFromDisk()
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return;
			}
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				_configLockedLogged = false;
				Log.LogInfo((object)"Configuration reloaded from disk.");
			}
			catch (IOException ex)
			{
				if (!_configLockedLogged)
				{
					_configLockedLogged = true;
					Log.LogWarning((object)("Config file busy, will retry: " + ex.Message));
				}
				_configReloadAt = Time.realtimeSinceStartup + 0.5f;
			}
			catch (Exception ex2)
			{
				Log.LogError((object)("Failed to reload configuration: " + ex2.Message));
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "PlayerAttackInput")]
	internal static class Player_PlayerAttackInput_Patch
	{
		private static bool Prefix(Player __instance, out AutoFire.InputOverride __state)
		{
			return AutoFire.BeforeAttackInput(__instance, out __state);
		}

		private static void Postfix(Player __instance, AutoFire.InputOverride __state)
		{
			AutoFire.AfterAttackInput(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(Attack), "Start")]
	internal static class Attack_Start_Patch
	{
		private static void Prefix(Attack __instance, Humanoid character, ItemData weapon)
		{
			if (WeaponRules.AppliesTo((Character)(object)character, weapon))
			{
				WeaponRules.ApplyMultipliers(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "GetAttackStamina")]
	internal static class Attack_GetAttackStamina_Patch
	{
		private static void Postfix(Attack __instance, ref float __result)
		{
			if (__result > 0f && WeaponRules.AppliesTo((Character)(object)__instance.m_character, __instance.m_weapon))
			{
				__result *= MachineArbalestPlugin.StaminaCostMultiplier.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "GetAttackEitr", new Type[]
	{
		typeof(Character),
		typeof(ItemData)
	})]
	internal static class Attack_GetAttackEitr_Patch
	{
		private static void Postfix(Character character, ItemData weapon, ref float __result)
		{
			if (__result > 0f && WeaponRules.AppliesTo(character, weapon))
			{
				__result *= MachineArbalestPlugin.EitrCostMultiplier.Value;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "QueueReloadAction")]
	internal static class Player_QueueReloadAction_Patch
	{
		private static void Prefix(Player __instance, out int __state)
		{
			__state = __instance.m_actionQueue.Count;
		}

		private static void Postfix(Player __instance, int __state)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Invalid comparison between Unknown and I4
			List<MinorActionData> actionQueue = __instance.m_actionQueue;
			if (actionQueue.Count > __state)
			{
				MinorActionData val = actionQueue[actionQueue.Count - 1];
				if ((int)val.m_type == 2 && WeaponRules.AppliesTo((Character)(object)__instance, val.m_item))
				{
					val.m_staminaDrain *= MachineArbalestPlugin.StaminaCostMultiplier.Value;
					val.m_eitrDrain *= MachineArbalestPlugin.EitrCostMultiplier.Value;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "ProjectileAttackTriggered")]
	internal static class Attack_ProjectileAttackTriggered_Patch
	{
		private static void Prefix(Attack __instance, out float __state)
		{
			__state = ((__instance.m_weapon != null) ? __instance.m_weapon.m_durability : 0f);
		}

		private static void Postfix(Attack __instance, float __state)
		{
			ItemData weapon = __instance.m_weapon;
			if (weapon != null && WeaponRules.AppliesTo((Character)(object)__instance.m_character, weapon))
			{
				float num = __state - weapon.m_durability;
				if (!(num <= 0f))
				{
					float num2 = __state - num * MachineArbalestPlugin.DurabilityMultiplier.Value;
					weapon.m_durability = Mathf.Max(num2, Mathf.Min(weapon.m_durability, 0f));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Attack), "UseAmmo")]
	internal static class Attack_UseAmmo_Patch
	{
		private static float s_owed;

		private static bool Prefix(Attack __instance, ref ItemData ammoItem, ref bool __result)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Invalid comparison between Unknown and I4
			ItemData weapon = __instance.m_weapon;
			float value = MachineArbalestPlugin.AmmoCostMultiplier.Value;
			if (value >= 1f || weapon == null || string.IsNullOrWhiteSpace(weapon.m_shared.m_ammoType) || !WeaponRules.AppliesTo((Character)(object)__instance.m_character, weapon))
			{
				return true;
			}
			ItemData val = Attack.FindAmmo(__instance.m_character, weapon);
			if (val == null || (int)val.m_shared.m_itemType == 2)
			{
				return true;
			}
			s_owed += value;
			if (s_owed >= 0.9999f)
			{
				s_owed = Mathf.Max(0f, s_owed - 1f);
				Diag.Log("ammo", $"Ammo: {val.m_shared.m_name} used (AmmoCostMultiplier {value:0.###})");
				return true;
			}
			Diag.Log("ammo", $"Ammo: {val.m_shared.m_name} free shot (AmmoCostMultiplier {value:0.###})");
			__instance.m_ammoItem = val;
			ammoItem = val;
			__result = true;
			return false;
		}
	}
	internal static class WeaponRules
	{
		internal enum Category
		{
			None,
			Crossbow,
			Bow,
			Staff
		}

		private const string ArbalestToken = "$item_crossbow_arbalest";

		public static Category GetCategory(ItemData weapon)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			if (weapon == null)
			{
				return Category.None;
			}
			SkillType skillType = weapon.m_shared.m_skillType;
			if ((int)skillType != 8)
			{
				if ((int)skillType != 9)
				{
					if ((int)skillType == 14)
					{
						return Category.Crossbow;
					}
					return Category.None;
				}
				return Category.Staff;
			}
			return Category.Bow;
		}

		public static bool IsAffected(ItemData weapon)
		{
			if (weapon == null || !MachineArbalestPlugin.Enabled.Value)
			{
				return false;
			}
			switch (GetCategory(weapon))
			{
			case Category.Crossbow:
				if (MachineArbalestPlugin.Crossbows.Value)
				{
					if (MachineArbalestPlugin.OnlyArbalest.Value)
					{
						return weapon.m_shared.m_name == "$item_crossbow_arbalest";
					}
					return true;
				}
				return false;
			case Category.Bow:
				return MachineArbalestPlugin.Bows.Value;
			case Category.Staff:
				return MachineArbalestPlugin.ElementalStaffs.Value;
			default:
				return false;
			}
		}

		public static bool AppliesTo(Character character, ItemData weapon)
		{
			if ((Object)(object)character != (Object)null && (Object)(object)character == (Object)(object)Player.m_localPlayer)
			{
				return IsAffected(weapon);
			}
			return false;
		}

		public static float FireInterval(ItemData weapon)
		{
			return GetCategory(weapon) switch
			{
				Category.Bow => MachineArbalestPlugin.BowFireInterval.Value, 
				Category.Staff => MachineArbalestPlugin.StaffFireInterval.Value, 
				_ => MachineArbalestPlugin.FireInterval.Value, 
			};
		}

		public static bool UsesVanillaReload(ItemData weapon)
		{
			if (MachineArbalestPlugin.UseVanillaReload.Value)
			{
				return GetCategory(weapon) == Category.Crossbow;
			}
			return false;
		}

		public static void ApplyMultipliers(Attack attack)
		{
			attack.m_damageMultiplier *= MachineArbalestPlugin.DamageMultiplier.Value;
			attack.m_recoilPushback *= MachineArbalestPlugin.RecoilMultiplier.Value;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}