using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using InControl;
using Photon.Pun;
using TMPro;
using UnboundLib;
using UnboundLib.Cards;
using UnboundLib.Utils.UI;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ZipsLOQ")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ZipsLOQ")]
[assembly: AssemblyTitle("ZipsLOQ")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ZipsLOQ;
internal static class CombatPatchState
{
internal sealed class RollingCounter
{
private readonly Queue<float> times = new Queue<float>();
internal void Prune(float now, float window)
{
while (times.Count > 0 && now - times.Peek() >= window)
{
times.Dequeue();
}
}
internal bool CanAdd(float now, int maximum, float window)
{
Prune(now, window);
return times.Count < Mathf.Max(1, maximum);
}
internal void Add(float now)
{
times.Enqueue(now);
}
}
internal sealed class BlockState
{
internal readonly RollingCounter Blocks = new RollingCounter();
internal readonly RollingCounter Effects = new RollingCounter();
}
internal sealed class SilenceState
{
internal struct Slice
{
internal float End;
internal float Duration;
internal Slice(float end, float duration)
{
End = end;
Duration = duration;
}
}
internal readonly Queue<Slice> Silence = new Queue<Slice>();
internal float Continuous;
internal bool WasActive;
internal void Begin(float now)
{
if (!WasActive)
{
WasActive = true;
Continuous = 0f;
}
}
internal void End()
{
WasActive = false;
Continuous = 0f;
}
internal void AddTime(float now, float duration, float window)
{
if (!(duration <= 0f))
{
Silence.Enqueue(new Slice(now, duration));
Prune(now, window);
}
}
internal float UsedInWindow(float now, float window)
{
Prune(now, window);
float num = now - window;
float num2 = 0f;
foreach (Slice item in Silence)
{
float num3 = item.End - item.Duration;
if (item.End > num)
{
num2 += item.Duration - Mathf.Max(0f, num - num3);
}
}
return Mathf.Max(0f, num2);
}
private void Prune(float now, float window)
{
float num = now - window;
while (Silence.Count > 0 && Silence.Peek().End <= num)
{
Silence.Dequeue();
}
}
}
internal static readonly Dictionary<int, BlockState> Blocks = new Dictionary<int, BlockState>();
internal static readonly Dictionary<int, SilenceState> Silences = new Dictionary<int, SilenceState>();
internal static BlockState ForBlock(int playerId)
{
if (!Blocks.TryGetValue(playerId, out var value))
{
value = new BlockState();
Blocks.Add(playerId, value);
}
return value;
}
internal static SilenceState ForSilence(int playerId)
{
if (!Silences.TryGetValue(playerId, out var value))
{
value = new SilenceState();
Silences.Add(playerId, value);
}
return value;
}
internal static void Reset()
{
Blocks.Clear();
Silences.Clear();
KillTracking.Reset();
}
}
internal static class KillTracking
{
private sealed class DamageShare
{
internal float Damage;
internal float MaxHealth;
}
internal struct DamageSnapshot
{
internal int VictimId;
internal int AttackerId;
internal float HealthBefore;
internal float MaxHealthBefore;
internal float DamageMagnitude;
}
private static readonly Dictionary<int, Dictionary<int, DamageShare>> DamageByVictim = new Dictionary<int, Dictionary<int, DamageShare>>();
private static readonly Dictionary<int, int> KillsByPlayer = new Dictionary<int, int>();
internal static bool TryCapture(HealthHandler health, Player attacker, Vector2 damage, out DamageSnapshot snapshot)
{
snapshot = default(DamageSnapshot);
if (!Plugin.IsAuthority || !Plugin.HasEnoughPlayers || (Object)(object)health == (Object)null || (Object)(object)attacker == (Object)null)
{
return false;
}
Player component = ((Component)health).GetComponent<Player>();
if ((Object)(object)component == (Object)null || (Object)(object)component.data == (Object)null || component.playerID == attacker.playerID || component.data.dead)
{
return false;
}
snapshot.VictimId = component.playerID;
snapshot.AttackerId = attacker.playerID;
snapshot.HealthBefore = component.data.health;
snapshot.MaxHealthBefore = Mathf.Max(1f, component.data.maxHealth);
snapshot.DamageMagnitude = ((Vector2)(ref damage)).magnitude;
return true;
}
internal static void RecordDamage(DamageSnapshot snapshot, HealthHandler health)
{
if ((Object)(object)health == (Object)null || !Plugin.IsAuthority)
{
return;
}
Player component = ((Component)health).GetComponent<Player>();
if ((Object)(object)component == (Object)null || component.playerID != snapshot.VictimId)
{
return;
}
float num = Mathf.Clamp(snapshot.HealthBefore - component.data.health, 0f, snapshot.DamageMagnitude);
if (!(num <= 0f))
{
if (!DamageByVictim.TryGetValue(snapshot.VictimId, out var value))
{
value = new Dictionary<int, DamageShare>();
DamageByVictim.Add(snapshot.VictimId, value);
}
if (!value.TryGetValue(snapshot.AttackerId, out var value2))
{
value2 = new DamageShare();
value.Add(snapshot.AttackerId, value2);
}
value2.Damage += num;
value2.MaxHealth = snapshot.MaxHealthBefore;
}
}
internal static void OnDeath(HealthHandler health)
{
if (!Plugin.IsAuthority || !Plugin.HasEnoughPlayers || (Object)(object)health == (Object)null)
{
return;
}
Player component = ((Component)health).GetComponent<Player>();
if ((Object)(object)component == (Object)null || (Object)(object)component.data == (Object)null || !DamageByVictim.TryGetValue(component.playerID, out var value))
{
return;
}
Player lastSourceOfDamage = component.data.lastSourceOfDamage;
if ((Object)(object)lastSourceOfDamage == (Object)null || lastSourceOfDamage.playerID == component.playerID)
{
DamageByVictim.Remove(component.playerID);
return;
}
if (value.TryGetValue(lastSourceOfDamage.playerID, out var value2))
{
float num = Mathf.Max(0f, LoqSettings.EffectiveDamageThresholdPercent) / 100f;
if (value2.Damage > Mathf.Max(1f, component.data.maxHealth) * num)
{
KillsByPlayer.TryGetValue(lastSourceOfDamage.playerID, out var value3);
value3++;
KillsByPlayer[lastSourceOfDamage.playerID] = value3;
int num2 = Mathf.Max(1, LoqSettings.EffectiveKillsPerReward);
if (value3 % num2 == 0)
{
Plugin.AwardKillReward(lastSourceOfDamage.playerID);
}
}
}
DamageByVictim.Remove(component.playerID);
}
internal static void Reset()
{
DamageByVictim.Clear();
KillsByPlayer.Clear();
}
}
[HarmonyPatch(typeof(Block), "Cooldown")]
internal static class BlockCooldownPatch
{
private static void Postfix(ref float __result)
{
if (LoqSettings.EffectiveAntiBlockEnabled)
{
__result = Mathf.Max(__result, 2.5f);
}
}
}
[HarmonyPatch(typeof(Block), "RPCA_DoBlock")]
internal static class BlockRateLimitPatch
{
private static bool Prefix(Block __instance, bool firstBlock, bool dontSetCD, BlockTriggerType triggerType, bool onlyBlockEffects)
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
if (!LoqSettings.EffectiveAntiBlockEnabled || (Object)(object)__instance == (Object)null)
{
return true;
}
Player component = ((Component)__instance).GetComponent<Player>();
if ((Object)(object)component == (Object)null)
{
return true;
}
float unscaledTime = Time.unscaledTime;
CombatPatchState.BlockState blockState = CombatPatchState.ForBlock(component.playerID);
blockState.Blocks.Prune(unscaledTime, 10f);
blockState.Effects.Prune(unscaledTime, 10f);
if (firstBlock && !dontSetCD && (int)triggerType == 0 && !onlyBlockEffects)
{
blockState.Blocks.Add(unscaledTime);
blockState.Effects.Add(unscaledTime);
return true;
}
if (!blockState.Blocks.CanAdd(unscaledTime, 4, 10f) || !blockState.Effects.CanAdd(unscaledTime, 5, 10f))
{
return false;
}
blockState.Effects.Add(unscaledTime);
blockState.Blocks.Add(unscaledTime);
return true;
}
}
[HarmonyPatch(typeof(SilenceHandler), "RPCA_AddSilence")]
internal static class SilenceApplyPatch
{
private static bool Prefix(SilenceHandler __instance, ref float f)
{
if (!LoqSettings.EffectiveAntiSilenceEnabled || (Object)(object)__instance == (Object)null)
{
return true;
}
Player component = ((Component)__instance).GetComponent<Player>();
if ((Object)(object)component == (Object)null || (Object)(object)component.data == (Object)null)
{
return true;
}
CombatPatchState.SilenceState silenceState = CombatPatchState.ForSilence(component.playerID);
float unscaledTime = Time.unscaledTime;
if (!component.data.isSilenced)
{
silenceState.Begin(unscaledTime);
}
float num = silenceState.UsedInWindow(unscaledTime, 10f);
float num2 = Mathf.Max(0f, 2f - silenceState.Continuous);
float num3 = Mathf.Max(0f, 4f - num);
float num4 = Mathf.Min(num2, num3);
if (num4 <= 0f)
{
if (component.data.isSilenced)
{
__instance.StopSilence();
}
return false;
}
f = Mathf.Min(f, num4);
if (component.data.silenceTime > num4)
{
component.data.silenceTime = num4;
}
return true;
}
}
[HarmonyPatch(typeof(SilenceHandler), "Update")]
internal static class SilenceDurationPatch
{
private static void Postfix(SilenceHandler __instance)
{
if (!LoqSettings.EffectiveAntiSilenceEnabled || (Object)(object)__instance == (Object)null)
{
return;
}
Player component = ((Component)__instance).GetComponent<Player>();
if ((Object)(object)component == (Object)null || (Object)(object)component.data == (Object)null)
{
return;
}
CombatPatchState.SilenceState silenceState = CombatPatchState.ForSilence(component.playerID);
if (!component.data.isSilenced)
{
silenceState.End();
return;
}
float num = Mathf.Max(0f, Time.unscaledDeltaTime);
float unscaledTime = Time.unscaledTime;
silenceState.Begin(unscaledTime);
silenceState.Continuous += num;
silenceState.AddTime(unscaledTime, num, 10f);
float num2 = Mathf.Max(0f, 2f - silenceState.Continuous);
float num3 = Mathf.Max(0f, 4f - silenceState.UsedInWindow(unscaledTime, 10f));
float num4 = Mathf.Min(num2, num3);
if (num4 <= 0f)
{
__instance.StopSilence();
}
else if (component.data.silenceTime > num4)
{
component.data.silenceTime = num4;
}
}
}
[HarmonyPatch(typeof(HealthHandler), "DoDamage", new Type[]
{
typeof(Vector2),
typeof(Vector2),
typeof(Color),
typeof(GameObject),
typeof(Player),
typeof(bool),
typeof(bool),
typeof(bool)
})]
internal static class DamageAttributionPatch
{
private static void Prefix(HealthHandler __instance, ref Vector2 damage, Player damagingPlayer, out KillTracking.DamageSnapshot __state)
{
//IL_0072: 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_0068: Unknown result type (might be due to invalid IL or missing references)
__state = default(KillTracking.DamageSnapshot);
if ((Object)(object)damagingPlayer != (Object)null && (Object)(object)__instance != (Object)null)
{
Player component = ((Component)__instance).GetComponent<Player>();
if ((Object)(object)component != (Object)null && damagingPlayer.teamID == component.teamID)
{
float num = Mathf.Clamp(LoqSettings.EffectiveFriendlyDamageMultiplier, 0f, 2f);
damage *= num;
}
}
KillTracking.TryCapture(__instance, damagingPlayer, damage, out __state);
}
private static void Postfix(HealthHandler __instance, KillTracking.DamageSnapshot __state)
{
if (__state.MaxHealthBefore > 0f)
{
KillTracking.RecordDamage(__state, __instance);
}
}
}
[HarmonyPatch(typeof(HealthHandler), "RPCA_Die")]
internal static class KillRewardDeathPatch
{
private static void Postfix(HealthHandler __instance)
{
KillTracking.OnDeath(__instance);
}
}
[HarmonyPatch(typeof(HealthHandler), "RPCA_Die_Phoenix")]
internal static class KillRewardPhoenixPatch
{
private static void Postfix(HealthHandler __instance)
{
KillTracking.OnDeath(__instance);
}
}
internal sealed class ProjectileBounceCounter : MonoBehaviour
{
internal int Count;
}
internal sealed class PendingBlockHit : MonoBehaviour
{
internal bool Waiting;
}
[HarmonyPatch(typeof(ProjectileHit), "Hit")]
internal static class BlockLatencyFixPatch
{
private static readonly HashSet<int> BypassDelay = new HashSet<int>();
private static bool Prefix(ProjectileHit __instance, HitInfo hit, bool forceCall)
{
int num = Mathf.Clamp(LoqSettings.EffectiveBlockLatencyWindowMs, 0, 50);
if ((Object)(object)__instance == (Object)null || hit == null || (Object)(object)hit.transform == (Object)null || num <= 0 || BypassDelay.Contains(((Object)__instance).GetInstanceID()))
{
return true;
}
if ((Object)(object)__instance.view == (Object)null || (!__instance.view.IsMine && !forceCall))
{
return true;
}
HealthHandler component = ((Component)hit.transform).GetComponent<HealthHandler>();
if ((Object)(object)component == (Object)null || (Object)(object)((Component)component).GetComponent<Player>() == (Object)null)
{
return true;
}
Block component2 = ((Component)component).GetComponent<Block>();
if ((Object)(object)component2 == (Object)null || component2.IsBlocking())
{
return true;
}
PendingBlockHit pendingBlockHit = ((Component)__instance).GetComponent<PendingBlockHit>();
if ((Object)(object)pendingBlockHit == (Object)null)
{
pendingBlockHit = ((Component)__instance).gameObject.AddComponent<PendingBlockHit>();
}
if (pendingBlockHit.Waiting)
{
return false;
}
pendingBlockHit.Waiting = true;
((MonoBehaviour)__instance).StartCoroutine(ResolveAfterBlockWindow(__instance, hit, forceCall, pendingBlockHit, num));
return false;
}
private static IEnumerator ResolveAfterBlockWindow(ProjectileHit projectile, HitInfo hit, bool forceCall, PendingBlockHit pending, int delayMs)
{
MoveTransform movement = ((Component)projectile).GetComponent<MoveTransform>();
bool movementWasEnabled = (Object)(object)movement != (Object)null && ((Behaviour)movement).enabled;
if ((Object)(object)movement != (Object)null)
{
((Behaviour)movement).enabled = false;
}
yield return (object)new WaitForSecondsRealtime((float)delayMs / 1000f);
if ((Object)(object)projectile == (Object)null)
{
yield break;
}
if ((Object)(object)movement != (Object)null)
{
((Behaviour)movement).enabled = movementWasEnabled;
}
if ((Object)(object)pending != (Object)null)
{
pending.Waiting = false;
}
int projectileId = ((Object)projectile).GetInstanceID();
BypassDelay.Add(projectileId);
try
{
((RayHit)projectile).Hit(hit, forceCall);
}
finally
{
BypassDelay.Remove(projectileId);
}
}
}
[HarmonyPatch(typeof(RayHitReflect), "DoHitEffect")]
internal static class BulletBounceLimitPatch
{
private static bool Prefix(RayHitReflect __instance, ref HasToReturn __result)
{
if ((Object)(object)__instance == (Object)null)
{
return true;
}
int num = Mathf.Clamp(LoqSettings.EffectiveBulletBounceLimit, 0, 50);
ProjectileBounceCounter component = ((Component)__instance).GetComponent<ProjectileBounceCounter>();
if ((Object)(object)component != (Object)null && component.Count < num)
{
return true;
}
if ((Object)(object)component == (Object)null && num > 0)
{
return true;
}
__result = (HasToReturn)1;
return false;
}
private static void Postfix(RayHitReflect __instance, HasToReturn __result)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Invalid comparison between Unknown and I4
if ((Object)(object)__instance != (Object)null && (int)__result == 0)
{
ProjectileBounceCounter projectileBounceCounter = ((Component)__instance).GetComponent<ProjectileBounceCounter>();
if ((Object)(object)projectileBounceCounter == (Object)null)
{
projectileBounceCounter = ((Component)__instance).gameObject.AddComponent<ProjectileBounceCounter>();
}
projectileBounceCounter.Count++;
}
}
}
[HarmonyPatch(typeof(ScreenEdgeBounce), "DoHit")]
internal static class VoidBounceLimitPatch
{
private static bool Prefix(ScreenEdgeBounce __instance)
{
if ((Object)(object)__instance == (Object)null)
{
return true;
}
int num = Mathf.Clamp(LoqSettings.EffectiveBulletBounceLimit, 0, 50);
ProjectileHit componentInParent = ((Component)__instance).GetComponentInParent<ProjectileHit>();
ProjectileBounceCounter projectileBounceCounter = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent<ProjectileBounceCounter>() : null);
if (((Object)(object)projectileBounceCounter == (Object)null && num > 0) || ((Object)(object)projectileBounceCounter != (Object)null && projectileBounceCounter.Count < num))
{
return true;
}
if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent.view != (Object)null)
{
if (componentInParent.view.IsMine)
{
PhotonNetwork.Destroy(((Component)componentInParent).gameObject);
}
}
else if ((Object)(object)componentInParent != (Object)null)
{
Object.Destroy((Object)(object)((Component)componentInParent).gameObject);
}
return false;
}
}
[HarmonyPatch(typeof(CardChoice), "DoPick")]
internal static class RewardCardPickPhasePatch
{
private static void Postfix(ref IEnumerator __result)
{
if ((Object)(object)Plugin.Instance != (Object)null)
{
__result = Plugin.Instance.ContinueCardPickWithPendingRewards(__result);
}
}
}
internal static class LoqSettings
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static UnityAction <>9__45_0;
public static Func<object, string> <>9__47_0;
public static UnityAction<bool> <>9__49_0;
public static UnityAction<bool> <>9__49_1;
public static UnityAction<float> <>9__52_0;
internal void <RegisterMenu>b__45_0()
{
}
internal string <CreateSyncSignature>b__47_0(object value)
{
return Convert.ToString(value, CultureInfo.InvariantCulture);
}
internal void <BuildMenu>b__49_0(bool value)
{
SetIfHost(AntiBlockEnabled, value);
}
internal void <BuildMenu>b__49_1(bool value)
{
SetIfHost(AntiSilenceEnabled, value);
}
internal void <SliderFriendlyDamage>b__52_0(float value)
{
SetIfHost(FriendlyDamageMultiplier, Mathf.Clamp(Mathf.Round(value * 100f) / 100f, 0f, 2f));
}
}
internal const int SyncValueCount = 7;
internal const int RequiredPlayers = 3;
private const int DefaultBulletBounceLimit = 5;
private const float DefaultFriendlyDamageMultiplier = 1f;
internal const float DamageBuff = 15f;
internal const float SpeedBuff = 15f;
internal const float HealthBuff = 20f;
internal const float AttackSpeedBuff = 25f;
internal const float ReloadSpeedBuff = 15f;
internal const int AmmoBuff = 1;
internal const float RegenBuff = 3f;
internal const float BlockCooldownBuff = 0.35f;
internal const float BulletSpeedBuff = 20f;
internal static ConfigEntry<int> KillsPerReward;
internal static ConfigEntry<float> DamageThresholdPercent;
internal static ConfigEntry<bool> AntiBlockEnabled;
internal static ConfigEntry<bool> AntiSilenceEnabled;
internal static ConfigEntry<int> BulletBounceLimit;
internal static ConfigEntry<float> FriendlyDamageMultiplier;
internal static ConfigEntry<int> BlockLatencyWindowMs;
private static bool hasHostSettings;
private static int hostKillsPerReward = 4;
private static float hostDamageThresholdPercent = 50f;
private static bool hostAntiBlockEnabled = true;
private static bool hostAntiSilenceEnabled = true;
private static int hostBulletBounceLimit = 5;
private static float hostFriendlyDamageMultiplier = 1f;
private static int hostBlockLatencyWindowMs;
private static bool UseHostSettings => PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode && !PhotonNetwork.IsMasterClient;
internal static int EffectiveKillsPerReward => (!UseHostSettings) ? Mathf.Clamp(KillsPerReward.Value, 1, 20) : (hasHostSettings ? hostKillsPerReward : 4);
internal static float EffectiveDamageThresholdPercent => (!UseHostSettings) ? Mathf.Clamp(DamageThresholdPercent.Value, 1f, 100f) : (hasHostSettings ? hostDamageThresholdPercent : 50f);
internal static bool EffectiveAntiBlockEnabled => (!UseHostSettings) ? AntiBlockEnabled.Value : (!hasHostSettings || hostAntiBlockEnabled);
internal static bool EffectiveAntiSilenceEnabled => (!UseHostSettings) ? AntiSilenceEnabled.Value : (!hasHostSettings || hostAntiSilenceEnabled);
internal static int EffectiveBulletBounceLimit => (!UseHostSettings) ? Mathf.Clamp(BulletBounceLimit.Value, 0, 50) : (hasHostSettings ? hostBulletBounceLimit : 5);
internal static float EffectiveFriendlyDamageMultiplier => (!UseHostSettings) ? Mathf.Clamp(FriendlyDamageMultiplier.Value, 0f, 2f) : (hasHostSettings ? hostFriendlyDamageMultiplier : 1f);
internal static int EffectiveBlockLatencyWindowMs => (!UseHostSettings) ? Mathf.Clamp(BlockLatencyWindowMs.Value, 0, 50) : (hasHostSettings ? hostBlockLatencyWindowMs : 0);
internal static void Bind(ConfigFile config)
{
KillsPerReward = config.Bind<int>("Kill Incentivizer", "Kills per reward", 4, "Qualifying kills required for each reward.");
DamageThresholdPercent = config.Bind<float>("Kill Incentivizer", "Damage required percent", 50f, "Damage required against a victim's max health. The killer must deal more than this amount.");
AntiBlockEnabled = config.Bind<bool>("Anti Block Abuse", "Enabled", true, "Enforce the fixed block limits.");
AntiSilenceEnabled = config.Bind<bool>("Anti Silence Abuse", "Enabled", true, "Enforce the fixed silence limits.");
BulletBounceLimit = config.Bind<int>("Anti Bounce Abuse", "Maximum bullet bounces", 5, "Hard maximum number of times each bullet can bounce.");
FriendlyDamageMultiplier = config.Bind<float>("Friendly Damage", "Damage multiplier", 1f, "Multiplier for damage dealt to yourself or a teammate.");
BlockLatencyWindowMs = config.Bind<int>("Block Latency Fixer", "Block grace window (ms)", 0, "Wait this many milliseconds after a bullet hit before deciding whether the hit was blocked.");
}
internal static void RegisterMenu()
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Expected O, but got Unknown
object obj = <>c.<>9__45_0;
if (obj == null)
{
UnityAction val = delegate
{
};
<>c.<>9__45_0 = val;
obj = (object)val;
}
Unbound.RegisterMenu("Zip's LOQ Patches", (UnityAction)obj, (Action<GameObject>)BuildMenu, (GameObject)null, true);
}
internal static object[] CreateSyncPayload()
{
return new object[7] { EffectiveKillsPerReward, EffectiveDamageThresholdPercent, AntiBlockEnabled.Value, AntiSilenceEnabled.Value, EffectiveBulletBounceLimit, EffectiveFriendlyDamageMultiplier, EffectiveBlockLatencyWindowMs };
}
internal static string CreateSyncSignature()
{
return string.Join("|", from value in CreateSyncPayload()
select Convert.ToString(value, CultureInfo.InvariantCulture));
}
internal static void ApplySyncPayload(object[] values)
{
KillsPerReward.Value = Mathf.Clamp(Convert.ToInt32(values[0]), 1, 20);
DamageThresholdPercent.Value = Mathf.Clamp(Convert.ToSingle(values[1]), 1f, 100f);
AntiBlockEnabled.Value = Convert.ToBoolean(values[2]);
AntiSilenceEnabled.Value = Convert.ToBoolean(values[3]);
BulletBounceLimit.Value = Mathf.Clamp(Convert.ToInt32(values[4]), 0, 50);
FriendlyDamageMultiplier.Value = Mathf.Clamp(Convert.ToSingle(values[5]), 0f, 2f);
BlockLatencyWindowMs.Value = Mathf.Clamp(Convert.ToInt32(values[6]), 0, 50);
hostKillsPerReward = Mathf.Clamp(KillsPerReward.Value, 1, 20);
hostDamageThresholdPercent = Mathf.Clamp(DamageThresholdPercent.Value, 1f, 100f);
hostAntiBlockEnabled = AntiBlockEnabled.Value;
hostAntiSilenceEnabled = AntiSilenceEnabled.Value;
hostBulletBounceLimit = Mathf.Clamp(BulletBounceLimit.Value, 0, 50);
hostFriendlyDamageMultiplier = Mathf.Clamp(FriendlyDamageMultiplier.Value, 0f, 2f);
hostBlockLatencyWindowMs = Mathf.Clamp(BlockLatencyWindowMs.Value, 0, 50);
hasHostSettings = true;
}
private static void BuildMenu(GameObject menu)
{
TextMeshProUGUI val = default(TextMeshProUGUI);
MenuHandler.CreateText("Kill Incentivizer", menu, ref val, 36, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
SliderInt("Kills needed per reward", menu, KillsPerReward, 1f, 20f);
SliderFloat("Damage needed (% max health)", menu, DamageThresholdPercent, 1f, 100f, 1f);
MenuHandler.CreateText("Abuse limits", menu, ref val, 36, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
MenuHandler.CreateToggle(AntiBlockEnabled.Value, "Anti Block Abuse", menu, (UnityAction<bool>)delegate(bool value)
{
SetIfHost(AntiBlockEnabled, value);
}, 25, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
MenuHandler.CreateToggle(AntiSilenceEnabled.Value, "Anti Silence Abuse", menu, (UnityAction<bool>)delegate(bool value)
{
SetIfHost(AntiSilenceEnabled, value);
}, 25, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
MenuHandler.CreateText("Anti Bounce Abuse", menu, ref val, 36, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
SliderInt("Maximum bullet bounces", menu, BulletBounceLimit, 0f, 50f);
MenuHandler.CreateText("Friendly Damage", menu, ref val, 36, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
SliderFriendlyDamage(menu);
MenuHandler.CreateText("Block Latency Fixer", menu, ref val, 36, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
SliderInt("Block grace window (ms)", menu, BlockLatencyWindowMs, 0f, 50f);
MenuHandler.CreateText("Kill rewards require 3+ players. Anti-abuse patches also apply in 2-player matches. In Sandbox, press F6 to spawn a passive dummy.", menu, ref val, 24, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
}
private static void SliderInt(string label, GameObject menu, ConfigEntry<int> entry, float min, float max)
{
Slider val = default(Slider);
MenuHandler.CreateSlider(label + ": " + entry.Value, menu, 22, min, max, (float)entry.Value, (UnityAction<float>)delegate(float value)
{
SetIfHost(entry, Mathf.Clamp(Mathf.RoundToInt(value), Mathf.RoundToInt(min), Mathf.RoundToInt(max)));
}, ref val, true, (Color?)null, (Direction)0, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
}
private static void SliderFloat(string label, GameObject menu, ConfigEntry<float> entry, float min, float max, float step)
{
Slider val = default(Slider);
MenuHandler.CreateSlider(label + ": " + entry.Value.ToString("0.##"), menu, 22, min, max, entry.Value, (UnityAction<float>)delegate(float value)
{
SetIfHost(entry, Mathf.Clamp(Mathf.Round(value / step) * step, min, max));
}, ref val, false, (Color?)null, (Direction)0, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
}
private static void SliderFriendlyDamage(GameObject menu)
{
Slider val = default(Slider);
MenuHandler.CreateSlider("Damage multiplier: " + FriendlyDamageMultiplier.Value.ToString("0.00") + "x", menu, 22, 0f, 2f, FriendlyDamageMultiplier.Value, (UnityAction<float>)delegate(float value)
{
SetIfHost(FriendlyDamageMultiplier, Mathf.Clamp(Mathf.Round(value * 100f) / 100f, 0f, 2f));
}, ref val, false, (Color?)null, (Direction)0, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null);
}
private static void SetIfHost<T>(ConfigEntry<T> entry, T value)
{
if (Plugin.CanEditSettings)
{
entry.Value = value;
Plugin.BroadcastSettingsAfterEdit();
}
}
}
[BepInPlugin("robin.zipsloqpatches", "Zip's LOQ Patches", "0.1.6")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInProcess("Rounds.exe")]
public sealed class Plugin : BaseUnityPlugin
{
public const string ModId = "robin.zipsloqpatches";
public const string ModName = "Zip's LOQ Patches";
public const string Version = "0.1.6";
internal const string RewardEvent = "ZipsLOQ_BeginRewardPick";
private const string SettingsEvent = "ZipsLOQ_SettingsSync";
private static readonly Queue<int> PendingRewardPicks = new Queue<int>();
private static bool rewardPickRunning;
private static bool syncingSettings;
private string lastSettingsSignature = "";
private int lastRoomPlayerCount = -1;
private bool dummySpawnInProgress;
internal static Plugin Instance { get; private set; }
internal static bool HasEnoughPlayers => (Object)(object)PlayerManager.instance != (Object)null && PlayerManager.instance.players.Count >= 3;
internal static bool IsAuthority => PhotonNetwork.OfflineMode || PhotonNetwork.IsMasterClient;
internal static bool CanEditSettings => PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient;
internal static bool IsSyncingSettings => syncingSettings;
private void Awake()
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Expected O, but got Unknown
Instance = this;
LoqSettings.Bind(((BaseUnityPlugin)this).Config);
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "robin.zipsloqpatches");
NetworkingManager.RegisterEvent("ZipsLOQ_BeginRewardPick", new PhotonEvent(OnRewardPickRequested));
NetworkingManager.RegisterEvent("ZipsLOQ_SettingsSync", new PhotonEvent(OnSettingsReceived));
((BaseUnityPlugin)this).Logger.LogInfo((object)"Zip's LOQ Patches loaded.");
}
private void Start()
{
SceneManager.sceneLoaded += OnSceneLoaded;
LoqSettings.RegisterMenu();
RewardCards.Register();
}
private void Update()
{
SyncHostSettingsIfNeeded();
CheckDummyKey();
}
private void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
if ((Object)(object)Instance == (Object)(object)this)
{
Instance = null;
}
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
string text = ((Scene)(ref scene)).name.ToLowerInvariant();
if (text.Contains("menu") || text.Contains("character"))
{
CombatPatchState.Reset();
PendingRewardPicks.Clear();
rewardPickRunning = false;
}
}
private void SyncHostSettingsIfNeeded()
{
if (!PhotonNetwork.OfflineMode && PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom)
{
int playerCount = PhotonNetwork.CurrentRoom.PlayerCount;
string text = LoqSettings.CreateSyncSignature();
if (playerCount != lastRoomPlayerCount || text != lastSettingsSignature)
{
lastRoomPlayerCount = playerCount;
lastSettingsSignature = text;
NetworkingManager.RaiseEventOthers("ZipsLOQ_SettingsSync", LoqSettings.CreateSyncPayload());
}
}
}
internal static void BroadcastSettings()
{
if (!((Object)(object)Instance == (Object)null) && !PhotonNetwork.OfflineMode && PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom)
{
Instance.lastSettingsSignature = LoqSettings.CreateSyncSignature();
Instance.lastRoomPlayerCount = PhotonNetwork.CurrentRoom.PlayerCount;
NetworkingManager.RaiseEventOthers("ZipsLOQ_SettingsSync", LoqSettings.CreateSyncPayload());
}
}
private static void OnSettingsReceived(object[] args)
{
if (args == null || args.Length != 7)
{
return;
}
syncingSettings = true;
try
{
LoqSettings.ApplySyncPayload(args);
}
catch (Exception ex)
{
Debug.LogError("[Zip's LOQ Patches] Could not apply host settings: " + ex);
}
finally
{
syncingSettings = false;
}
}
private void CheckDummyKey()
{
if (Input.GetKeyDown((KeyCode)287) && !((Object)(object)GM_Test.instance == (Object)null) && ((Component)GM_Test.instance).gameObject.activeInHierarchy && !GameManager.lockInput && !dummySpawnInProgress)
{
((MonoBehaviour)this).StartCoroutine(SpawnPassiveDummy());
}
}
private IEnumerator SpawnPassiveDummy()
{
PlayerAssigner assigner = PlayerAssigner.instance;
if ((Object)(object)assigner == (Object)null || (Object)(object)PlayerManager.instance == (Object)null || PlayerManager.instance.players.Count >= assigner.maxPlayers)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)"Sandbox dummy could not be spawned: the player limit has been reached.");
Unbound.BuildInfoPopup("No free player slots for another dummy.");
yield break;
}
dummySpawnInProgress = true;
int playerCountBefore = PlayerManager.instance.players.Count;
yield return ((MonoBehaviour)assigner).StartCoroutine(assigner.CreatePlayer((InputDevice)null, true));
float wait = 0f;
while (PlayerManager.instance.players.Count <= playerCountBefore && wait < 5f)
{
wait += Time.unscaledDeltaTime;
yield return null;
}
if (PlayerManager.instance.players.Count > playerCountBefore)
{
Player spawned = PlayerManager.instance.players[PlayerManager.instance.players.Count - 1];
PlayerAI[] aiControllers = ((Component)spawned).GetComponentsInChildren<PlayerAI>(true);
for (int i = 0; i < aiControllers.Length; i++)
{
((Behaviour)aiControllers[i]).enabled = false;
}
spawned.data.input.ResetInput();
((BaseUnityPlugin)this).Logger.LogInfo((object)("Spawned passive Sandbox dummy with player ID " + spawned.playerID + "."));
Unbound.BuildInfoPopup("Passive dummy spawned.");
}
else
{
((BaseUnityPlugin)this).Logger.LogWarning((object)"Sandbox dummy spawn did not create a player.");
Unbound.BuildInfoPopup("Dummy spawn failed. Check BepInEx/LogOutput.log.");
}
dummySpawnInProgress = false;
}
private static void OnRewardPickRequested(object[] args)
{
if (args != null && args.Length != 0 && args[0] is int)
{
PendingRewardPicks.Enqueue((int)args[0]);
}
}
internal IEnumerator ContinueCardPickWithPendingRewards(IEnumerator normalPick)
{
while (normalPick?.MoveNext() ?? false)
{
yield return normalPick.Current;
}
GM_ArmsRace roundGame = GM_ArmsRace.instance;
if (rewardPickRunning || PendingRewardPicks.Count == 0 || (Object)(object)roundGame == (Object)null || !((Component)roundGame).gameObject.activeInHierarchy || !roundGame.pickPhase || (Object)(object)GameManager.instance == (Object)null || GameManager.instance.battleOngoing)
{
yield break;
}
rewardPickRunning = true;
while (PendingRewardPicks.Count > 0)
{
int playerId = PendingRewardPicks.Dequeue();
float wait = 0f;
while (!RewardCards.Ready && wait < 10f)
{
wait += Time.unscaledDeltaTime;
yield return null;
}
wait = 0f;
while (((Object)(object)CardChoice.instance == (Object)null || (Object)(object)CardChoiceVisuals.instance == (Object)null) && wait < 10f)
{
wait += Time.unscaledDeltaTime;
yield return null;
}
if (RewardCards.Ready && (Object)(object)CardChoice.instance != (Object)null && (Object)(object)CardChoiceVisuals.instance != (Object)null)
{
yield return ((MonoBehaviour)Instance).StartCoroutine(RewardCards.ShowRewardChoice(playerId));
}
}
rewardPickRunning = false;
}
internal static void AwardKillReward(int playerId)
{
if (IsAuthority && HasEnoughPlayers && RewardCards.Ready)
{
NetworkingManager.RaiseEvent("ZipsLOQ_BeginRewardPick", new object[1] { playerId });
}
}
internal static void BroadcastSettingsAfterEdit()
{
if (!IsSyncingSettings)
{
BroadcastSettings();
}
}
}
internal enum RewardKind
{
Damage,
Speed,
Health,
AttackSpeed,
ReloadSpeed,
Ammo,
Regeneration,
BlockCooldown,
BulletSpeed
}
internal static class RewardCards
{
private static readonly Dictionary<RewardKind, CardInfo> Cards = new Dictionary<RewardKind, CardInfo>();
private static readonly List<CardInfo> OrderedCards = new List<CardInfo>();
private static int builtCount;
internal static bool Ready => builtCount == 9 && OrderedCards.Count == 9;
internal static void Register()
{
CustomCard.BuildCard<DamageRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.Damage, info);
});
CustomCard.BuildCard<SpeedRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.Speed, info);
});
CustomCard.BuildCard<HealthRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.Health, info);
});
CustomCard.BuildCard<AttackSpeedRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.AttackSpeed, info);
});
CustomCard.BuildCard<ReloadSpeedRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.ReloadSpeed, info);
});
CustomCard.BuildCard<AmmoRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.Ammo, info);
});
CustomCard.BuildCard<RegenerationRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.Regeneration, info);
});
CustomCard.BuildCard<BlockCooldownRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.BlockCooldown, info);
});
CustomCard.BuildCard<BulletSpeedRewardCard>((Action<CardInfo>)delegate(CardInfo info)
{
Add(RewardKind.BulletSpeed, info);
});
}
private static void Add(RewardKind kind, CardInfo info)
{
if ((Object)(object)info == (Object)null)
{
Debug.LogError("[Zip's LOQ Patches] A reward card failed to register: " + kind);
return;
}
Cards[kind] = info;
if (!OrderedCards.Contains(info))
{
OrderedCards.Add(info);
builtCount++;
}
RefreshCard(info, kind);
}
internal static string Title(RewardKind kind)
{
return kind switch
{
RewardKind.Damage => "LOQ: Damage",
RewardKind.Speed => "LOQ: Speed",
RewardKind.Health => "LOQ: Health",
RewardKind.AttackSpeed => "LOQ: Attack Speed",
RewardKind.ReloadSpeed => "LOQ: Quick Reload",
RewardKind.Ammo => "LOQ: Extra Ammo",
RewardKind.Regeneration => "LOQ: Regeneration",
RewardKind.BlockCooldown => "LOQ: Quick Block",
_ => "LOQ: Bullet Speed",
};
}
internal static string Description(RewardKind kind)
{
return kind switch
{
RewardKind.Damage => "Increase damage by " + 15f.ToString("0.#") + "%.",
RewardKind.Speed => "Increase movement speed by " + 15f.ToString("0.#") + "%.",
RewardKind.Health => "Increase maximum health by " + 20f.ToString("0.#") + "%.",
RewardKind.AttackSpeed => "Increase attack speed by " + 25f.ToString("0.#") + "%.",
RewardKind.ReloadSpeed => "Reload " + 15f.ToString("0.#") + "% faster.",
RewardKind.Ammo => "Gain " + 1 + " ammo.",
RewardKind.Regeneration => "Regenerate " + 3f.ToString("0.##") + " health per second.",
RewardKind.BlockCooldown => "Reduce block cooldown by " + 0.35f.ToString("0.##") + " seconds.",
_ => "Increase bullet speed by " + 20f.ToString("0.#") + "%.",
};
}
internal static IEnumerator ShowRewardChoice(int playerId)
{
if (Ready && !((Object)(object)CardChoice.instance == (Object)null) && !((Object)(object)CardChoiceVisuals.instance == (Object)null))
{
RefreshAllCards();
CardInfo[] originalCards = CardChoice.instance.cards;
CardChoice.instance.cards = OrderedCards.ToArray();
CardChoiceVisuals.instance.Show(playerId, true);
yield return CardChoice.instance.DoPick(1, playerId, (PickerType)1);
CardChoice.instance.cards = originalCards;
}
}
internal static void RefreshAllCards()
{
KeyValuePair<RewardKind, CardInfo>[] array = Cards.ToArray();
for (int i = 0; i < array.Length; i++)
{
KeyValuePair<RewardKind, CardInfo> keyValuePair = array[i];
RefreshCard(keyValuePair.Value, keyValuePair.Key);
}
}
private static void RefreshCard(CardInfo info, RewardKind kind)
{
info.cardName = Title(kind);
info.cardDestription = Description(kind);
info.allowMultiple = true;
Gun component = ((Component)info).GetComponent<Gun>();
if ((Object)(object)component != (Object)null)
{
component.damage = 1f;
component.attackSpeed = 1f;
component.reloadTime = 1f;
component.ammo = 0;
component.projectileSpeed = 1f;
}
CharacterStatModifiers component2 = ((Component)info).GetComponent<CharacterStatModifiers>();
if ((Object)(object)component2 != (Object)null)
{
component2.movementSpeed = 1f;
component2.health = 1f;
component2.regen = 0f;
}
Block componentInChildren = ((Component)info).GetComponentInChildren<Block>(true);
if ((Object)(object)componentInChildren != (Object)null)
{
componentInChildren.cdAdd = 0f;
}
switch (kind)
{
case RewardKind.Damage:
if ((Object)(object)component != (Object)null)
{
component.damage = 1f + Percent(15f);
}
break;
case RewardKind.Speed:
if ((Object)(object)component2 != (Object)null)
{
component2.movementSpeed = 1f + Percent(15f);
}
break;
case RewardKind.Health:
if ((Object)(object)component2 != (Object)null)
{
component2.health = 1f + Percent(20f);
}
break;
case RewardKind.AttackSpeed:
if ((Object)(object)component != (Object)null)
{
component.attackSpeed = 1f + Percent(25f);
}
break;
case RewardKind.ReloadSpeed:
if ((Object)(object)component != (Object)null)
{
component.reloadTime = 1f / (1f + Percent(15f));
}
break;
case RewardKind.Ammo:
if ((Object)(object)component != (Object)null)
{
component.ammo = Mathf.Max(0, 1);
}
break;
case RewardKind.Regeneration:
if ((Object)(object)component2 != (Object)null)
{
component2.regen = Mathf.Max(0f, 3f);
}
break;
case RewardKind.BlockCooldown:
if ((Object)(object)componentInChildren != (Object)null)
{
componentInChildren.cdAdd = 0f - Mathf.Max(0f, 0.35f);
}
break;
case RewardKind.BulletSpeed:
if ((Object)(object)component != (Object)null)
{
component.projectileSpeed = 1f + Percent(20f);
}
break;
}
}
private static float Percent(float value)
{
return Mathf.Max(0f, value) / 100f;
}
}
internal abstract class LOQRewardCard : CustomCard
{
protected abstract RewardKind Kind { get; }
protected override string GetTitle()
{
return RewardCards.Title(Kind);
}
protected override string GetDescription()
{
return RewardCards.Description(Kind);
}
protected override CardInfoStat[] GetStats()
{
return (CardInfoStat[])(object)new CardInfoStat[0];
}
protected override Rarity GetRarity()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
return (Rarity)0;
}
protected override GameObject GetCardArt()
{
return null;
}
protected override CardThemeColorType GetTheme()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
return (CardThemeColorType)2;
}
public override string GetModName()
{
return "Zip's LOQ Patches";
}
public override bool GetEnabled()
{
return false;
}
public override void SetupCard(CardInfo cardInfo, Gun gun, ApplyCardStats cardStats, CharacterStatModifiers stats, Block block)
{
if ((Object)(object)gun != (Object)null)
{
gun.damage = 1f;
gun.attackSpeed = 1f;
gun.reloadTime = 1f;
gun.ammo = 0;
gun.projectileSpeed = 1f;
}
if ((Object)(object)stats != (Object)null)
{
stats.movementSpeed = 1f;
stats.health = 1f;
stats.regen = 0f;
}
if ((Object)(object)block != (Object)null)
{
block.cdAdd = 0f;
}
}
public override void OnAddCard(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers stats)
{
}
}
internal sealed class DamageRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.Damage;
}
internal sealed class SpeedRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.Speed;
}
internal sealed class HealthRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.Health;
}
internal sealed class AttackSpeedRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.AttackSpeed;
}
internal sealed class ReloadSpeedRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.ReloadSpeed;
}
internal sealed class AmmoRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.Ammo;
}
internal sealed class RegenerationRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.Regeneration;
}
internal sealed class BlockCooldownRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.BlockCooldown;
}
internal sealed class BulletSpeedRewardCard : LOQRewardCard
{
protected override RewardKind Kind => RewardKind.BulletSpeed;
}