Decompiled source of CODFishPerks v1.4.1

FishPerksMod.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
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 FishNet.Object;
using FishPerksMod.Patches;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("FishPerksMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("FishPerksMod")]
[assembly: AssemblyTitle("FishPerksMod")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace FishPerksMod
{
	public class FishPerkDefinition
	{
		public FishPerkType Type;

		public string Name;
	}
	public static class FishPerkDatabase
	{
		public static readonly Dictionary<FishPerkType, FishPerkDefinition> Definitions = new Dictionary<FishPerkType, FishPerkDefinition>
		{
			{
				FishPerkType.StoppingPower,
				new FishPerkDefinition
				{
					Type = FishPerkType.StoppingPower,
					Name = "Stopping Power"
				}
			},
			{
				FishPerkType.Juggernaut,
				new FishPerkDefinition
				{
					Type = FishPerkType.Juggernaut,
					Name = "Juggernaut"
				}
			},
			{
				FishPerkType.SpeedCola,
				new FishPerkDefinition
				{
					Type = FishPerkType.SpeedCola,
					Name = "Speed Cola"
				}
			},
			{
				FishPerkType.DoubleTap,
				new FishPerkDefinition
				{
					Type = FishPerkType.DoubleTap,
					Name = "Double Tap"
				}
			},
			{
				FishPerkType.FizzyYummy,
				new FishPerkDefinition
				{
					Type = FishPerkType.FizzyYummy,
					Name = "Fizzy Yummy"
				}
			},
			{
				FishPerkType.QuickFix,
				new FishPerkDefinition
				{
					Type = FishPerkType.QuickFix,
					Name = "Quick Fix"
				}
			},
			{
				FishPerkType.Hardline,
				new FishPerkDefinition
				{
					Type = FishPerkType.Hardline,
					Name = "Hardline"
				}
			},
			{
				FishPerkType.QuickRevive,
				new FishPerkDefinition
				{
					Type = FishPerkType.QuickRevive,
					Name = "Quick Revive"
				}
			}
		};
	}
	public static class FishPerkManager
	{
		public const float StoppingPowerDamageMulti = 1.55f;

		public const float JuggernautDamageTakenMulti = 0.1f;

		public const float SpeedColaReloadMulti = 1.4f;

		public const float DoubleTapFireRateMulti = 1.4f;

		public const float FizzyYummySpeedMulti = 1.3f;

		public const int QuickFixRestorePercent = 5;

		public const float HardlineMultiplier = 1.35f;

		public const float BuffDurationSeconds = 120f;

		public static bool VerboseLogging = true;

		private static readonly Dictionary<Creature, FishPerkType> _sessionPerkCache = new Dictionary<Creature, FishPerkType>();

		private static readonly List<FishPerkType> _perkBag = new List<FishPerkType>();

		private static int _perkBagCycle;

		private static readonly Dictionary<int, FishPerkType> _networkPerkByObjectId = new Dictionary<int, FishPerkType>();

		private static readonly Dictionary<Player, (FishPerkType perk, float expireTime)> _activeBuffs = new Dictionary<Player, (FishPerkType, float)>();

		private static readonly Dictionary<Player, float> _quickFixNextTick = new Dictionary<Player, float>();

		public static ulong CurrentlyLoadingSteamId;

		public static SavedPlayer CurrentlyLoadingSavedPlayer;

		internal static void Announce(string message)
		{
			if (!((Object)(object)Server.Instance == (Object)null) && ((NetworkBehaviour)Server.Instance).IsServerInitialized)
			{
				if ((Object)(object)OnlineChatManager.Instance != (Object)null)
				{
					ulong num = (((Object)(object)Player.LocalPlayer != (Object)null) ? Player.LocalPlayer.SteamID : 0);
					OnlineChatManager.Instance.SendChatMessage(num, message);
				}
				else
				{
					ChatManager.ChatMessage(message);
				}
			}
		}

		public static void ClearSession()
		{
			_sessionPerkCache.Clear();
			_networkPerkByObjectId.Clear();
			_perkBag.Clear();
			_perkBagCycle = 0;
			_activeBuffs.Clear();
			_quickFixNextTick.Clear();
		}

		public static FishPerkType GetOrAssignPerk(Creature creature)
		{
			if ((Object)(object)creature == (Object)null)
			{
				return FishPerkType.StoppingPower;
			}
			if (_networkPerkByObjectId.TryGetValue(((NetworkBehaviour)creature).ObjectId, out var value))
			{
				_sessionPerkCache[creature] = value;
				return value;
			}
			if (_sessionPerkCache.TryGetValue(creature, out var value2))
			{
				return value2;
			}
			if (!IsServerAuthoritative())
			{
				return FishPerkType.StoppingPower;
			}
			FishPerkType fishPerkType = TakeNextPerkFromBag();
			_sessionPerkCache[creature] = fishPerkType;
			if (VerboseLogging)
			{
				Plugin.Log.LogInfo((object)$"[FishPerksMod] Assigned perk '{fishPerkType}' to '{SafeGetName(creature)}' (server perk bag).");
			}
			return fishPerkType;
		}

		public static void AssignSpecificPerk(Creature creature, FishPerkType perk)
		{
			if (!((Object)(object)creature == (Object)null))
			{
				_sessionPerkCache[creature] = perk;
				if (VerboseLogging)
				{
					Plugin.Log.LogInfo((object)$"[FishPerksMod] Restored persisted perk '{perk}' on '{SafeGetName(creature)}'.");
				}
			}
		}

		private static bool IsServerAuthoritative()
		{
			if ((Object)(object)Server.Instance != (Object)null)
			{
				return ((NetworkBehaviour)Server.Instance).IsServerInitialized;
			}
			return false;
		}

		private static FishPerkType TakeNextPerkFromBag()
		{
			FishPerkType[] array = (FishPerkType[])Enum.GetValues(typeof(FishPerkType));
			if (array.Length == 0)
			{
				return FishPerkType.StoppingPower;
			}
			if (_perkBag.Count == 0)
			{
				_perkBag.AddRange(array);
				Random random = new Random(1179210568 + _perkBagCycle++);
				for (int num = _perkBag.Count - 1; num > 0; num--)
				{
					int index = random.Next(num + 1);
					FishPerkType value = _perkBag[num];
					_perkBag[num] = _perkBag[index];
					_perkBag[index] = value;
				}
			}
			int index2 = _perkBag.Count - 1;
			FishPerkType result = _perkBag[index2];
			_perkBag.RemoveAt(index2);
			return result;
		}

		private static string SafeGetName(Creature c)
		{
			try
			{
				return ((Item)c).GetName();
			}
			catch
			{
				return ((Object)c).name;
			}
		}

		public static string GetDisplayName(FishPerkType perk)
		{
			if (!FishPerkDatabase.Definitions.TryGetValue(perk, out var value))
			{
				return perk.ToString();
			}
			return value.Name;
		}

		public static string GetDescription(FishPerkType perk)
		{
			return perk switch
			{
				FishPerkType.StoppingPower => $"Increases bullet damage by {54.999996f:0}%.", 
				FishPerkType.Juggernaut => $"Reduces incoming damage by {90f:0}%.", 
				FishPerkType.SpeedCola => $"Increases reload speed by {39.999996f:0}%.", 
				FishPerkType.DoubleTap => $"Increases fire rate by {39.999996f:0}%.", 
				FishPerkType.FizzyYummy => $"Increases movement speed by {29.999996f:0}%.", 
				FishPerkType.QuickFix => $"Restores {5}% of health and hunger every second.", 
				FishPerkType.Hardline => $"Increases all trickshot score multipliers by {35.000004f:0}%.", 
				FishPerkType.QuickRevive => "Revives you during a boss fight while another player is still alive.", 
				_ => string.Empty, 
			};
		}

		public static void ApplyBuff(Player player, FishPerkType perk, float? durationOverrideSeconds = null)
		{
			if (!((Object)(object)player == (Object)null))
			{
				float num = durationOverrideSeconds ?? 120f;
				_activeBuffs[player] = (perk, Time.time + num);
				if (perk == FishPerkType.QuickFix)
				{
					_quickFixNextTick[player] = Time.time + 1f;
				}
				if (VerboseLogging)
				{
					Plugin.Log.LogInfo((object)$"[FishPerksMod] Applied buff '{perk}' to '{SafePlayerName(player)}' for {num:0}s.");
				}
			}
		}

		public static void SyncBuff(Player player, FishPerkType perk, float durationSeconds)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)OnlineChatManager.Instance == (Object)null))
			{
				string text = string.Format(CultureInfo.InvariantCulture, "[FP_SYNC_BUFF]|{0}|{1}|{2:0.###}", player.SteamID, (int)perk, durationSeconds);
				OnlineChatManager.Instance.SendChatMessage(player.SteamID, text);
			}
		}

		public static void SyncPerkAssignment(Creature creature, FishPerkType perk)
		{
			if (!((Object)(object)creature == (Object)null) && !((Object)(object)OnlineChatManager.Instance == (Object)null))
			{
				OnlineChatManager instance = OnlineChatManager.Instance;
				long num = 0L;
				string text = ((NetworkBehaviour)creature).ObjectId.ToString();
				int num2 = (int)perk;
				instance.SendChatMessage((ulong)num, "[FP_SYNC_FISH]|" + text + "|" + num2);
			}
		}

		public static void SyncQuickRevive(Player player)
		{
			if (!((Object)(object)player == (Object)null))
			{
				QuickReviveSignals.MarkPending(player.SteamID);
				if (!((Object)(object)OnlineChatManager.Instance == (Object)null))
				{
					OnlineChatManager.Instance.SendChatMessage(player.SteamID, "[FP_SYNC_REVIVE]|" + player.SteamID);
				}
			}
		}

		internal static bool TryHandleNetworkPayload(string message)
		{
			if (string.IsNullOrEmpty(message))
			{
				return false;
			}
			string[] array = message.Split(new char[1] { '|' });
			if (array.Length == 4 && array[0] == "[FP_SYNC_BUFF]" && ulong.TryParse(array[1], out var steamId) && int.TryParse(array[2], out var result) && float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && Enum.IsDefined(typeof(FishPerkType), result))
			{
				Player val = PlayerManager.Players?.FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.SteamID == steamId));
				if ((Object)(object)val != (Object)null)
				{
					ApplyBuff(val, (FishPerkType)result, result2);
				}
				return true;
			}
			if (array.Length == 3 && array[0] == "[FP_SYNC_FISH]" && int.TryParse(array[1], out var result3) && int.TryParse(array[2], out var result4) && Enum.IsDefined(typeof(FishPerkType), result4))
			{
				_networkPerkByObjectId[result3] = (FishPerkType)result4;
				return true;
			}
			if (array.Length == 2 && array[0] == "[FP_SYNC_REVIVE]" && ulong.TryParse(array[1], out var reviveSteamId))
			{
				QuickReviveSignals.MarkPending(reviveSteamId);
				QuickReviveSignals.Trigger(PlayerManager.Players?.FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.SteamID == reviveSteamId)));
				return true;
			}
			return false;
		}

		public static bool TryGetActiveBuff(Player player, out FishPerkType perk)
		{
			perk = FishPerkType.StoppingPower;
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			if (!_activeBuffs.TryGetValue(player, out (FishPerkType, float) value))
			{
				return false;
			}
			if (Time.time >= value.Item2)
			{
				_activeBuffs.Remove(player);
				_quickFixNextTick.Remove(player);
				Announce(SafePlayerName(player) + "'s " + GetDisplayName(value.Item1) + " effect has expired.");
				if (VerboseLogging)
				{
					Plugin.Log.LogInfo((object)$"[FishPerksMod] Buff '{value.Item1}' expired for '{SafePlayerName(player)}'.");
				}
				return false;
			}
			(perk, _) = value;
			return true;
		}

		public static bool HasActivePerk(Player player, FishPerkType perk)
		{
			if (TryGetActiveBuff(player, out var perk2))
			{
				return perk2 == perk;
			}
			return false;
		}

		public static float GetRemainingBuffTime(Player player)
		{
			if ((Object)(object)player == (Object)null || !_activeBuffs.TryGetValue(player, out (FishPerkType, float) value))
			{
				return 0f;
			}
			return Mathf.Max(0f, value.Item2 - Time.time);
		}

		public static void UpdatePeriodicEffects()
		{
			foreach (KeyValuePair<Player, (FishPerkType, float)> item in new List<KeyValuePair<Player, (FishPerkType, float)>>(_activeBuffs))
			{
				if (TryGetActiveBuff(item.Key, out var perk) && perk == FishPerkType.QuickFix && !item.Key.Dying.IsDead && (!_quickFixNextTick.TryGetValue(item.Key, out var value) || Time.time >= value))
				{
					item.Key.Vitals.Heal(5);
					item.Key.Vitals.RestoreFullness(5);
					_quickFixNextTick[item.Key] = Time.time + 1f;
				}
			}
		}

		private static string SafePlayerName(Player p)
		{
			try
			{
				return p.SteamName;
			}
			catch
			{
				return ((Object)p).name;
			}
		}
	}
	public enum FishPerkType
	{
		StoppingPower,
		Juggernaut,
		SpeedCola,
		DoubleTap,
		FizzyYummy,
		QuickFix,
		Hardline,
		QuickRevive
	}
	internal sealed class PerkHud : MonoBehaviour
	{
		private TextMeshProUGUI _text;

		public static void Create()
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)PlayerUI.CanvasTextPrefab == (Object)null) && !((Object)(object)PlayerUI.FXCanvasTrans == (Object)null) && !((Object)(object)PlayerUI.FXCanvasTrans.Find("FishPerksActivePerk") != (Object)null))
			{
				TextMeshProUGUI val = Object.Instantiate<TextMeshProUGUI>(PlayerUI.CanvasTextPrefab, PlayerUI.FXCanvasTrans);
				((Object)val).name = "FishPerksActivePerk";
				((Graphic)val).raycastTarget = false;
				((TMP_Text)val).alignment = (TextAlignmentOptions)257;
				((TMP_Text)val).enableWordWrapping = false;
				((TMP_Text)val).fontSize = ((TMP_Text)val).fontSize * 0.72f;
				((Graphic)val).color = Color32.op_Implicit(new Color32(byte.MaxValue, (byte)220, (byte)120, byte.MaxValue));
				RectTransform rectTransform = ((TMP_Text)val).rectTransform;
				rectTransform.anchorMin = new Vector2(0f, 1f);
				rectTransform.anchorMax = new Vector2(0f, 1f);
				rectTransform.pivot = new Vector2(0f, 1f);
				rectTransform.anchoredPosition = new Vector2(20f, -20f);
				rectTransform.sizeDelta = new Vector2(320f, 32f);
				PerkHud perkHud = ((Component)val).gameObject.AddComponent<PerkHud>();
				perkHud._text = val;
				perkHud.UpdateText();
			}
		}

		private void Update()
		{
			UpdateText();
		}

		private void UpdateText()
		{
			Player localPlayer = Player.LocalPlayer;
			if (Plugin.HudEnabled == null || !Plugin.HudEnabled.Value || (Object)(object)localPlayer == (Object)null || !FishPerkManager.TryGetActiveBuff(localPlayer, out var perk))
			{
				((Behaviour)_text).enabled = false;
				return;
			}
			((Behaviour)_text).enabled = true;
			((TMP_Text)_text).text = $"<b>{FishPerkManager.GetDisplayName(perk)}</b>  <color=#FFFFFF>{FishPerkManager.GetRemainingBuffTime(localPlayer):0}s</color>";
		}
	}
	[HarmonyPatch(typeof(PlayerUI), "InitializeLocal")]
	internal static class PerkHudInitializationPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			PerkHud.Create();
		}
	}
	internal static class PersistenceStore
	{
		[Serializable]
		private class Entry
		{
			public ulong SteamId;

			public byte Slot;

			public string Perk;
		}

		[Serializable]
		private class SaveFile
		{
			public List<Entry> Entries = new List<Entry>();
		}

		private static readonly Dictionary<ulong, Dictionary<byte, FishPerkType>> _data = new Dictionary<ulong, Dictionary<byte, FishPerkType>>();

		private static string _currentSaveName;

		private static string GetPath(string saveName)
		{
			string text = Path.Combine(Paths.ConfigPath, "FishPerksMod");
			Directory.CreateDirectory(text);
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			string text2 = string.Join("_", saveName.Split(invalidFileNameChars, StringSplitOptions.None));
			return Path.Combine(text, text2 + "_perks.json");
		}

		public static void Load(string saveName)
		{
			_currentSaveName = saveName;
			_data.Clear();
			try
			{
				string path = GetPath(saveName);
				if (!File.Exists(path))
				{
					Plugin.Log.LogInfo((object)("[FishPerksMod] No persisted fish perk data found for save '" + saveName + "' (this is normal the first time)."));
					return;
				}
				SaveFile saveFile = JsonUtility.FromJson<SaveFile>(File.ReadAllText(path));
				if (saveFile?.Entries == null)
				{
					return;
				}
				int num = 0;
				foreach (Entry entry in saveFile.Entries)
				{
					if (Enum.TryParse<FishPerkType>(entry.Perk, out var result))
					{
						if (!_data.TryGetValue(entry.SteamId, out var value))
						{
							value = new Dictionary<byte, FishPerkType>();
							_data[entry.SteamId] = value;
						}
						value[entry.Slot] = result;
						num++;
					}
				}
				Plugin.Log.LogInfo((object)$"[FishPerksMod] Loaded {num} persisted fish perk(s) for save '{saveName}'.");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] Failed to load persisted fish perks: {arg}");
			}
		}

		public static void Set(ulong steamId, byte slot, FishPerkType perk)
		{
			if (!_data.TryGetValue(steamId, out var value))
			{
				value = new Dictionary<byte, FishPerkType>();
				_data[steamId] = value;
			}
			value[slot] = perk;
		}

		public static void Remove(ulong steamId, byte slot)
		{
			if (_data.TryGetValue(steamId, out var value))
			{
				value.Remove(slot);
			}
		}

		public static bool TryGet(ulong steamId, byte slot, out FishPerkType perk)
		{
			perk = FishPerkType.StoppingPower;
			if (_data.TryGetValue(steamId, out var value))
			{
				return value.TryGetValue(slot, out perk);
			}
			return false;
		}

		public static void Save(string saveName = null)
		{
			saveName = saveName ?? _currentSaveName;
			if (string.IsNullOrEmpty(saveName))
			{
				return;
			}
			try
			{
				SaveFile saveFile = new SaveFile();
				foreach (KeyValuePair<ulong, Dictionary<byte, FishPerkType>> datum in _data)
				{
					foreach (KeyValuePair<byte, FishPerkType> item in datum.Value)
					{
						saveFile.Entries.Add(new Entry
						{
							SteamId = datum.Key,
							Slot = item.Key,
							Perk = item.Value.ToString()
						});
					}
				}
				string contents = JsonUtility.ToJson((object)saveFile, true);
				File.WriteAllText(GetPath(saveName), contents);
				if (FishPerkManager.VerboseLogging)
				{
					Plugin.Log.LogInfo((object)$"[FishPerksMod] Persisted {saveFile.Entries.Count} fish perk(s) for save '{saveName}'.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] Failed to save persisted fish perks: {arg}");
			}
		}
	}
	[BepInPlugin("com.fishperksmod.fishperks", "CODFish Perks", "1.4.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.fishperksmod.fishperks";

		public const string PluginName = "CODFish Perks";

		public const string PluginVersion = "1.4.1";

		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static ConfigEntry<bool> HudEnabled { get; private set; }

		private void Awake()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			HudEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "Show Active Perk HUD", true, "Show or hide the active perk and remaining time on screen.");
			_harmony = new Harmony("com.fishperksmod.fishperks");
			_harmony.PatchAll();
			Log.LogInfo((object)"CODFish Perks 1.4.1 loaded.");
		}

		private void Update()
		{
			FishPerkManager.UpdatePeriodicEffects();
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Instance = null;
		}
	}
	internal static class ReflectionUtil
	{
		private static readonly Dictionary<(Type, string), FieldInfo> _cache = new Dictionary<(Type, string), FieldInfo>();

		private static FieldInfo FindField(Type type, string name)
		{
			(Type, string) key = (type, name);
			if (_cache.TryGetValue(key, out var value))
			{
				return value;
			}
			FieldInfo fieldInfo = null;
			Type type2 = type;
			while (type2 != null)
			{
				fieldInfo = type2.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (fieldInfo != null)
				{
					break;
				}
				type2 = type2.BaseType;
			}
			_cache[key] = fieldInfo;
			if (fieldInfo == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[FishPerksMod] Reflection: could not find field '" + name + "' on type '" + type.FullName + "' or any of its base types. This usually means the field was renamed in a game update."));
				}
			}
			return fieldInfo;
		}

		public static T GetField<T>(object instance, string fieldName)
		{
			if (instance == null)
			{
				return default(T);
			}
			FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
			if (fieldInfo == null)
			{
				return default(T);
			}
			try
			{
				return (T)fieldInfo.GetValue(instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[FishPerksMod] Reflection: failed to read field '" + fieldName + "' on '" + instance.GetType().Name + "': " + ex.Message));
				}
				return default(T);
			}
		}

		public static void SetField<T>(object instance, string fieldName, T value)
		{
			if (instance == null)
			{
				return;
			}
			FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
			if (fieldInfo == null)
			{
				return;
			}
			try
			{
				fieldInfo.SetValue(instance, value);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[FishPerksMod] Reflection: failed to set field '" + fieldName + "' on '" + instance.GetType().Name + "': " + ex.Message));
				}
			}
		}
	}
}
namespace FishPerksMod.Patches
{
	[HarmonyPatch(typeof(Attachments), "get_Damage")]
	internal static class StoppingPowerPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Attachments __instance, ref int __result)
		{
			try
			{
				Weapon weapon = __instance.Weapon;
				Player val = (((Object)(object)weapon != (Object)null) ? ((Item)weapon).Holder : null);
				if (!((Object)(object)val == (Object)null) && FishPerkManager.HasActivePerk(val, FishPerkType.StoppingPower))
				{
					int num = Mathf.RoundToInt((float)__result * 1.55f);
					if (FishPerkManager.VerboseLogging)
					{
						Plugin.Log.LogInfo((object)$"[FishPerksMod] Stopping Power: {__result} -> {num} damage for '{((Object)val).name}'.");
					}
					__result = num;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] StoppingPowerPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerVitals), "TakeDamage")]
	internal static class JuggernautPatch
	{
		[HarmonyPrefix]
		private static void Prefix(PlayerVitals __instance, ref int amount)
		{
			try
			{
				Player field = ReflectionUtil.GetField<Player>(__instance, "_player");
				if (!((Object)(object)field == (Object)null) && FishPerkManager.HasActivePerk(field, FishPerkType.Juggernaut))
				{
					int num = Mathf.Max(1, Mathf.RoundToInt((float)amount * 0.1f));
					if (FishPerkManager.VerboseLogging)
					{
						Plugin.Log.LogInfo((object)$"[FishPerksMod] Juggernaut: incoming damage {amount} -> {num} for '{((Object)field).name}'.");
					}
					amount = num;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] JuggernautPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Weapon), "Shoot")]
	internal static class DoubleTapPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Weapon __instance, ref float __state)
		{
			__state = ReflectionUtil.GetField<float>(__instance, "_timeBetweenShots");
			if ((Object)(object)((Item)__instance).Holder != (Object)null && FishPerkManager.HasActivePerk(((Item)__instance).Holder, FishPerkType.DoubleTap))
			{
				ReflectionUtil.SetField(__instance, "_timeBetweenShots", __state / 1.4f);
			}
		}

		[HarmonyPostfix]
		private static void Postfix(Weapon __instance, float __state)
		{
			ReflectionUtil.SetField(__instance, "_timeBetweenShots", __state);
		}
	}
	[HarmonyPatch(typeof(Weapon), "Reload")]
	internal static class SpeedColaPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Weapon __instance)
		{
			if (!((Object)(object)((Item)__instance).Holder == (Object)null))
			{
				Animation field = ReflectionUtil.GetField<Animation>(__instance, "_anim");
				string field2 = ReflectionUtil.GetField<string>(__instance, "_activeReloadAnimName");
				if ((Object)(object)field != (Object)null && !string.IsNullOrEmpty(field2) && (TrackedReference)(object)field[field2] != (TrackedReference)null)
				{
					field[field2].speed = (FishPerkManager.HasActivePerk(((Item)__instance).Holder, FishPerkType.SpeedCola) ? 1.4f : 1f);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PlayerEating), "FinishEating")]
	internal static class ClientFinishEatingPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerEating __instance)
		{
			try
			{
				Creature field = ReflectionUtil.GetField<Creature>(__instance, "_curFood");
				Player field2 = ReflectionUtil.GetField<Player>(__instance, "_player");
				if (!((Object)(object)field == (Object)null) && !((Object)(object)field2 == (Object)null) && field.IsDrip && FishPerkManager.VerboseLogging)
				{
					Plugin.Log.LogInfo((object)("[FishPerksMod] (client) '" + ((Object)field2).name + "' finished eating a drip fish; awaiting server perk sync."));
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] ClientFinishEatingPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Server), "RpcLogic___FinishEatingCreature___1039939981")]
	internal static class ServerFinishEatingPatch
	{
		[HarmonyPostfix]
		private static void Postfix(object[] __args)
		{
			try
			{
				if (__args == null || __args.Length < 2)
				{
					return;
				}
				object obj = __args[0];
				Creature val = (Creature)((obj is Creature) ? obj : null);
				object obj2 = __args[1];
				Player val2 = (Player)((obj2 is Player) ? obj2 : null);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null) && val.IsDrip)
				{
					FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(val);
					FishPerkManager.ApplyBuff(val2, orAssignPerk);
					FishPerkManager.SyncBuff(val2, orAssignPerk, 120f);
					FishPerkManager.Announce(val2.SteamName + " consumed " + FishPerkManager.GetDisplayName(orAssignPerk) + ".");
					if (FishPerkManager.VerboseLogging)
					{
						Plugin.Log.LogInfo((object)$"[FishPerksMod] (server) Registered buff '{orAssignPerk}' for '{((Object)val2).name}'.");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] ServerFinishEatingPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Item), "InitializeBait")]
	internal static class DripFishCaughtAnnouncementPatch
	{
		private static readonly HashSet<Item> AnnouncedItems = new HashSet<Item>();

		[HarmonyPostfix]
		private static void Postfix(Item __instance, FishingRod rod)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)rod == (Object)null) && Object.op_Implicit((Object)(object)__instance.Creature) && __instance.Creature.IsDrip && !((Object)(object)((Item)rod).Holder == (Object)null) && !AnnouncedItems.Contains(__instance))
			{
				FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(__instance.Creature);
				AnnouncedItems.Add(__instance);
				FishPerkManager.Announce(((Item)rod).Holder.SteamName + " has obtained " + FishPerkManager.GetDisplayName(orAssignPerk) + ".");
			}
		}
	}
	[HarmonyPatch(typeof(Creature), "SetDrip")]
	internal static class DripPerkAssignmentPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Creature __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && __instance.IsDrip)
			{
				FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(__instance);
				if (((NetworkBehaviour)__instance).IsServerInitialized)
				{
					FishPerkManager.SyncPerkAssignment(__instance, orAssignPerk);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Item), "SetAttachedRod")]
	internal static class HookedDripPerkAssignmentPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Item __instance)
		{
			Creature val = (((Object)(object)__instance != (Object)null) ? __instance.Creature : null);
			if ((Object)(object)val != (Object)null && val.IsDrip)
			{
				FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(val);
				if (((NetworkBehaviour)__instance).IsServerInitialized)
				{
					FishPerkManager.SyncPerkAssignment(val, orAssignPerk);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Item), "OnStartClient")]
	internal static class SyncedDripPerkAssignmentPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Item __instance)
		{
			Creature val = (((Object)(object)__instance != (Object)null) ? __instance.Creature : null);
			if ((Object)(object)val != (Object)null && val.IsDrip)
			{
				FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(val);
				if (((NetworkBehaviour)__instance).IsServerInitialized)
				{
					FishPerkManager.SyncPerkAssignment(val, orAssignPerk);
				}
			}
		}
	}
	[HarmonyPatch(typeof(OnlineChatManager), "RpcLogic___SendChatMessage___3264264606")]
	internal static class FishPerkNetworkPayloadPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(string __1)
		{
			return !FishPerkManager.TryHandleNetworkPayload(__1);
		}
	}
	[HarmonyPatch(typeof(Creature), "OnDeath")]
	internal static class DefeatedDripPerkAssignmentPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Creature __instance)
		{
			if ((Object)(object)__instance != (Object)null && __instance.IsDrip)
			{
				FishPerkManager.GetOrAssignPerk(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(KillScoreCalculator), "GetRangedBonuses")]
	internal static class HardlinePatch
	{
		[HarmonyPostfix]
		private static void Postfix(ref List<Bonus> __result)
		{
			//IL_0024: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.LocalPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && FishPerkManager.HasActivePerk(localPlayer, FishPerkType.Hardline) && __result != null)
			{
				for (int i = 0; i < __result.Count; i++)
				{
					Bonus value = __result[i];
					value.Worth *= 1.35f;
					__result[i] = value;
				}
				if (FishPerkManager.VerboseLogging)
				{
					Plugin.Log.LogInfo((object)$"[FishPerksMod] Hardline increased {__result.Count} ranged score multipliers by {35.000004f:0}%.");
				}
			}
		}
	}
	[HarmonyPatch(typeof(WeaponUI), "ShowInspectText", new Type[]
	{
		typeof(Item),
		typeof(string)
	})]
	internal static class InspectItemPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Item item, ref string extraDescription)
		{
			try
			{
				Creature val = (((Object)(object)item != (Object)null) ? item.Creature : null);
				if (!((Object)(object)val == (Object)null) && val.IsDrip)
				{
					FishPerkType orAssignPerk = FishPerkManager.GetOrAssignPerk(val);
					string text = "<b>" + FishPerkManager.GetDisplayName(orAssignPerk) + "</b>\n" + FishPerkManager.GetDescription(orAssignPerk);
					extraDescription = (string.IsNullOrEmpty(extraDescription) ? text : (extraDescription + "\n\n" + text));
					if (FishPerkManager.VerboseLogging)
					{
						Plugin.Log.LogInfo((object)$"[FishPerksMod] Inspect panel updated for '{((Object)val).name}' with perk '{orAssignPerk}'.");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] InspectItemPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerMovement), "Move")]
	internal static class FizzyYummyPatch
	{
		private struct MovementState
		{
			public float WalkSpeed;

			public float SprintSpeed;

			public bool Changed;
		}

		[HarmonyPrefix]
		private static void Prefix(PlayerMovement __instance, ref MovementState __state)
		{
			Player field = ReflectionUtil.GetField<Player>(__instance, "_player");
			if (!((Object)(object)field == (Object)null) && FishPerkManager.HasActivePerk(field, FishPerkType.FizzyYummy))
			{
				__state.WalkSpeed = ReflectionUtil.GetField<float>(__instance, "_walkSpeed");
				__state.SprintSpeed = ReflectionUtil.GetField<float>(__instance, "_sprintSpeed");
				float num = 1.3f;
				ReflectionUtil.SetField(__instance, "_walkSpeed", __state.WalkSpeed * num);
				ReflectionUtil.SetField(__instance, "_sprintSpeed", __state.SprintSpeed * num);
				__state.Changed = true;
			}
		}

		[HarmonyPostfix]
		private static void Postfix(PlayerMovement __instance, MovementState __state)
		{
			if (__state.Changed)
			{
				ReflectionUtil.SetField(__instance, "_walkSpeed", __state.WalkSpeed);
				ReflectionUtil.SetField(__instance, "_sprintSpeed", __state.SprintSpeed);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerMovement), "Move")]
	internal static class FizzyYummyVelocityPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerMovement __instance)
		{
			//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_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_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			Player field = ReflectionUtil.GetField<Player>(__instance, "_player");
			Rigidbody field2 = ReflectionUtil.GetField<Rigidbody>(__instance, "_rig");
			if (!((Object)(object)field == (Object)null) && !((Object)(object)field2 == (Object)null) && FishPerkManager.HasActivePerk(field, FishPerkType.FizzyYummy))
			{
				Vector3 linearVelocity = field2.linearVelocity;
				Vector3 val = new Vector3(linearVelocity.x, 0f, linearVelocity.z) * 1.3f;
				field2.linearVelocity = new Vector3(val.x, linearVelocity.y, val.z);
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "OnServerLoaded")]
	internal static class LoadPerksPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if (SaveManager.CurServerSave != null)
			{
				PersistenceStore.Load(SaveManager.CurServerSave.Name);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerInventory), "LoadFromSave")]
	internal static class RestorePerksPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerInventory __instance)
		{
			SavedPlayer field = ReflectionUtil.GetField<SavedPlayer>(__instance, "_toLoadFrom");
			Player field2 = ReflectionUtil.GetField<Player>(__instance, "_player");
			if (field == null || (Object)(object)field2 == (Object)null)
			{
				return;
			}
			Dictionary<byte, Item> field3 = ReflectionUtil.GetField<Dictionary<byte, Item>>(__instance, "_items");
			if (field3 != null)
			{
				foreach (KeyValuePair<byte, Item> item in field3)
				{
					if ((Object)(object)item.Value != (Object)null && Object.op_Implicit((Object)(object)item.Value.Creature) && item.Value.Creature.IsDrip && PersistenceStore.TryGet(field.SteamID, item.Key, out var perk))
					{
						FishPerkManager.AssignSpecificPerk(item.Value.Creature, perk);
						FishPerkManager.SyncPerkAssignment(item.Value.Creature, perk);
					}
				}
			}
			Item uninitializedHeldItem = field2.Holding.UninitializedHeldItem;
			if ((Object)(object)uninitializedHeldItem != (Object)null && Object.op_Implicit((Object)(object)uninitializedHeldItem.Creature) && uninitializedHeldItem.Creature.IsDrip && PersistenceStore.TryGet(field.SteamID, 0, out var perk2))
			{
				FishPerkManager.AssignSpecificPerk(uninitializedHeldItem.Creature, perk2);
				FishPerkManager.SyncPerkAssignment(uninitializedHeldItem.Creature, perk2);
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "SavePlayer")]
	internal static class CapturePerksPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Player player, Item heldItem, Dictionary<byte, Item> slotToItem)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			Capture(player.SteamID, 0, heldItem);
			if (slotToItem == null)
			{
				return;
			}
			foreach (KeyValuePair<byte, Item> item in slotToItem)
			{
				Capture(player.SteamID, item.Key, item.Value);
			}
		}

		private static void Capture(ulong steamId, byte slot, Item item)
		{
			if ((Object)(object)item != (Object)null && Object.op_Implicit((Object)(object)item.Creature) && item.Creature.IsDrip)
			{
				PersistenceStore.Set(steamId, slot, FishPerkManager.GetOrAssignPerk(item.Creature));
			}
			else
			{
				PersistenceStore.Remove(steamId, slot);
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "SaveServer")]
	internal static class SavePerksPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			if (SaveManager.CurServerSave != null)
			{
				PersistenceStore.Save(SaveManager.CurServerSave.Name);
			}
		}
	}
	[HarmonyPatch(typeof(Server), "OnStopServer")]
	internal static class ClearSessionPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			FishPerkManager.ClearSession();
		}
	}
	[HarmonyPatch(typeof(PlayerDying), "ServerDie")]
	internal static class QuickRevivePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(PlayerDying __instance)
		{
			try
			{
				Player player = ReflectionUtil.GetField<Player>(__instance, "_player");
				if ((Object)(object)player == (Object)null || !FishPerkManager.HasActivePerk(player, FishPerkType.QuickRevive))
				{
					return true;
				}
				Creature boss = BossManager.Boss;
				bool num = (Object)(object)boss != (Object)null && !boss.IsDead;
				bool flag = PlayerManager.Players != null && PlayerManager.Players.Any((Player other) => (Object)(object)other != (Object)null && (Object)(object)other != (Object)(object)player && !other.Dying.IsDead && other.Vitals.Health > 0);
				if (!num || !flag)
				{
					return true;
				}
				FishPerkManager.SyncQuickRevive(player);
				player.Vitals.OnResurrect();
				if (FishPerkManager.VerboseLogging)
				{
					Plugin.Log.LogInfo((object)("[FishPerksMod] Quick Revive saved '" + ((Object)player).name + "' during a boss fight."));
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[FishPerksMod] Quick Revive patch failed: {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerDying), "LocalResurrect")]
	internal static class QuickReviveLocalSignalPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerDying __instance)
		{
			QuickReviveSignals.Play(__instance);
		}
	}
	[HarmonyPatch(typeof(PlayerDying), "ResurrectEffect")]
	internal static class QuickReviveObserverSignalPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerDying __instance)
		{
			QuickReviveSignals.Play(__instance);
		}
	}
	internal static class QuickReviveSignals
	{
		private static readonly HashSet<ulong> PendingPlayers = new HashSet<ulong>();

		internal static void MarkPending(ulong steamId)
		{
			if (steamId != 0L)
			{
				PendingPlayers.Add(steamId);
			}
		}

		internal static void Trigger(Player player)
		{
			if ((Object)(object)player != (Object)null && PendingPlayers.Remove(player.SteamID))
			{
				PlayEffects(player);
			}
		}

		internal static void Play(PlayerDying dying)
		{
			Player field = ReflectionUtil.GetField<Player>(dying, "_player");
			if (!((Object)(object)field == (Object)null) && PendingPlayers.Remove(field.SteamID))
			{
				PlayEffects(field);
			}
		}

		private static void PlayEffects(Player player)
		{
			player.Effects.OnRestoredFullness();
			player.Effects.OnRestoredFullness();
			AudioManager.PlayRandomPlayerClip("ShinyCreatureCaught_0", 1, 3, player, false, (AudioDistance)2, 0.5f, 0.1f);
		}
	}
}