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