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);
}
}
}