using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using RoundsPartyPack.Abilities; using RoundsPartyPack.Art; using RoundsPartyPack.Cards; using RoundsPartyPack.Draft; using RoundsPartyPack.Framework; using RoundsPartyPack.Settings; using RoundsPartyPack.Visuals; using TMPro; using UnboundLib; using UnboundLib.Cards; using UnboundLib.GameModes; using UnboundLib.Networking; using UnboundLib.Utils; using UnboundLib.Utils.UI; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BetterRounds")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BetterRounds")] [assembly: AssemblyTitle("BetterRounds")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RoundsPartyPack { internal static class CardRegistry { private static readonly MethodInfo BuildCardMethod = typeof(CustomCard).GetMethods(BindingFlags.Static | BindingFlags.Public).Single((MethodInfo method) => method.Name == "BuildCard" && method.IsGenericMethodDefinition && method.GetParameters().Length == 1); private static readonly MethodInfo BuildUnityCardMethod = (from method in typeof(CustomCard).GetMethods(BindingFlags.Static | BindingFlags.Public) where method.Name == "BuildUnityCard" && method.IsGenericMethodDefinition select method).Single(delegate(MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); return parameters.Length == 2 && parameters[0].ParameterType == typeof(GameObject); }); private static readonly MethodInfo DontDestroyOnLoadMethod = typeof(Object).GetMethod("DontDestroyOnLoad", BindingFlags.Static | BindingFlags.Public); private static readonly MethodInfo DestroyImmediateMethod = typeof(Object).GetMethod("DestroyImmediate", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Object) }, null); private static readonly FieldInfo UnboundTemplateCardField = typeof(Unbound).GetField("templateCard", BindingFlags.Static | BindingFlags.NonPublic); private static readonly FieldInfo IsPrefabField = typeof(CustomCard).GetField("isPrefab", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly List ManualCardTypes = (from type in GetLoadableTypes() where type != null && !type.IsAbstract && typeof(BalancedCard).IsAssignableFrom(type) && type != typeof(GeneratedTemplateCard) select type).OrderBy((Type type) => type.FullName, StringComparer.Ordinal).ToList(); private static readonly TemplateCardDefinition[] TemplateCards = PrototypeTemplateCatalog.All; private static bool _registered; internal static int CardCount => ManualCardTypes.Count + TemplateCards.Length; internal static void RegisterAll() { if (_registered) { return; } _registered = true; List list = new List(); foreach (Type manualCardType in ManualCardTypes) { Action action = CardMetadataRegistry.Register; BuildCardMethod.MakeGenericMethod(manualCardType).Invoke(null, new object[1] { action }); } TemplateCardDefinition[] templateCards = TemplateCards; foreach (TemplateCardDefinition templateCardDefinition in templateCards) { if (templateCardDefinition.Spec.IsManagedClass && templateCardDefinition.Spec.Tier >= 4 && !templateCardDefinition.HasUniqueMechanic) { list.Add(templateCardDefinition.Spec.Title); } RegisterTemplate(templateCardDefinition); } if (list.Count <= 0) { return; } throw new InvalidOperationException("BetterRounds upper-tier audit failed. Managed T4/T5 cards without unique mechanics: " + string.Join(", ", list)); } private static IEnumerable GetLoadableTypes() { try { return typeof(CardRegistry).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type type) => type != null); } } private static void RegisterTemplate(TemplateCardDefinition templateCard) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) object? obj = UnboundTemplateCardField?.GetValue(null); CardInfo val = (CardInfo)((obj is CardInfo) ? obj : null); if (val != null) { GameObject val2 = Object.Instantiate(((Component)val).gameObject, new Vector3(0f, 100f, 0f), default(Quaternion), (Transform)null); if (val2.transform.childCount > 0) { GameObject gameObject = ((Component)val2.transform.GetChild(0)).gameObject; DestroyImmediateMethod?.Invoke(null, new object[1] { gameObject }); } val2.transform.parent = null; ((Object)val2).name = templateCard.Spec.Title; DontDestroyOnLoadMethod?.Invoke(null, new object[1] { val2 }); ExtensionMethods.GetOrAddComponent(val2, false).Definition = templateCard; GeneratedTemplateCard orAddComponent = ExtensionMethods.GetOrAddComponent(val2, false); IsPrefabField?.SetValue(orAddComponent, true); CardMetadataRegistry.RegisterTemplate(((Object)val2).name, templateCard); Action action = CardMetadataRegistry.Register; BuildUnityCardMethod.MakeGenericMethod(typeof(GeneratedTemplateCard)).Invoke(null, new object[2] { val2, action }); } } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.betterrounds.rounds", "BetterRounds", "0.34.1")] [BepInProcess("Rounds.exe")] public sealed class Plugin : BaseUnityPlugin { internal const string ModId = "com.betterrounds.rounds"; internal const string ModName = "BetterRounds"; internal const string ModVersion = "0.34.1"; private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("com.betterrounds.rounds"); _harmony.PatchAll(); } private void Start() { BetterRoundsSettings.Initialize((BaseUnityPlugin)(object)this); CardRegistry.RegisterAll(); AbilityGameModeHooks.Initialize(); PickFlowController.Initialize(); DraftEngine.Initialize(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Loaded {0} with {1} discovered cards.", "BetterRounds", CardRegistry.CardCount)); Unbound.RegisterCredits("BetterRounds", new string[1] { "BetterRounds" }, "Discord", "https://discord.gg/FactionRealm"); } } } namespace RoundsPartyPack.Visuals { internal enum FeedbackCueKind { Ready, Shield, Summon, Beacon, Hazard, Consume, Disrupt, Expose, Overheat } internal static class FeedbackCueUtility { private const int SampleRate = 22050; private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Dictionary Clips = new Dictionary(); private static readonly Type AudioClipType = Type.GetType("UnityEngine.AudioClip, UnityEngine.AudioModule") ?? Type.GetType("UnityEngine.AudioClip, UnityEngine"); private static readonly Type AudioSourceType = Type.GetType("UnityEngine.AudioSource, UnityEngine.AudioModule") ?? Type.GetType("UnityEngine.AudioSource, UnityEngine"); internal static void PlayCue(Vector3 position, FeedbackCueKind kind, float volume = 0.12f, float pitch = 1f) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) object clip = GetClip(kind); if (clip != null && !(AudioSourceType == null)) { GameObject val = new GameObject(); ((Object)val).name = "BetterRoundsFeedbackCue"; val.transform.position = position; object? target = ((object)val).GetType().GetMethod("AddComponent", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null)?.Invoke(val, new object[1] { AudioSourceType }); SetProperty(target, "playOnAwake", false); SetProperty(target, "spatialBlend", 0f); SetProperty(target, "volume", Clamp01(volume)); float num = ((pitch <= 0.01f) ? 1f : pitch); SetProperty(target, "pitch", num); SetProperty(target, "clip", clip); InvokeMethod(target, "Play"); float floatProperty = GetFloatProperty(clip, "length", 0.1f); ExtensionMethods.GetOrAddComponent(val, false).Initialize(floatProperty / Math.Max(0.05f, num) + 0.08f); } } private static object GetClip(FeedbackCueKind kind) { if (Clips.TryGetValue(kind, out var value) && value != null) { return value; } value = CreateClip(kind); if (value != null) { Clips[kind] = value; } return value; } private static object CreateClip(FeedbackCueKind kind) { return kind switch { FeedbackCueKind.Ready => BuildTone("BetterRoundsCueReady", 0.09f, 820f, 1120f, 0.38f, 1.6f, 0.18f), FeedbackCueKind.Shield => BuildTone("BetterRoundsCueShield", 0.11f, 540f, 720f, 0.44f, 1.8f, 0.2f), FeedbackCueKind.Summon => BuildTone("BetterRoundsCueSummon", 0.12f, 660f, 990f, 0.48f, 1.9f, 0.17f), FeedbackCueKind.Beacon => BuildTone("BetterRoundsCueBeacon", 0.12f, 580f, 860f, 0.42f, 1.8f, 0.18f), FeedbackCueKind.Hazard => BuildTone("BetterRoundsCueHazard", 0.13f, 320f, 220f, 0.55f, 2.2f, 0.2f), FeedbackCueKind.Consume => BuildTone("BetterRoundsCueConsume", 0.08f, 920f, 1240f, 0.34f, 1.5f, 0.16f), FeedbackCueKind.Disrupt => BuildTone("BetterRoundsCueDisrupt", 0.1f, 430f, 310f, 0.5f, 2f, 0.18f), FeedbackCueKind.Expose => BuildTone("BetterRoundsCueExpose", 0.1f, 740f, 520f, 0.46f, 1.8f, 0.18f), FeedbackCueKind.Overheat => BuildTone("BetterRoundsCueOverheat", 0.14f, 280f, 180f, 0.62f, 2.4f, 0.22f), _ => null, }; } private static object BuildTone(string name, float durationSeconds, float primaryFrequency, float secondaryFrequency, float secondaryMix, float decayPower, float amplitude) { if (AudioClipType == null) { return null; } int num = Math.Max(1, (int)(22050f * Math.Max(0.03f, durationSeconds))); float[] array = new float[num]; double num2 = Math.PI * 2.0; for (int i = 0; i < num; i++) { float num3 = (float)i / 22050f; float num4 = (float)i / (float)Math.Max(1, num - 1); float num5 = (float)Math.Pow(Math.Max(0f, 1f - num4), decayPower); float num6 = (float)Math.Sin(num2 * (double)primaryFrequency * (double)num3); float num7 = ((secondaryFrequency > 0.01f) ? ((float)Math.Sin(num2 * (double)secondaryFrequency * (double)num3)) : 0f); float num8 = num6 + num7 * secondaryMix; array[i] = num8 * num5 * amplitude; } object obj = AudioClipType.GetMethod("Create", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[5] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(bool) }, null)?.Invoke(null, new object[5] { name, num, 1, 22050, false }); InvokeMethod(obj, "SetData", array, 0); return obj; } private static void SetProperty(object target, string propertyName, object value) { PropertyInfo propertyInfo = target?.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(target, value, null); } } private static float GetFloatProperty(object target, string propertyName, float fallback) { PropertyInfo propertyInfo = target?.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanRead && propertyInfo.PropertyType == typeof(float)) { return (float)propertyInfo.GetValue(target, null); } return fallback; } private static void InvokeMethod(object target, string methodName, params object[] args) { (target?.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.Invoke(target, args); } private static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } } internal sealed class FeedbackCueLifetime : MonoBehaviour { private float _destroyAt; internal void Initialize(float duration) { _destroyAt = Time.unscaledTime + Math.Max(0.05f, duration); } private void Update() { if (Time.unscaledTime >= _destroyAt) { Object.Destroy((Object)(object)((Component)this).gameObject); } } } internal sealed class PlayerUpgradeVisualController : MonoBehaviour { private struct UpgradeProfile { internal int ManagedCards; internal int MechanicScore; internal int UniqueMechanicCards; internal int UpperTierCards; internal int CapstoneCards; internal int GemCount; internal CardClass DominantClass; internal Color ThemeColor; internal float VisualPower; } private struct OrbitGem { internal GameObject GameObject; internal float PhaseOffset; internal float VerticalOffset; } private struct BurstGem { internal GameObject GameObject; internal Vector3 Direction; internal float VerticalLift; internal float Speed; internal float Age; internal float Lifetime; internal float StartScale; internal float EndScale; } private struct StatusSignal { internal Vector3 Position { get; } internal float Scale { get; } internal Color Color { get; } internal float Alpha { get; } internal float PulseAmplitude { get; } internal float PulseSpeed { get; } internal float FloatAmplitude { get; } internal float FloatSpeed { get; } internal float SpinSpeed { get; } internal StatusSignal(Vector3 position, float scale, Color color, float alpha, float pulseAmplitude, float pulseSpeed, float floatAmplitude, float floatSpeed, float spinSpeed) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Position = position; Scale = scale; Color = color; Alpha = alpha; PulseAmplitude = pulseAmplitude; PulseSpeed = pulseSpeed; FloatAmplitude = floatAmplitude; FloatSpeed = floatSpeed; SpinSpeed = spinSpeed; } } private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Type ComponentType = typeof(Transform).BaseType; private static readonly MethodInfo GetComponentsInChildrenMethod = typeof(GameObject).GetMethod("GetComponentsInChildren", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(Type), typeof(bool) }, null); private Player _player; private CharacterData _data; private Gun _gun; private Gun _coloredGun; private bool _projectileColorCaptured; private bool _projectileColorApplied; private Color _baseProjectileColor = WhiteColor; private readonly List _orbitGems = new List(); private readonly List _burstGems = new List(); private readonly List _statusGems = new List(); private readonly List _statusSignals = new List(); private UpgradeProfile _profile; private bool _refreshQueued; private float _refreshAt; private float _pickupAccentUntil; private float _lastUpdateAt; private bool _telemetryCueInitialized; private bool _wasChamberReady; private bool _wasReloadModeReady; private bool _wasPerfectParryReady; private bool _wasOrbitWardActive; private bool _wasHazardZoneArmed; private bool _wasOverheatActive; private static Color WhiteColor => new Color(1f, 1f, 1f, 1f); internal static PlayerUpgradeVisualController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void NotifyCardAdded(CardSpec spec) { if (spec != null) { SpawnPickupBurst(spec); _pickupAccentUntil = MaxFloat(_pickupAccentUntil, Time.unscaledTime + 0.9f + (float)spec.Tier * 0.1f); EmitPickupCue(spec); } QueueRefresh(0.08f); } internal void NotifyCardRemoved() { QueueRefresh(0.08f); } private void OnEnable() { _telemetryCueInitialized = false; QueueRefresh(0f); } private void Update() { ResolveReferences(); SyncTintTarget(); if (_refreshQueued && Time.unscaledTime >= _refreshAt) { RefreshProfile(); } float unscaledTime = Time.unscaledTime; float deltaTime = ((_lastUpdateAt <= 0f) ? 0f : MaxFloat(0f, unscaledTime - _lastUpdateAt)); _lastUpdateAt = unscaledTime; UpdateOrbitGems(unscaledTime); UpdateBurstGems(deltaTime); UpdateStatusGems(unscaledTime); } private void OnDisable() { RestoreProjectileColor(); ClearVisualObjects(); } private void QueueRefresh(float delay) { _refreshQueued = true; _refreshAt = Time.unscaledTime + MaxFloat(0f, delay); } private void RefreshProfile() { _refreshQueued = false; _profile = BuildProfile(); SyncPersistentVisuals(); ApplyProjectileColor(); } private UpgradeProfile BuildProfile() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) UpgradeProfile upgradeProfile = default(UpgradeProfile); upgradeProfile.ThemeColor = ResolveClassColor(CardClass.Neutral); UpgradeProfile upgradeProfile2 = upgradeProfile; if (_data?.currentCards == null) { return upgradeProfile2; } Dictionary dictionary = new Dictionary(); int num = 0; int num2 = 0; int num3 = 0; foreach (CardInfo currentCard in _data.currentCards) { if (!CardMetadataRegistry.TryGetMetadata(currentCard, out var metadata) || metadata?.Spec == null) { continue; } CardSpec spec = metadata.Spec; upgradeProfile2.ManagedCards++; if (metadata.HasUniqueMechanic) { upgradeProfile2.UniqueMechanicCards++; } upgradeProfile2.MechanicScore += metadata.MechanicScore; if (spec.Tier >= 4 || spec.Track == CardTrack.Signature || spec.Track == CardTrack.Capstone) { upgradeProfile2.UpperTierCards++; } if (spec.Track == CardTrack.Capstone || spec.Tier >= 5) { upgradeProfile2.CapstoneCards++; } if (spec.IsWildcard) { num++; continue; } if (spec.IsTroll) { num2++; continue; } if (!spec.IsManagedClass) { num3++; continue; } if (!dictionary.ContainsKey(spec.CardClass)) { dictionary[spec.CardClass] = 0; } dictionary[spec.CardClass]++; } upgradeProfile2.DominantClass = ResolveDominantClass(dictionary, num, num2, num3); upgradeProfile2.ThemeColor = ResolveClassColor(upgradeProfile2.DominantClass); upgradeProfile2.GemCount = ResolveGemCount(upgradeProfile2); upgradeProfile2.VisualPower = Clamp01((float)upgradeProfile2.ManagedCards * 0.05f + (float)upgradeProfile2.MechanicScore * 0.05f + (float)upgradeProfile2.UniqueMechanicCards * 0.08f + (float)upgradeProfile2.UpperTierCards * 0.14f + (float)upgradeProfile2.CapstoneCards * 0.24f); return upgradeProfile2; } private static CardClass ResolveDominantClass(Dictionary classCounts, int wildcardCount, int trollCount, int neutralCount) { CardClass result = CardClass.Neutral; int num = 0; foreach (KeyValuePair classCount in classCounts) { if (classCount.Value > num) { result = classCount.Key; num = classCount.Value; } } if (num > 0) { return result; } if (wildcardCount >= trollCount && wildcardCount >= neutralCount && wildcardCount > 0) { return CardClass.Wildcard; } if (trollCount > 0 && trollCount >= neutralCount) { return CardClass.Troll; } return CardClass.Neutral; } private static int ResolveGemCount(UpgradeProfile profile) { if (profile.ManagedCards <= 0) { return 0; } return ClampInt(1 + Math.Min(1, profile.MechanicScore / 4) + Math.Min(2, profile.UpperTierCards / 2) + Math.Min(1, profile.UniqueMechanicCards / 3) + Math.Min(2, profile.CapstoneCards), 1, 6); } private void SyncPersistentVisuals() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) EnsureOrbitGemCount(_profile.GemCount); for (int i = 0; i < _orbitGems.Count; i++) { OrbitGem value = _orbitGems[i]; if (value.GameObject != null) { ApplyTint(value.GameObject, BuildGemTint(_profile.ThemeColor, i, _orbitGems.Count)); float num = 0.24f + _profile.VisualPower * 0.16f + ((_profile.CapstoneCards > 0 && i == _orbitGems.Count - 1) ? 0.08f : 0f); value.GameObject.transform.localScale = new Vector3(num, num, num); _orbitGems[i] = value; } } } private void EnsureOrbitGemCount(int desiredCount) { while (_orbitGems.Count > desiredCount) { int index = _orbitGems.Count - 1; OrbitGem orbitGem = _orbitGems[index]; if (orbitGem.GameObject != null) { Object.Destroy((Object)(object)orbitGem.GameObject); } _orbitGems.RemoveAt(index); } while (_orbitGems.Count < desiredCount) { GameObject val = CreateVisualSeed($"BetterRoundsOrbitGem_{_orbitGems.Count}"); if (val != null) { OrbitGem orbitGem2 = default(OrbitGem); orbitGem2.GameObject = val; orbitGem2.PhaseOffset = 360f * ((float)_orbitGems.Count / (float)Math.Max(1, desiredCount)); orbitGem2.VerticalOffset = 0.12f * ((_orbitGems.Count % 2 == 0) ? 1f : (-1f)); OrbitGem item = orbitGem2; _orbitGems.Add(item); continue; } break; } } private void UpdateOrbitGems(float time) { //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) if (_orbitGems.Count == 0) { return; } float num = ((time <= _pickupAccentUntil) ? 0.18f : 0f); for (int i = 0; i < _orbitGems.Count; i++) { OrbitGem value = _orbitGems[i]; if (value.GameObject != null) { bool flag = _profile.CapstoneCards > 0 && i == _orbitGems.Count - 1; float num2 = (flag ? 38f : (62f + _profile.VisualPower * 36f)); float num3 = time * num2 + value.PhaseOffset; float num4 = (flag ? (0.3f + (float)_profile.CapstoneCards * 0.05f) : (0.55f + _profile.VisualPower * 0.35f + (float)i * 0.04f)); float num5 = (flag ? (0.92f + 0.06f * Sin(time * 2.8f + (float)i)) : (value.VerticalOffset + 0.14f * Sin(time * 2.1f + (float)i))); Vector3 position = (flag ? new Vector3(Cos(DegToRad(num3)) * num4, num5, 0f) : new Vector3(Cos(DegToRad(num3)) * num4, num5 + Sin(DegToRad(num3)) * 0.1f, 0f)); SetTransformLocalPosition(value.GameObject.transform, position); float num6 = 1f + 0.1f * Sin(time * 3.1f + (float)i); if (num > 0f) { num6 += num; } float num7 = 0.24f + _profile.VisualPower * 0.16f + (flag ? 0.08f : 0f); value.GameObject.transform.localScale = new Vector3(num7 * num6, num7 * num6, num7 * num6); SetTransformLocalRotation(value.GameObject.transform, Quaternion.Euler(0f, 0f, 0f - num3)); _orbitGems[i] = value; } } } private void SpawnPickupBurst(CardSpec spec) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) int num = ((spec.Tier >= 5) ? 4 : ((spec.Tier >= 4) ? 3 : ((spec.Tier < 3) ? 1 : 2))); Color a = ResolveClassColor(spec.IsWildcard ? CardClass.Wildcard : (spec.IsTroll ? CardClass.Troll : spec.CardClass)); for (int i = 0; i < num; i++) { GameObject val = CreateVisualSeed($"BetterRoundsBurstGem_{i}"); if (val == null) { break; } ApplyTint(val, LerpColor(a, WhiteColor, 0.2f + (float)spec.Tier * 0.05f)); float num2 = 0.22f + (float)spec.Tier * 0.04f; val.transform.localScale = new Vector3(num2, num2, num2); BurstGem burstGem = default(BurstGem); burstGem.GameObject = val; burstGem.Direction = BuildDirection(360f / (float)num * (float)i); burstGem.VerticalLift = 0.25f + 0.05f * (float)i; burstGem.StartScale = num2; burstGem.EndScale = num2 * 0.55f; burstGem.Lifetime = 0.45f + (float)spec.Tier * 0.08f; burstGem.Speed = 0.8f + (float)spec.Tier * 0.12f; BurstGem item = burstGem; _burstGems.Add(item); } } private void UpdateStatusGems(float time) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) BuildStatusSignals(); EnsureStatusGemCount(_statusSignals.Count); for (int i = 0; i < _statusSignals.Count; i++) { GameObject val = _statusGems[i]; if (val != null) { StatusSignal statusSignal = _statusSignals[i]; Vector3 position = statusSignal.Position; if (statusSignal.FloatAmplitude > 0.001f) { position.y += statusSignal.FloatAmplitude * Sin(time * statusSignal.FloatSpeed + (float)i * 0.7f); } SetTransformLocalPosition(val.transform, position); float num = statusSignal.Scale; if (statusSignal.PulseAmplitude > 0.001f) { num *= 1f + statusSignal.PulseAmplitude * Sin(time * statusSignal.PulseSpeed + (float)i * 0.8f); } val.transform.localScale = new Vector3(num, num, num); SetTransformLocalRotation(val.transform, Quaternion.Euler(0f, 0f, time * statusSignal.SpinSpeed + (float)i * 18f)); ApplyTint(val, statusSignal.Color); SetVisualAlpha(val, statusSignal.Alpha); } } } private void BuildStatusSignals() { //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_050b: Unknown result type (might be due to invalid IL or missing references) //IL_0529: Unknown result type (might be due to invalid IL or missing references) //IL_0574: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_05dd: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) //IL_0646: Unknown result type (might be due to invalid IL or missing references) //IL_0664: Unknown result type (might be due to invalid IL or missing references) //IL_0758: Unknown result type (might be due to invalid IL or missing references) //IL_0776: Unknown result type (might be due to invalid IL or missing references) //IL_07c1: Unknown result type (might be due to invalid IL or missing references) //IL_07df: Unknown result type (might be due to invalid IL or missing references) //IL_06d6: Unknown result type (might be due to invalid IL or missing references) //IL_06f4: Unknown result type (might be due to invalid IL or missing references) //IL_082a: Unknown result type (might be due to invalid IL or missing references) //IL_0848: Unknown result type (might be due to invalid IL or missing references) //IL_0893: Unknown result type (might be due to invalid IL or missing references) //IL_08b1: Unknown result type (might be due to invalid IL or missing references) //IL_08fc: Unknown result type (might be due to invalid IL or missing references) //IL_091a: Unknown result type (might be due to invalid IL or missing references) //IL_0965: Unknown result type (might be due to invalid IL or missing references) //IL_0983: Unknown result type (might be due to invalid IL or missing references) //IL_09ce: Unknown result type (might be due to invalid IL or missing references) //IL_09ec: Unknown result type (might be due to invalid IL or missing references) //IL_0a37: Unknown result type (might be due to invalid IL or missing references) //IL_0a55: Unknown result type (might be due to invalid IL or missing references) //IL_0ab0: Unknown result type (might be due to invalid IL or missing references) //IL_0ace: Unknown result type (might be due to invalid IL or missing references) //IL_0b11: Unknown result type (might be due to invalid IL or missing references) //IL_0b2f: Unknown result type (might be due to invalid IL or missing references) //IL_0b75: Unknown result type (might be due to invalid IL or missing references) //IL_0b93: Unknown result type (might be due to invalid IL or missing references) //IL_0bd9: Unknown result type (might be due to invalid IL or missing references) //IL_0bf7: Unknown result type (might be due to invalid IL or missing references) //IL_0c3d: Unknown result type (might be due to invalid IL or missing references) //IL_0c5b: Unknown result type (might be due to invalid IL or missing references) //IL_0ca1: Unknown result type (might be due to invalid IL or missing references) //IL_0cbf: Unknown result type (might be due to invalid IL or missing references) //IL_0d05: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Unknown result type (might be due to invalid IL or missing references) //IL_0d86: Unknown result type (might be due to invalid IL or missing references) //IL_0da4: Unknown result type (might be due to invalid IL or missing references) //IL_1051: Unknown result type (might be due to invalid IL or missing references) //IL_107f: Unknown result type (might be due to invalid IL or missing references) //IL_10c6: Unknown result type (might be due to invalid IL or missing references) //IL_10e4: Unknown result type (might be due to invalid IL or missing references) //IL_0f94: Unknown result type (might be due to invalid IL or missing references) //IL_0f79: Unknown result type (might be due to invalid IL or missing references) //IL_0f99: Unknown result type (might be due to invalid IL or missing references) //IL_0fba: Unknown result type (might be due to invalid IL or missing references) //IL_0e82: Unknown result type (might be due to invalid IL or missing references) //IL_0e87: Unknown result type (might be due to invalid IL or missing references) //IL_0ea8: Unknown result type (might be due to invalid IL or missing references) //IL_0e58: Unknown result type (might be due to invalid IL or missing references) //IL_0e5d: Unknown result type (might be due to invalid IL or missing references) //IL_0e67: Unknown result type (might be due to invalid IL or missing references) //IL_0e4b: Unknown result type (might be due to invalid IL or missing references) //IL_0fcf: Unknown result type (might be due to invalid IL or missing references) //IL_0ebd: Unknown result type (might be due to invalid IL or missing references) _statusSignals.Clear(); if (_player == null) { return; } PlayerAbilityController.AbilityVisualTelemetry telemetry = ((Component)_player).GetComponent()?.GetVisualTelemetry() ?? default(PlayerAbilityController.AbilityVisualTelemetry); DraftEconomyController component = ((Component)_player).GetComponent(); PlayerMarkStatusController component2 = ((Component)_player).GetComponent(); PlayerDisruptionStatusController component3 = ((Component)_player).GetComponent(); int num = component?.PeekQueuedRoundBonuses() ?? 0; int num2 = component2?.GetTotalMarks() ?? 0; bool flag = component3?.HasActiveDisruption() ?? false; float num3 = component3?.GetVisualIntensity() ?? 0f; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; if (_data?.currentCards != null) { foreach (CardInfo currentCard in _data.currentCards) { if (CardMetadataRegistry.TryGetMetadata(currentCard, out var metadata) && metadata?.Spec?.DraftTags != null) { IReadOnlyList draftTags = metadata.Spec.DraftTags; flag2 |= draftTags.Any((string tag) => string.Equals(tag, "teleport", StringComparison.OrdinalIgnoreCase)); flag3 |= draftTags.Any((string tag) => string.Equals(tag, "drill", StringComparison.OrdinalIgnoreCase)); flag4 |= draftTags.Any((string tag) => string.Equals(tag, "reflect", StringComparison.OrdinalIgnoreCase)); flag5 |= draftTags.Any((string tag) => string.Equals(tag, "orbit", StringComparison.OrdinalIgnoreCase)); flag6 |= draftTags.Any((string tag) => string.Equals(tag, "shield", StringComparison.OrdinalIgnoreCase)); flag7 |= draftTags.Any((string tag) => string.Equals(tag, "minion", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "helper", StringComparison.OrdinalIgnoreCase)); flag8 |= draftTags.Any((string tag) => string.Equals(tag, "spawn", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "remnant", StringComparison.OrdinalIgnoreCase)); } } } EmitTelemetryCues(telemetry); if (telemetry.CounterChargeReady || telemetry.BlockGhostReady) { _statusSignals.Add(new StatusSignal(new Vector3(-0.48f, 0.42f, 0f), 0.2f, new Color(0.32f, 0.72f, 1f, 1f), 0.96f, 0.08f, 3.6f, 0.04f, 2.4f, 72f)); } if (telemetry.PerfectParryReady) { _statusSignals.Add(new StatusSignal(new Vector3(-0.54f, 0.68f, 0f), 0.21f, new Color(0.84f, 0.94f, 1f, 1f), 0.98f, 0.1f, 4.6f, 0.04f, 2.6f, 92f)); } if (telemetry.ChamberReady) { _statusSignals.Add(new StatusSignal(new Vector3(0.48f, 0.42f, 0f), 0.22f, new Color(0.99f, 0.84f, 0.22f, 1f), 0.98f, 0.1f, 4.4f, 0.03f, 2f, 85f)); } if (telemetry.LastShellReady) { _statusSignals.Add(new StatusSignal(new Vector3(0.34f, 0.12f, 0f), 0.19f, new Color(1f, 0.52f, 0.16f, 1f), 0.95f, 0.12f, 5.4f, 0.03f, 2f, 96f)); } if (telemetry.ReloadChargeReady) { _statusSignals.Add(new StatusSignal(new Vector3(0.54f, 0.72f, 0f), 0.17f, new Color(1f, 0.92f, 0.42f, 1f), 0.95f, 0.09f, 4.1f, 0.03f, 1.9f, 82f)); } if (telemetry.ReloadModeReady) { _statusSignals.Add(new StatusSignal(new Vector3(0.56f, 0.58f, 0f), 0.18f, new Color(0.96f, 0.8f, 0.3f, 1f), 0.96f, 0.12f, 4.8f, 0.03f, 2.1f, 108f)); } if (telemetry.KillChargeReady) { _statusSignals.Add(new StatusSignal(new Vector3(-0.34f, 0.88f, 0f), 0.18f, new Color(1f, 0.38f, 0.24f, 1f), 0.95f, 0.11f, 4.9f, 0.04f, 2.2f, 90f)); } if (telemetry.MissChargeReady) { _statusSignals.Add(new StatusSignal(new Vector3(-0.48f, -0.04f, 0f), 0.17f, new Color(0.62f, 0.82f, 1f, 1f), 0.9f, 0.08f, 3.8f, 0.03f, 1.8f, 66f)); } if (telemetry.MarkChargeReady) { _statusSignals.Add(new StatusSignal(new Vector3(0f, 0.62f, 0f), 0.2f, new Color(0.97f, 0.34f, 0.78f, 1f), 0.98f, 0.1f, 4.2f, 0.05f, 2.8f, 94f)); } if (telemetry.HitDisruptionArmed) { _statusSignals.Add(new StatusSignal(new Vector3(-0.12f, 0.84f, 0f), 0.16f, new Color(1f, 0.38f, 0.62f, 1f), 0.9f, 0.08f, 3.6f, 0.03f, 1.8f, 78f)); } if (telemetry.OrbitWardActive) { int num4 = Math.Min(2, Math.Max(1, telemetry.OrbitWardCount)); for (int i = 0; i < num4; i++) { _statusSignals.Add(new StatusSignal(new Vector3(-0.2f + (float)i * 0.4f, 0.98f, 0f), 0.15f, new Color(0.82f, 0.42f, 1f, 1f), 0.92f, 0.1f, 4.4f, 0.05f, 2.6f, (i % 2 == 0) ? 120f : (-120f))); } } else if (flag5) { _statusSignals.Add(new StatusSignal(new Vector3(-0.56f, 0.98f, 0f), 0.13f, new Color(0.76f, 0.54f, 1f, 1f), 0.76f, 0.05f, 2.6f, 0.03f, 1.8f, 96f)); } if (telemetry.HazardZoneArmed) { _statusSignals.Add(new StatusSignal(new Vector3(0f, -0.34f, 0f), 0.18f, new Color(1f, 0.52f, 0.22f, 1f), 0.84f, 0.08f, 3f, 0.02f, 1.5f, 44f)); } if (telemetry.GroundedStanceActive) { _statusSignals.Add(new StatusSignal(new Vector3(-0.26f, -0.28f, 0f), 0.18f, new Color(0.78f, 0.88f, 1f, 1f), 0.86f, 0.07f, 2.6f, 0.02f, 1.4f, 34f)); } if (telemetry.WallStanceActive) { _statusSignals.Add(new StatusSignal(new Vector3(0.52f, -0.04f, 0f), 0.17f, new Color(0.72f, 0.9f, 1f, 1f), 0.88f, 0.07f, 3.2f, 0.03f, 1.6f, 44f)); } if (telemetry.LowHealthStanceActive) { _statusSignals.Add(new StatusSignal(new Vector3(-0.18f, 0.06f, 0f), 0.21f, new Color(0.94f, 0.2f, 0.18f, 1f), 0.92f, 0.14f, 5f, 0.02f, 1.8f, 52f)); } if (telemetry.ComebackRushActive) { _statusSignals.Add(new StatusSignal(new Vector3(0.32f, 1.04f, 0f), 0.18f, new Color(1f, 0.72f, 0.26f, 1f), 0.96f, 0.12f, 4.8f, 0.05f, 2.5f, 88f)); } if (telemetry.EmptyMagRushActive) { _statusSignals.Add(new StatusSignal(new Vector3(0f, -0.16f, 0f), 0.24f, new Color(1f, 0.34f, 0.22f, 1f), 0.92f, 0.14f, 5.2f, 0.03f, 2f, 68f)); } if (telemetry.OverheatActive) { _statusSignals.Add(new StatusSignal(new Vector3(0.22f, -0.3f, 0f), 0.2f, new Color(1f, 0.42f, 0.18f, 1f), 0.96f, 0.13f, 5.6f, 0.03f, 2f, 76f)); } if (flag) { float alpha = Clamp01(0.72f + num3 * 0.6f); _statusSignals.Add(new StatusSignal(new Vector3(0f, 0.18f, 0f), 0.19f, new Color(0.98f, 0.18f, 0.18f, 1f), alpha, 0.1f, 5.4f, 0.02f, 1.8f, -58f)); } if (flag2) { _statusSignals.Add(new StatusSignal(new Vector3(-0.58f, 0.22f, 0f), 0.13f, new Color(0.96f, 0.42f, 0.86f, 1f), 0.78f, 0.05f, 2.4f, 0.03f, 1.6f, 72f)); } if (flag3) { _statusSignals.Add(new StatusSignal(new Vector3(0.6f, 0.24f, 0f), 0.13f, new Color(1f, 0.82f, 0.32f, 1f), 0.8f, 0.05f, 2.5f, 0.03f, 1.6f, 56f)); } if (flag4) { _statusSignals.Add(new StatusSignal(new Vector3(0.58f, 0.9f, 0f), 0.13f, new Color(0.7f, 0.9f, 1f, 1f), 0.78f, 0.05f, 2.7f, 0.03f, 1.7f, -84f)); } if (flag6) { _statusSignals.Add(new StatusSignal(new Vector3(-0.6f, 0.56f, 0f), 0.14f, new Color(0.54f, 0.86f, 1f, 1f), 0.8f, 0.05f, 2.6f, 0.03f, 1.7f, 34f)); } if (flag7) { _statusSignals.Add(new StatusSignal(new Vector3(0.18f, 0.96f, 0f), 0.13f, new Color(0.7f, 1f, 0.64f, 1f), 0.78f, 0.06f, 2.8f, 0.04f, 1.9f, 104f)); } if (flag8) { _statusSignals.Add(new StatusSignal(new Vector3(0.18f, -0.44f, 0f), 0.14f, new Color(1f, 0.68f, 0.24f, 1f), 0.8f, 0.07f, 3f, 0.03f, 1.8f, 52f)); } if (num > 0) { int num5 = Math.Min(3, num); for (int j = 0; j < num5; j++) { _statusSignals.Add(new StatusSignal(new Vector3(-0.22f + (float)j * 0.16f, 1.06f, 0f), 0.15f, new Color(0.98f, 0.92f, 0.48f, 1f), 0.94f, 0.06f, 3.1f, 0.04f, 1.7f, 54f)); } } if (telemetry.CadenceInterval > 0) { int num6 = Math.Min(4, telemetry.CadenceInterval); int num7 = Math.Min(num6, telemetry.CadenceProgress); for (int k = 0; k < num6; k++) { bool flag9 = k < num7; bool flag10 = telemetry.CadenceReady && k == num6 - 1; Color color = (Color)(flag10 ? new Color(1f, 0.86f, 0.28f, 1f) : (flag9 ? LerpColor(_profile.ThemeColor, WhiteColor, 0.18f) : new Color(0.42f, 0.46f, 0.52f, 1f))); _statusSignals.Add(new StatusSignal(new Vector3(0.14f + (float)k * 0.12f, 0.86f, 0f), flag10 ? 0.15f : 0.12f, color, (flag9 || flag10) ? 0.92f : 0.7f, flag10 ? 0.1f : 0.04f, flag10 ? 5f : 2.8f, 0.02f, 1.7f, flag10 ? 74f : 28f)); } } if (telemetry.HeatThreshold > 0) { int num8 = Math.Min(5, telemetry.HeatThreshold); int num9 = Math.Min(num8, telemetry.HeatStacks); for (int l = 0; l < num8; l++) { bool flag11 = l < num9; Color color2 = (flag11 ? new Color(1f, 0.54f, 0.18f, 1f) : new Color(0.46f, 0.4f, 0.36f, 1f)); _statusSignals.Add(new StatusSignal(new Vector3(-0.08f + (float)l * 0.1f, -0.4f, 0f), flag11 ? 0.12f : 0.1f, color2, flag11 ? 0.92f : 0.68f, flag11 ? 0.08f : 0.03f, flag11 ? 4.3f : 2.4f, 0.02f, 1.4f, flag11 ? 62f : 18f)); } } if (num2 > 0) { _statusSignals.Add(new StatusSignal(new Vector3(0f, 1.18f, 0f), 0.19f + (float)Math.Min(2, num2) * 0.03f, new Color(0.96f, 0.24f, 0.62f, 1f), 0.96f, 0.12f, 4.8f, 0.05f, 3.1f, 92f)); if (num2 > 1) { _statusSignals.Add(new StatusSignal(new Vector3(0.18f, 1.1f, 0f), 0.12f, new Color(1f, 0.48f, 0.74f, 1f), 0.88f, 0.08f, 3.7f, 0.04f, 2.2f, 60f)); } } } private void EmitPickupCue(CardSpec spec) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (spec?.DraftTags != null && spec.DraftTags.Count != 0) { IReadOnlyList draftTags = spec.DraftTags; if (draftTags.Any((string tag) => string.Equals(tag, "shield", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "block", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "parry", StringComparison.OrdinalIgnoreCase))) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Shield, 0.11f, 1.02f); } else if (draftTags.Any((string tag) => string.Equals(tag, "summon", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "orbit", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "ward", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "helper", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "minion", StringComparison.OrdinalIgnoreCase))) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Summon, 0.11f, 1.04f); } else if (draftTags.Any((string tag) => string.Equals(tag, "hazard", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "zone", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "mine", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "remnant", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "spawn", StringComparison.OrdinalIgnoreCase))) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Hazard, 0.1f); } else if (draftTags.Any((string tag) => string.Equals(tag, "reload", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "chamber", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "weapon mode", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "teleport", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "reflect", StringComparison.OrdinalIgnoreCase) || string.Equals(tag, "drill", StringComparison.OrdinalIgnoreCase))) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Ready, 0.1f); } } } private void EmitTelemetryCues(PlayerAbilityController.AbilityVisualTelemetry telemetry) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) if (!_telemetryCueInitialized) { _telemetryCueInitialized = true; _wasChamberReady = telemetry.ChamberReady; _wasReloadModeReady = telemetry.ReloadModeReady; _wasPerfectParryReady = telemetry.PerfectParryReady; _wasOrbitWardActive = telemetry.OrbitWardActive; _wasHazardZoneArmed = telemetry.HazardZoneArmed; _wasOverheatActive = telemetry.OverheatActive; return; } if (telemetry.ChamberReady && !_wasChamberReady) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Ready, 0.09f, 1.06f); } if (telemetry.ReloadModeReady && !_wasReloadModeReady) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Ready, 0.1f, 0.94f); } if (telemetry.PerfectParryReady && !_wasPerfectParryReady) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Shield, 0.1f, 1.08f); } if (telemetry.OrbitWardActive && !_wasOrbitWardActive) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Summon, 0.1f); } if (telemetry.HazardZoneArmed && !_wasHazardZoneArmed) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Hazard, 0.08f, 0.96f); } if (telemetry.OverheatActive && !_wasOverheatActive) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Overheat, 0.12f, 0.92f); } _wasChamberReady = telemetry.ChamberReady; _wasReloadModeReady = telemetry.ReloadModeReady; _wasPerfectParryReady = telemetry.PerfectParryReady; _wasOrbitWardActive = telemetry.OrbitWardActive; _wasHazardZoneArmed = telemetry.HazardZoneArmed; _wasOverheatActive = telemetry.OverheatActive; } private void EnsureStatusGemCount(int desiredCount) { while (_statusGems.Count > desiredCount) { int index = _statusGems.Count - 1; GameObject val = _statusGems[index]; if (val != null) { Object.Destroy((Object)(object)val); } _statusGems.RemoveAt(index); } while (_statusGems.Count < desiredCount) { GameObject val2 = CreateVisualSeed($"BetterRoundsStatusGem_{_statusGems.Count}"); if (val2 != null) { _statusGems.Add(val2); continue; } break; } } private void UpdateBurstGems(float deltaTime) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) for (int num = _burstGems.Count - 1; num >= 0; num--) { BurstGem value = _burstGems[num]; value.Age += deltaTime; if (value.GameObject == null || value.Age >= value.Lifetime) { if (value.GameObject != null) { Object.Destroy((Object)(object)value.GameObject); } _burstGems.RemoveAt(num); } else { float num2 = value.Age / value.Lifetime; float num3 = 1f - (float)Math.Pow(1f - num2, 2.0); Vector3 position = value.Direction * (value.Speed * num3) + new Vector3(0f, 1f, 0f) * value.VerticalLift * num3; SetTransformLocalPosition(value.GameObject.transform, position); float num4 = LerpFloat(value.StartScale, value.EndScale, num3); value.GameObject.transform.localScale = new Vector3(num4, num4, num4); SetTransformLocalRotation(value.GameObject.transform, Quaternion.Euler(0f, 0f, num3 * 540f)); SetVisualAlpha(value.GameObject, 1f - num2); _burstGems[num] = value; } } } private GameObject CreateVisualSeed(string name) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); GameObject val2 = (GameObject)((val != null) ? ((object)Object.Instantiate(val, new Vector3(0f, 0f, 0f), default(Quaternion), (Transform)null)) : ((object)new GameObject())); if (val2 == null) { return null; } ((Object)val2).name = name; SetTransformParent(val2.transform, ((Component)this).transform); SetTransformLocalPosition(val2.transform, new Vector3(0f, 0f, 0f)); SetTransformLocalRotation(val2.transform, default(Quaternion)); val2.transform.localScale = Vector3.one * 0.25f; return val2; } private void SyncTintTarget() { if (_coloredGun != _gun) { RestoreProjectileColor(); _coloredGun = _gun; _projectileColorCaptured = _coloredGun != null && TryGetMemberValue(_coloredGun, "projectileColor", out _baseProjectileColor); _projectileColorApplied = false; if (_coloredGun != null) { ApplyProjectileColor(); } } } private void ApplyProjectileColor() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if (_coloredGun != null && _projectileColorCaptured && !(_baseProjectileColor.a < 0.2f)) { if (_profile.ManagedCards <= 0) { RestoreProjectileColor(); return; } float t = Clamp01(0.22f + _profile.VisualPower * 0.48f); Color value = LerpColor(_baseProjectileColor, _profile.ThemeColor, t); value.a = MaxFloat(_baseProjectileColor.a, 0.85f); SetMemberValue(_coloredGun, "projectileColor", value); _projectileColorApplied = true; } } private void RestoreProjectileColor() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (_coloredGun != null && _projectileColorCaptured && _projectileColorApplied) { SetMemberValue(_coloredGun, "projectileColor", _baseProjectileColor); } _projectileColorApplied = false; } private void ResolveReferences() { if (_player == null) { _player = ((Component)this).GetComponent(); } if (_player != null) { _data = _player.data ?? ((Component)_player).GetComponent(); _gun = ((Component)_player).GetComponentInChildren(); } } private void ClearVisualObjects() { for (int i = 0; i < _orbitGems.Count; i++) { if (_orbitGems[i].GameObject != null) { Object.Destroy((Object)(object)_orbitGems[i].GameObject); } } for (int j = 0; j < _burstGems.Count; j++) { if (_burstGems[j].GameObject != null) { Object.Destroy((Object)(object)_burstGems[j].GameObject); } } for (int k = 0; k < _statusGems.Count; k++) { if (_statusGems[k] != null) { Object.Destroy((Object)(object)_statusGems[k]); } } _orbitGems.Clear(); _burstGems.Clear(); _statusGems.Clear(); _statusSignals.Clear(); } private static Color BuildGemTint(Color baseColor, int index, int total) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) float t = ((total <= 1) ? 0.08f : ((float)index / (float)Math.Max(1, total - 1) * 0.26f)); Color result = LerpColor(baseColor, WhiteColor, t); result.a = 0.95f; return result; } private static void ApplyTint(GameObject target, Color color) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) foreach (object component in GetComponents(target)) { if (component == null) { continue; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } object obj = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(component, null); if (obj != null) { PropertyInfo property2 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property2 != null && property2.CanWrite && property2.PropertyType == typeof(Color)) { property2.SetValue(obj, color, null); } } } } private static void SetVisualAlpha(GameObject target, float alpha) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) foreach (object component in GetComponents(target)) { if (component == null) { continue; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { Color val = (Color)property.GetValue(component, null); val.a = alpha; property.SetValue(component, val, null); } object obj = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(component, null); if (obj != null) { PropertyInfo property2 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property2 != null && property2.CanWrite && property2.PropertyType == typeof(Color)) { Color val2 = (Color)property2.GetValue(obj, null); val2.a = alpha; property2.SetValue(obj, val2, null); } } } } private static IEnumerable GetComponents(GameObject target) { if (target == null || GetComponentsInChildrenMethod == null || ComponentType == null) { return Array.Empty(); } return (GetComponentsInChildrenMethod.Invoke(target, new object[2] { ComponentType, true }) as IEnumerable) ?? Array.Empty(); } private static Color ResolveClassColor(CardClass cardClass) { //IL_0049: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) return (Color)(cardClass switch { CardClass.Marksman => new Color(0.95f, 0.78f, 0.22f, 1f), CardClass.Bulwark => new Color(0.25f, 0.57f, 0.92f, 1f), CardClass.Skirmisher => new Color(0.6f, 0.9f, 0.88f, 1f), CardClass.Trickster => new Color(0.93f, 0.42f, 0.82f, 1f), CardClass.Demolitionist => new Color(0.95f, 0.4f, 0.24f, 1f), CardClass.Brawler => new Color(0.88f, 0.24f, 0.28f, 1f), CardClass.Arcanist => new Color(0.44f, 0.82f, 1f, 1f), CardClass.Gambler => new Color(0.97f, 0.84f, 0.38f, 1f), CardClass.Wildcard => new Color(0.94f, 0.9f, 0.56f, 1f), CardClass.Troll => new Color(0.92f, 0.28f, 0.3f, 1f), _ => new Color(0.8f, 0.84f, 0.92f, 1f), }); } private static bool TryGetMemberValue(object target, string memberName, out T value) { value = default(T); object memberValue = GetMemberValue(target, memberName); if (memberValue == null) { return false; } if (memberValue is T val) { value = val; return true; } try { value = (T)Convert.ChangeType(memberValue, typeof(T)); return true; } catch { return false; } } private static object GetMemberValue(object target, string memberName) { Type type = target?.GetType(); if (type == null) { return null; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(target, null); } return null; } private static void SetMemberValue(object target, string memberName, T value) { Type type = target?.GetType(); if (type == null) { return; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(target, value); return; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); } } private static void SetTransformParent(Transform child, Transform parent) { PropertyInfo propertyInfo = ((object)child)?.GetType().GetProperty("parent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(child, parent, null); } } private static void SetTransformLocalPosition(Transform transform, Vector3 position) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) PropertyInfo propertyInfo = ((object)transform)?.GetType().GetProperty("localPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(transform, position, null); } } private static void SetTransformLocalRotation(Transform transform, Quaternion rotation) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) PropertyInfo propertyInfo = ((object)transform)?.GetType().GetProperty("localRotation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(transform, rotation, null); } } private static float Sin(float value) { return (float)Math.Sin(value); } private static float Cos(float value) { return (float)Math.Cos(value); } private static float DegToRad(float degrees) { return degrees * (MathF.PI / 180f); } private static Vector3 BuildDirection(float angleDegrees) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) float value = DegToRad(angleDegrees); return new Vector3(Cos(value), Sin(value), 0f); } private static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } private static float MaxFloat(float a, float b) { if (!(a > b)) { return b; } return a; } private static int ClampInt(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } private static float LerpFloat(float a, float b, float t) { float num = Clamp01(t); return a + (b - a) * num; } private static Color LerpColor(Color a, Color b, float t) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) float num = Clamp01(t); return new Color(a.r + (b.r - a.r) * num, a.g + (b.g - a.g) * num, a.b + (b.b - a.b) * num, a.a + (b.a - a.a) * num); } } } namespace RoundsPartyPack.Settings { internal static class BetterRoundsSettings { private delegate int IntValueProvider(); private delegate bool BoolValueProvider(); private struct RuntimeSettings { internal int RoundsToWinGame { get; } internal int PointsToWinRound { get; } internal int StarterCardsShownPerDraw { get; } internal int CardsShownPerDraw { get; } internal int StarterCardsSelectedPerDraw { get; } internal int CardsSelectedPerDraw { get; } internal bool BetterRoundsOnlyDraftPool { get; } internal RuntimeSettings(int roundsToWinGame, int pointsToWinRound, int starterCardsShownPerDraw, int cardsShownPerDraw, int starterCardsSelectedPerDraw, int cardsSelectedPerDraw, bool betterRoundsOnlyDraftPool) { RoundsToWinGame = roundsToWinGame; PointsToWinRound = pointsToWinRound; StarterCardsShownPerDraw = starterCardsShownPerDraw; CardsShownPerDraw = cardsShownPerDraw; StarterCardsSelectedPerDraw = starterCardsSelectedPerDraw; CardsSelectedPerDraw = cardsSelectedPerDraw; BetterRoundsOnlyDraftPool = betterRoundsOnlyDraftPool; } } private interface ISettingBinding { void Refresh(bool interactable); } private sealed class SliderBinding : ISettingBinding { private static readonly MethodInfo SliderSetValueWithoutNotifyMethod = typeof(Slider).GetMethod("SetValueWithoutNotify", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(float) }, null); private static readonly PropertyInfo SliderValueProperty = typeof(Slider).GetProperty("value", BindingFlags.Instance | BindingFlags.Public); private readonly Slider _slider; private readonly TMP_InputField _inputField; private readonly IntValueProvider _valueProvider; internal SliderBinding(Slider slider, TMP_InputField inputField, IntValueProvider valueProvider) { _slider = slider; _inputField = inputField; _valueProvider = valueProvider; } public void Refresh(bool interactable) { int num = _valueProvider(); if (SliderSetValueWithoutNotifyMethod != null) { SliderSetValueWithoutNotifyMethod.Invoke(_slider, new object[1] { (float)num }); } else if (SliderValueProperty != null) { SliderValueProperty.SetValue(_slider, (float)num, null); } _slider.interactable = interactable; if (_inputField != null) { _inputField.SetTextWithoutNotify(num.ToString()); _inputField.interactable = interactable; _inputField.readOnly = !interactable; } } } private sealed class ToggleBinding : ISettingBinding { private static readonly MethodInfo ToggleSetIsOnWithoutNotifyMethod = typeof(Component).Assembly.GetType("UnityEngine.UI.Toggle")?.GetMethod("SetIsOnWithoutNotify", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(bool) }, null); private static readonly PropertyInfo ToggleIsOnProperty = typeof(Component).Assembly.GetType("UnityEngine.UI.Toggle")?.GetProperty("isOn", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo ToggleInteractableProperty = typeof(Component).Assembly.GetType("UnityEngine.UI.Selectable")?.GetProperty("interactable", BindingFlags.Instance | BindingFlags.Public); private readonly Component _toggle; private readonly BoolValueProvider _valueProvider; internal ToggleBinding(Component toggle, BoolValueProvider valueProvider) { _toggle = toggle; _valueProvider = valueProvider; } public void Refresh(bool interactable) { if (_toggle != null) { bool flag = _valueProvider(); if (ToggleSetIsOnWithoutNotifyMethod != null) { ToggleSetIsOnWithoutNotifyMethod.Invoke(_toggle, new object[1] { flag }); } else if (ToggleIsOnProperty != null) { ToggleIsOnProperty.SetValue(_toggle, flag, null); } ToggleInteractableProperty?.SetValue(_toggle, interactable, null); } } } private sealed class SettingsMenuController : MonoBehaviour { private readonly List _bindings = new List(); private TextMeshProUGUI _statusText; internal void Initialize(TextMeshProUGUI statusText, IEnumerable bindings) { _statusText = statusText; _bindings.Clear(); _bindings.AddRange(bindings); Refresh(); } private void OnEnable() { Refresh(); } private void Update() { Refresh(); } private void Refresh() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (_statusText != null) { _statusText.text = GetRoomStateText(); _statusText.color = GetRoomStateColor(); } bool interactable = CanEditLiveRoomSettings(); foreach (ISettingBinding binding in _bindings) { binding.Refresh(interactable); } } } [CompilerGenerated] private static class <>O { public static UnityAction <0>__EmptyMenuAction; public static Action <1>__BuildMenu; public static Action <2>__OnHandshakeCompleted; public static OnLeftDelegate <3>__OnLeftRoom; public static Func <4>__ApplyMatchSettingsHook; public static UnityAction <5>__OnRoundsToWinGameChanged; public static UnityAction <6>__OnPointsToWinRoundChanged; public static UnityAction <7>__OnStarterCardsShownPerDrawChanged; public static UnityAction <8>__OnStarterCardsSelectedPerDrawChanged; public static UnityAction <9>__OnCardsShownPerDrawChanged; public static UnityAction <10>__OnCardsSelectedPerDrawChanged; public static UnityAction <11>__OnBetterRoundsOnlyDraftPoolChanged; } private const string ConfigSection = "BetterRounds"; private const int MinRoundsToWinGame = 1; private const int MaxRoundsToWinGame = 99; private const int MinPointsToWinRound = 1; private const int MaxPointsToWinRound = 99; private const int MinCardsShownPerDraw = 1; private const int MaxCardsShownPerDraw = 99; private const int MinCardsSelectedPerDraw = 1; private const int MaxCardsSelectedPerDraw = 99; private static ConfigEntry _roundsToWinGameConfig; private static ConfigEntry _pointsToWinRoundConfig; private static ConfigEntry _starterCardsShownPerDrawConfig; private static ConfigEntry _cardsShownPerDrawConfig; private static ConfigEntry _starterCardsSelectedPerDrawConfig; private static ConfigEntry _cardsSelectedPerDrawConfig; private static ConfigEntry _betterRoundsOnlyDraftPoolConfig; private static RuntimeSettings _runtimeSettings; private static bool _initialized; internal static int GetCardsShownPerDraw(bool starterPhase) { if (!starterPhase) { return _runtimeSettings.CardsShownPerDraw; } return _runtimeSettings.StarterCardsShownPerDraw; } internal static int GetCardsSelectedPerDraw(bool starterPhase) { if (!starterPhase) { return _runtimeSettings.CardsSelectedPerDraw; } return _runtimeSettings.StarterCardsSelectedPerDraw; } internal static bool UseBetterRoundsOnlyDraftPool() { return _runtimeSettings.BetterRoundsOnlyDraftPool; } internal static void Initialize(BaseUnityPlugin plugin) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Expected O, but got Unknown //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Expected O, but got Unknown if (!_initialized) { _initialized = true; _roundsToWinGameConfig = plugin.Config.Bind("BetterRounds", "Rounds To Win Game", 5, new ConfigDescription("Rounds needed to win the match.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _pointsToWinRoundConfig = plugin.Config.Bind("BetterRounds", "Points To Win Round", 2, new ConfigDescription("Points needed to win each round.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _starterCardsShownPerDrawConfig = plugin.Config.Bind("BetterRounds", "Starter Cards Shown Per Draw", 5, new ConfigDescription("How many cards are shown in the opening game-start draft.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _cardsShownPerDrawConfig = plugin.Config.Bind("BetterRounds", "Cards Shown Per Draw", 5, new ConfigDescription("How many cards are shown during standard in-match drafts.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _starterCardsSelectedPerDrawConfig = plugin.Config.Bind("BetterRounds", "Starter Cards Selected Per Draw", 1, new ConfigDescription("How many cards each player gets to take during the opening game-start draft.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _cardsSelectedPerDrawConfig = plugin.Config.Bind("BetterRounds", "Cards Selected Per Draw", 1, new ConfigDescription("How many cards each player gets to take during standard in-match drafts.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _betterRoundsOnlyDraftPoolConfig = plugin.Config.Bind("BetterRounds", "BetterRounds Only Draft Pool", true, new ConfigDescription("When enabled, draft pools only use BetterRounds-managed cards instead of mixing in default base-game cards.", (AcceptableValueBase)null, Array.Empty())); LoadRuntimeFromConfig(); object obj = <>O.<0>__EmptyMenuAction; if (obj == null) { UnityAction val = EmptyMenuAction; <>O.<0>__EmptyMenuAction = val; obj = (object)val; } Unbound.RegisterMenu("BetterRounds", (UnityAction)obj, (Action)BuildMenu, (GameObject)null, true); Unbound.RegisterHandshake("com.betterrounds.rounds", (Action)OnHandshakeCompleted); object obj2 = <>O.<3>__OnLeftRoom; if (obj2 == null) { OnLeftDelegate val2 = OnLeftRoom; <>O.<3>__OnLeftRoom = val2; obj2 = (object)val2; } Unbound.OnLeftRoom += (OnLeftDelegate)obj2; GameModeManager.AddHook("InitEnd", (Func)ApplyMatchSettingsHook, 0); GameModeManager.AddHook("GameStart", (Func)ApplyMatchSettingsHook, 800); ApplyCurrentMatchSettings(); } } private static void BuildMenu(GameObject menu) { List list = new List(); TextMeshProUGUI val = default(TextMeshProUGUI); MenuHandler.CreateText("BetterRounds Settings", menu, ref val, 60, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateText(" ", menu, ref val, 12, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); TextMeshProUGUI statusText = default(TextMeshProUGUI); MenuHandler.CreateText(string.Empty, menu, ref statusText, 26, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateText(" ", menu, ref val, 18, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateText("Match settings", menu, ref val, 42, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); list.Add(CreateSlider(menu, "Rounds to win game", 1, 99, () => _runtimeSettings.RoundsToWinGame, OnRoundsToWinGameChanged)); list.Add(CreateSlider(menu, "Points to win round", 1, 99, () => _runtimeSettings.PointsToWinRound, OnPointsToWinRoundChanged)); MenuHandler.CreateText(" ", menu, ref val, 18, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateText("Opening draft", menu, ref val, 42, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); list.Add(CreateSlider(menu, "Starter cards shown", 1, 99, () => _runtimeSettings.StarterCardsShownPerDraw, OnStarterCardsShownPerDrawChanged)); list.Add(CreateSlider(menu, "Starter cards selected", 1, 99, () => _runtimeSettings.StarterCardsSelectedPerDraw, OnStarterCardsSelectedPerDrawChanged)); MenuHandler.CreateText(" ", menu, ref val, 18, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); MenuHandler.CreateText("Round drafts", menu, ref val, 42, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); list.Add(CreateSlider(menu, "Cards shown", 1, 99, () => _runtimeSettings.CardsShownPerDraw, OnCardsShownPerDrawChanged)); list.Add(CreateSlider(menu, "Cards selected", 1, 99, () => _runtimeSettings.CardsSelectedPerDraw, OnCardsSelectedPerDrawChanged)); list.Add(CreateToggle(menu, "BetterRounds-only cards", () => _runtimeSettings.BetterRoundsOnlyDraftPool, OnBetterRoundsOnlyDraftPoolChanged)); ExtensionMethods.GetOrAddComponent(menu, false).Initialize(statusText, list); } private static SliderBinding CreateSlider(GameObject menu, string label, int min, int max, IntValueProvider valueProvider, UnityAction onValueChanged) { Slider slider = default(Slider); TMP_InputField componentInChildren = MenuHandler.CreateSlider(label, menu, 30, (float)min, (float)max, (float)valueProvider(), onValueChanged, ref slider, true, (Color?)null, (Direction)0, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null).GetComponentInChildren(); TextMeshProUGUI val = default(TextMeshProUGUI); MenuHandler.CreateText(" ", menu, ref val, 12, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); return new SliderBinding(slider, componentInChildren, valueProvider); } private static ToggleBinding CreateToggle(GameObject menu, string label, BoolValueProvider valueProvider, UnityAction onValueChanged) { GameObject val = MenuHandler.CreateToggle(valueProvider(), label, menu, onValueChanged, 30, false, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); Type type = typeof(Component).Assembly.GetType("UnityEngine.UI.Toggle"); Component toggle = ((type == null) ? null : val.GetComponent(type)); TextMeshProUGUI val2 = default(TextMeshProUGUI); MenuHandler.CreateText(" ", menu, ref val2, 12, true, (Color?)null, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); return new ToggleBinding(toggle, valueProvider); } private static void OnRoundsToWinGameChanged(float value) { _roundsToWinGameConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnPointsToWinRoundChanged(float value) { _pointsToWinRoundConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnStarterCardsShownPerDrawChanged(float value) { _starterCardsShownPerDrawConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnCardsShownPerDrawChanged(float value) { _cardsShownPerDrawConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnStarterCardsSelectedPerDrawChanged(float value) { _starterCardsSelectedPerDrawConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnCardsSelectedPerDrawChanged(float value) { _cardsSelectedPerDrawConfig.Value = Clamp(Mathf.RoundToInt(value), 1, 99); AdoptLocalSettingsIfAuthoritative(); } private static void OnBetterRoundsOnlyDraftPoolChanged(bool value) { _betterRoundsOnlyDraftPoolConfig.Value = value; AdoptLocalSettingsIfAuthoritative(); } private static void AdoptLocalSettingsIfAuthoritative() { if (CanEditLiveRoomSettings()) { LoadRuntimeFromConfig(); ApplyCurrentMatchSettings(); BroadcastRuntimeSettingsIfHost(); } } private static void LoadRuntimeFromConfig() { _runtimeSettings = BuildRuntimeSettings(_roundsToWinGameConfig.Value, _pointsToWinRoundConfig.Value, _starterCardsShownPerDrawConfig.Value, _cardsShownPerDrawConfig.Value, _starterCardsSelectedPerDrawConfig.Value, _cardsSelectedPerDrawConfig.Value, _betterRoundsOnlyDraftPoolConfig.Value); } private static RuntimeSettings BuildRuntimeSettings(int roundsToWinGame, int pointsToWinRound, int starterCardsShownPerDraw, int cardsShownPerDraw, int starterCardsSelectedPerDraw, int cardsSelectedPerDraw, bool betterRoundsOnlyDraftPool) { return new RuntimeSettings(Clamp(roundsToWinGame, 1, 99), Clamp(pointsToWinRound, 1, 99), Clamp(starterCardsShownPerDraw, 1, 99), Clamp(cardsShownPerDraw, 1, 99), Clamp(starterCardsSelectedPerDraw, 1, 99), Clamp(cardsSelectedPerDraw, 1, 99), betterRoundsOnlyDraftPool); } private static void OnHandshakeCompleted() { if (PhotonNetwork.IsMasterClient) { ApplyCurrentMatchSettings(); BroadcastRuntimeSettingsIfHost(); } } private static void BroadcastRuntimeSettingsIfHost() { if (PhotonNetwork.CurrentRoom != null && PhotonNetwork.IsMasterClient) { NetworkingManager.RPC_Others(typeof(BetterRoundsSettings), "RPC_SyncSettings", new object[7] { _runtimeSettings.RoundsToWinGame, _runtimeSettings.PointsToWinRound, _runtimeSettings.StarterCardsShownPerDraw, _runtimeSettings.CardsShownPerDraw, _runtimeSettings.StarterCardsSelectedPerDraw, _runtimeSettings.CardsSelectedPerDraw, _runtimeSettings.BetterRoundsOnlyDraftPool }); } } [UnboundRPC] private static void RPC_SyncSettings(int roundsToWinGame, int pointsToWinRound, int starterCardsShownPerDraw, int cardsShownPerDraw, int starterCardsSelectedPerDraw, int cardsSelectedPerDraw, bool betterRoundsOnlyDraftPool) { _runtimeSettings = BuildRuntimeSettings(roundsToWinGame, pointsToWinRound, starterCardsShownPerDraw, cardsShownPerDraw, starterCardsSelectedPerDraw, cardsSelectedPerDraw, betterRoundsOnlyDraftPool); ApplyCurrentMatchSettings(); } private static IEnumerator ApplyMatchSettingsHook(IGameModeHandler gameModeHandler) { ApplyCurrentMatchSettings(); yield break; } private static void ApplyCurrentMatchSettings() { try { IGameModeHandler currentHandler = GameModeManager.CurrentHandler; if (((currentHandler != null) ? currentHandler.Settings : null) != null) { if (currentHandler.Settings.ContainsKey("roundsToWinGame")) { currentHandler.ChangeSetting("roundsToWinGame", (object)_runtimeSettings.RoundsToWinGame); } if (currentHandler.Settings.ContainsKey("pointsToWinRound")) { currentHandler.ChangeSetting("pointsToWinRound", (object)_runtimeSettings.PointsToWinRound); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds skipped applying match settings: " + ex.GetType().Name)); } } } private static void OnLeftRoom() { LoadRuntimeFromConfig(); ApplyCurrentMatchSettings(); } private static bool CanEditLiveRoomSettings() { if (!PhotonNetwork.OfflineMode && PhotonNetwork.CurrentRoom != null) { return PhotonNetwork.IsMasterClient; } return true; } private static string GetRoomStateText() { if (PhotonNetwork.OfflineMode) { return "Offline match. Changes apply immediately."; } if (PhotonNetwork.CurrentRoom == null) { return "Local defaults. These apply offline and when you host a room."; } if (PhotonNetwork.IsMasterClient) { int num = Mathf.Max(0, PhotonNetwork.CurrentRoom.PlayerCount - 1); if (num <= 0) { return "You are the host. Changes apply immediately and will sync when others join."; } return string.Format("You are the host. Live changes sync to {0} other player{1}.", num, (num == 1) ? string.Empty : "s"); } Player masterClient = PhotonNetwork.MasterClient; string text = ((masterClient != null) ? masterClient.NickName : null); if (string.IsNullOrWhiteSpace(text)) { text = "The host"; } return text + " controls this room. Sliders are locked and show the live host values."; } private static Color GetRoomStateColor() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!CanEditLiveRoomSettings()) { return new Color(1f, 0.79f, 0.43f, 1f); } return new Color(0.74f, 0.95f, 0.82f, 1f); } private static int Clamp(int value, int min, int max) { return Mathf.Clamp(value, min, max); } private static void EmptyMenuAction() { } } } namespace RoundsPartyPack.Patches { [HarmonyPatch(typeof(Gun), "Attack")] internal static class GunAttackAbilityPatch { private static void Prefix(Gun __instance, ref bool __state) { __state = PlayerAbilityController.GetOrAdd(PlayerAbilityController.ResolvePlayerFromGun(__instance))?.PrepareAttack() ?? false; } private static void Postfix(Gun __instance, bool __result, bool __state) { if (__state) { PlayerAbilityController.GetOrAdd(PlayerAbilityController.ResolvePlayerFromGun(__instance))?.CompleteAttack(__result); } } } [HarmonyPatch(typeof(Block), "blocked")] internal static class BlockedAbilityPatch { private static void Postfix(Block __instance) { HealthHandler value = Traverse.Create((object)__instance).Field("health").GetValue(); PlayerAbilityController.GetOrAdd(PlayerAbilityController.ResolvePlayerFromCharacterData((value == null) ? null : Traverse.Create((object)value).Field("data").GetValue()))?.NotifySuccessfulBlock(); } } [HarmonyPatch] internal static class TakeDamageAbilityPatch { private struct DamageState { internal Player DamagingPlayer { get; } internal Player DamagedPlayer { get; } internal CharacterData DamagedData { get; } internal bool Valid { get; } internal DamageState(Player damagingPlayer, Player damagedPlayer, CharacterData damagedData) { DamagingPlayer = damagingPlayer; DamagedPlayer = damagedPlayer; DamagedData = damagedData; Valid = damagingPlayer != null && damagedPlayer != null && damagedData != null; } } private static readonly Type Vector2Type = AccessTools.TypeByName("UnityEngine.Vector2"); private static readonly Type ColorType = AccessTools.TypeByName("UnityEngine.Color"); private static readonly Type GameObjectType = AccessTools.TypeByName("UnityEngine.GameObject"); private static MethodBase TargetMethod() { if (Vector2Type == null || ColorType == null || GameObjectType == null) { return null; } return AccessTools.Method(typeof(HealthHandler), "TakeDamage", new Type[7] { Vector2Type, Vector2Type, ColorType, GameObjectType, typeof(Player), typeof(bool), typeof(bool) }, (Type[])null); } private static void Prefix(HealthHandler __instance, object[] __args, out DamageState __state) { __state = default(DamageState); if (__args == null || __args.Length < 7) { return; } float num = PlayerAbilityController.ReadDamageMagnitude(__args[0]); if (num <= 0.001f) { return; } CharacterData value = Traverse.Create((object)__instance).Field("data").GetValue(); if (value == null || Traverse.Create((object)__instance).Field("isRespawning").GetValue() || !Traverse.Create((object)value).Field("isPlaying").GetValue() || Traverse.Create((object)value).Field("dead").GetValue()) { return; } object obj = __args[6]; bool flag = default(bool); int num2; if (obj is bool) { flag = (bool)obj; num2 = 1; } else { num2 = 0; } int num3 = num2 & (flag ? 1 : 0); Block value2 = Traverse.Create((object)value).Field("block").GetValue(); if (num3 == 0 && PlayerAbilityController.IsBlocking(value2)) { return; } Player val = PlayerAbilityController.ResolvePlayerFromCharacterData(value); object obj2 = __args[4]; Player val2 = (Player)((obj2 is Player) ? obj2 : null); if (val != null && val2 != null) { float num4 = PlayerExposureStatusController.GetOrAdd(val)?.GetDamageTakenMultiplier(val2) ?? 1f; if (num4 > 1.001f) { __args[0] = PlayerAbilityController.ScaleDamageArgument(__args[0], num4); num *= num4; } } PlayerAbilityController.GetOrAdd(val)?.NotifyDamageTaken(num); PlayerAbilityController.GetOrAdd(val2)?.NotifyDamageDealt(num, val); __state = new DamageState(val2, val, value); } private static void Postfix(DamageState __state) { if (__state.Valid && __state.DamagedPlayer != __state.DamagingPlayer && Traverse.Create((object)__state.DamagedData).Field("dead").GetValue()) { PlayerAbilityController.GetOrAdd(__state.DamagingPlayer)?.NotifyKillConfirmed(__state.DamagedPlayer); } } } [HarmonyPatch(typeof(CardChoice), "DoPick", new Type[] { typeof(int), typeof(int), typeof(PickerType) })] internal static class CardChoiceDoPickPatch { private static void Prefix(CardChoice __instance, int __1) { try { DraftEngine.PreparePickPool(__instance, __1); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds skipped pick-pool curation: " + ex.GetType().Name)); } } } private static void Postfix(int __1, PickerType __2) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { PickFlowController.TrackPicker(__1, __2); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds skipped pick tracking: " + ex.GetType().Name)); } } } } [HarmonyPatch(typeof(CardChoice), "Spawn")] internal static class CardChoiceSpawnPatch { private static void Postfix(ref GameObject __result) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) try { if (__result != null) { __result.transform.localScale = PickOfferLayout.GetScale(PickOfferLayout.GetDisplayedCardCount()); PhotonView component = __result.GetComponent(); if (component != null && PhotonNetwork.CurrentRoom != null) { NetworkingManager.RPC_Others(typeof(CardChoiceSpawnPatch), "RPC_ApplyRemoteScale", new object[1] { component.ViewID }); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds skipped card spawn scaling: " + ex.GetType().Name)); } } } [UnboundRPC] private static void RPC_ApplyRemoteScale(int viewId) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) PhotonView val = PhotonView.Find(viewId); if (val != null) { ((Component)val).gameObject.transform.localScale = PickOfferLayout.GetScale(PickOfferLayout.GetDisplayedCardCount()); } } } [HarmonyPatch(typeof(CardChoice), "StartPick")] internal static class CardChoiceStartPickPatch { private static void Prefix(CardChoice __instance) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) try { Transform[] value = Traverse.Create((object)__instance).Field("children").GetValue(); if (value == null || value.Length == 0) { return; } Transform val = value[0]; if (val == null) { return; } Transform[] array = value.Skip(1).ToArray(); foreach (Transform val2 in array) { if (val2 != null) { Object.Destroy((Object)(object)((Component)val2).gameObject); } } int displayedCardCount = PickOfferLayout.GetDisplayedCardCount(); if (displayedCardCount <= 0) { return; } List worldPositions = PickOfferLayout.GetWorldPositions(displayedCardCount); List rotations = PickOfferLayout.GetRotations(displayedCardCount); if (worldPositions.Count < displayedCardCount || rotations.Count < displayedCardCount) { return; } Vector3 scale = PickOfferLayout.GetScale(displayedCardCount); List list = new List { val }; val.position = worldPositions[0]; val.rotation = rotations[0]; val.localScale = scale; for (int j = 1; j < displayedCardCount; j++) { GameObject val3 = Object.Instantiate(((Component)val).gameObject, worldPositions[j], rotations[j], ((Component)__instance).transform); if (val3 != null) { ((Object)val3).name = list.Count.ToString(); val3.transform.localScale = scale; list.Add(val3.transform); } } Traverse.Create((object)__instance).Field("children").SetValue((object)list.ToArray()); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds skipped card-offer relayout: " + ex.GetType().Name)); } } } } } namespace RoundsPartyPack.Framework { internal static class AbilityCardEffects { internal static CardEffectModule CounterCharge(float damageMultiplier, float speedMultiplier) { return new CounterChargeEffect(damageMultiplier, speedMultiplier); } internal static CardEffectModule FirstShotBonus(float damageMultiplier, float speedMultiplier) { return new FirstShotBonusEffect(damageMultiplier, speedMultiplier); } internal static CardEffectModule LastBulletBonus(float damageMultiplier, float speedMultiplier) { return new LastBulletBonusEffect(damageMultiplier, speedMultiplier); } internal static CardEffectModule LastShell(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new LastShellEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule ReloadCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new ReloadChargeEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule KillCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new KillChargeEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule MissCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new MissChargeEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule AirborneBonus(float damageMultiplier, float speedMultiplier) { return new AirborneBonusEffect(damageMultiplier, speedMultiplier); } internal static CardEffectModule BlockAmmo(int ammoGain) { return new BlockAmmoEffect(ammoGain); } internal static CardEffectModule BlockTempo(float duration, float movementMultiplier, float reloadMultiplier) { return new BlockTempoEffect(duration, movementMultiplier, reloadMultiplier); } internal static CardEffectModule BlockGhost(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new BlockGhostEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule PerfectParry(float readyWindowSeconds, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new PerfectParryEffect(readyWindowSeconds, damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule ChamberedRound(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new ChamberedRoundEffect(damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule EmptyMagRush(float duration, float movementMultiplier, float reloadMultiplier) { return new EmptyMagRushEffect(duration, movementMultiplier, reloadMultiplier); } internal static CardEffectModule LowHealthStance(float healthFractionThreshold, float damageMultiplier, float movementMultiplier, float reloadMultiplier, float lifestealFraction) { return new LowHealthStanceEffect(healthFractionThreshold, damageMultiplier, movementMultiplier, reloadMultiplier, lifestealFraction); } internal static CardEffectModule GroundedStance(float damageMultiplier, float movementMultiplier, float reloadMultiplier) { return new GroundedStanceEffect(damageMultiplier, movementMultiplier, reloadMultiplier); } internal static CardEffectModule WallStance(float wallDistance, float damageMultiplier, float speedMultiplier, float reloadMultiplier) { return new WallStanceEffect(wallDistance, damageMultiplier, speedMultiplier, reloadMultiplier); } internal static CardEffectModule PatientShot(float readyDelay, float damageMultiplier, float speedMultiplier) { return new PatientShotEffect(readyDelay, damageMultiplier, speedMultiplier); } internal static CardEffectModule ChainShot(float window, float damageStep, float speedStep, int maxStacks) { return new ChainShotEffect(window, damageStep, speedStep, maxStacks); } internal static CardEffectModule Lifesteal(float fraction) { return new LifestealEffect(fraction); } internal static CardEffectModule PainRush(float duration, float movementMultiplier, float reloadMultiplier) { return new PainRushEffect(duration, movementMultiplier, reloadMultiplier); } internal static CardEffectModule DeathVow(float duration, float damageMultiplier, float movementMultiplier, float reloadMultiplier, int maxStoredCharges) { return new DeathVowEffect(duration, damageMultiplier, movementMultiplier, reloadMultiplier, maxStoredCharges); } internal static CardEffectModule HitCharge(float damageMultiplier, float speedMultiplier) { return new HitChargeEffect(damageMultiplier, speedMultiplier); } internal static CardEffectModule CadenceRound(int everyShots, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { return new CadenceRoundEffect(everyShots, damageMultiplier, speedMultiplier, projectileSizeMultiplier, unblockable); } internal static CardEffectModule HeatMode(int shotsToOverheat, float heatWindow, float perStackDamageMultiplier, float perStackSpeedMultiplier, float overheatDuration, float overheatReloadMultiplier, float overheatSpreadPenalty) { return new HeatModeEffect(shotsToOverheat, heatWindow, perStackDamageMultiplier, perStackSpeedMultiplier, overheatDuration, overheatReloadMultiplier, overheatSpreadPenalty); } internal static CardEffectModule MarkedPrey(int marksApplied, int maxStacks, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, float healFraction) { return new MarkedPreyEffect(marksApplied, maxStacks, damageMultiplier, speedMultiplier, projectileSizeMultiplier, healFraction); } internal static CardEffectModule HitDisruption(float duration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new HitDisruptionEffect(duration, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } internal static CardEffectModule HitExposure(float duration, float damageMultiplier) { return new HitExposureEffect(duration, damageMultiplier); } internal static CardEffectModule BlockZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new BlockZoneEffect(duration, radius, pulseInterval, marksApplied, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } internal static CardEffectModule KillZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new KillZoneEffect(duration, radius, pulseInterval, marksApplied, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } internal static CardEffectModule ReloadMode(int shotsPerReload, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool ignoreWalls, int extraProjectiles, float spreadPenalty) { return new ReloadModeEffect(shotsPerReload, damageMultiplier, speedMultiplier, projectileSizeMultiplier, ignoreWalls, extraProjectiles, spreadPenalty); } internal static CardEffectModule OrbitWard(int count, float range, float pulseInterval, int marksApplied, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new OrbitWardEffect(count, range, pulseInterval, marksApplied, disruptionDuration, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } internal static CardEffectModule TrailZone(float spawnInterval, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new TrailZoneEffect(spawnInterval, duration, radius, pulseInterval, marksApplied, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } internal static CardEffectModule BeaconDrop(float spawnInterval, float duration, float range, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { return new BeaconDropEffect(spawnInterval, duration, range, pulseInterval, marksApplied, exposureDuration, exposureDamageMultiplier, disruptionDuration, movementMultiplier, reloadMultiplier, blockCooldownMultiplier); } } internal sealed class CounterChargeEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal CounterChargeEffect(float damageMultiplier, float speedMultiplier) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddCounterCharge(_damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveCounterCharge(_damageMultiplier, _speedMultiplier); } } internal sealed class FirstShotBonusEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal FirstShotBonusEffect(float damageMultiplier, float speedMultiplier) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddFirstShotBonus(_damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveFirstShotBonus(_damageMultiplier, _speedMultiplier); } } internal sealed class LastBulletBonusEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal LastBulletBonusEffect(float damageMultiplier, float speedMultiplier) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddLastBulletBonus(_damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveLastBulletBonus(_damageMultiplier, _speedMultiplier); } } internal sealed class AirborneBonusEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal AirborneBonusEffect(float damageMultiplier, float speedMultiplier) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddAirborneBonus(_damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveAirborneBonus(_damageMultiplier, _speedMultiplier); } } internal sealed class LastShellEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal LastShellEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddLastShell(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveLastShell(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class ReloadChargeEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal ReloadChargeEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddReloadCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveReloadCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class KillChargeEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal KillChargeEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddKillCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveKillCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class MissChargeEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal MissChargeEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddMissCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveMissCharge(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class BlockAmmoEffect : CardEffectModule { private readonly int _ammoGain; internal override bool IsUniqueMechanic => true; internal BlockAmmoEffect(int ammoGain) { _ammoGain = ammoGain; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddBlockAmmo(_ammoGain); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveBlockAmmo(_ammoGain); } } internal sealed class BlockTempoEffect : CardEffectModule { private readonly float _duration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; internal override bool IsUniqueMechanic => true; internal BlockTempoEffect(float duration, float movementMultiplier, float reloadMultiplier) { _duration = duration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddBlockTempo(_duration, _movementMultiplier, _reloadMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveBlockTempo(_movementMultiplier, _reloadMultiplier); } } internal sealed class BlockGhostEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal BlockGhostEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddBlockGhost(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveBlockGhost(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class PerfectParryEffect : CardEffectModule { private readonly float _readyWindowSeconds; private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "disruption", "precision" }; internal PerfectParryEffect(float readyWindowSeconds, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _readyWindowSeconds = readyWindowSeconds; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddPerfectParry(_readyWindowSeconds, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemovePerfectParry(_readyWindowSeconds, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class ChamberedRoundEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "weapon_mode", "precision" }; internal ChamberedRoundEffect(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddChamberedRound(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveChamberedRound(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class EmptyMagRushEffect : CardEffectModule { private readonly float _duration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "weapon_mode", "stance" }; internal EmptyMagRushEffect(float duration, float movementMultiplier, float reloadMultiplier) { _duration = duration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddEmptyMagRush(_duration, _movementMultiplier, _reloadMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveEmptyMagRush(_movementMultiplier, _reloadMultiplier); } } internal sealed class LowHealthStanceEffect : CardEffectModule { private readonly float _healthFractionThreshold; private readonly float _damageMultiplier; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _lifestealFraction; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "stance" }; internal LowHealthStanceEffect(float healthFractionThreshold, float damageMultiplier, float movementMultiplier, float reloadMultiplier, float lifestealFraction) { _healthFractionThreshold = healthFractionThreshold; _damageMultiplier = damageMultiplier; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _lifestealFraction = lifestealFraction; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddLowHealthStance(_healthFractionThreshold, _damageMultiplier, _movementMultiplier, _reloadMultiplier, _lifestealFraction); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveLowHealthStance(_damageMultiplier, _movementMultiplier, _reloadMultiplier, _lifestealFraction); } } internal sealed class GroundedStanceEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "stance" }; internal GroundedStanceEffect(float damageMultiplier, float movementMultiplier, float reloadMultiplier) { _damageMultiplier = damageMultiplier; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddGroundedStance(_damageMultiplier, _movementMultiplier, _reloadMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveGroundedStance(_damageMultiplier, _movementMultiplier, _reloadMultiplier); } } internal sealed class WallStanceEffect : CardEffectModule { private readonly float _wallDistance; private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _reloadMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "stance", "precision" }; internal WallStanceEffect(float wallDistance, float damageMultiplier, float speedMultiplier, float reloadMultiplier) { _wallDistance = wallDistance; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _reloadMultiplier = reloadMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddWallStance(_wallDistance, _damageMultiplier, _speedMultiplier, _reloadMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveWallStance(_wallDistance, _damageMultiplier, _speedMultiplier, _reloadMultiplier); } } internal sealed class PatientShotEffect : CardEffectModule { private readonly float _readyDelay; private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "precision" }; internal PatientShotEffect(float readyDelay, float damageMultiplier, float speedMultiplier) { _readyDelay = readyDelay; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddPatientShot(_readyDelay, _damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemovePatientShot(_damageMultiplier, _speedMultiplier); } } internal sealed class ChainShotEffect : CardEffectModule { private readonly float _window; private readonly float _damageStep; private readonly float _speedStep; private readonly int _maxStacks; internal override bool IsUniqueMechanic => true; internal ChainShotEffect(float window, float damageStep, float speedStep, int maxStacks) { _window = window; _damageStep = damageStep; _speedStep = speedStep; _maxStacks = maxStacks; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddChainShot(_window, _damageStep, _speedStep, _maxStacks); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveChainShot(_damageStep, _speedStep); } } internal sealed class LifestealEffect : CardEffectModule { private readonly float _fraction; internal override bool IsUniqueMechanic => true; internal LifestealEffect(float fraction) { _fraction = fraction; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddLifesteal(_fraction); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveLifesteal(_fraction); } } internal sealed class PainRushEffect : CardEffectModule { private readonly float _duration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; internal override bool IsUniqueMechanic => true; internal PainRushEffect(float duration, float movementMultiplier, float reloadMultiplier) { _duration = duration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddPainRush(_duration, _movementMultiplier, _reloadMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemovePainRush(_movementMultiplier, _reloadMultiplier); } } internal sealed class DeathVowEffect : CardEffectModule { private readonly float _duration; private readonly float _damageMultiplier; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly int _maxStoredCharges; internal override bool IsUniqueMechanic => true; internal DeathVowEffect(float duration, float damageMultiplier, float movementMultiplier, float reloadMultiplier, int maxStoredCharges) { _duration = duration; _damageMultiplier = damageMultiplier; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _maxStoredCharges = maxStoredCharges; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddDeathVow(_duration, _damageMultiplier, _movementMultiplier, _reloadMultiplier, _maxStoredCharges); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveDeathVow(_damageMultiplier, _movementMultiplier, _reloadMultiplier); } } internal sealed class HitChargeEffect : CardEffectModule { private readonly float _damageMultiplier; private readonly float _speedMultiplier; internal override bool IsUniqueMechanic => true; internal HitChargeEffect(float damageMultiplier, float speedMultiplier) { _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddHitCharge(_damageMultiplier, _speedMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveHitCharge(_damageMultiplier, _speedMultiplier); } } internal sealed class CadenceRoundEffect : CardEffectModule { private readonly int _everyShots; private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _unblockable; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "weapon_mode", "precision" }; internal CadenceRoundEffect(int everyShots, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _everyShots = everyShots; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _unblockable = unblockable; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddCadenceRound(_everyShots, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveCadenceRound(_damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _unblockable); } } internal sealed class HeatModeEffect : CardEffectModule { private readonly int _shotsToOverheat; private readonly float _heatWindow; private readonly float _perStackDamageMultiplier; private readonly float _perStackSpeedMultiplier; private readonly float _overheatDuration; private readonly float _overheatReloadMultiplier; private readonly float _overheatSpreadPenalty; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "weapon_mode" }; internal HeatModeEffect(int shotsToOverheat, float heatWindow, float perStackDamageMultiplier, float perStackSpeedMultiplier, float overheatDuration, float overheatReloadMultiplier, float overheatSpreadPenalty) { _shotsToOverheat = shotsToOverheat; _heatWindow = heatWindow; _perStackDamageMultiplier = perStackDamageMultiplier; _perStackSpeedMultiplier = perStackSpeedMultiplier; _overheatDuration = overheatDuration; _overheatReloadMultiplier = overheatReloadMultiplier; _overheatSpreadPenalty = overheatSpreadPenalty; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddHeatMode(_shotsToOverheat, _heatWindow, _perStackDamageMultiplier, _perStackSpeedMultiplier, _overheatDuration, _overheatReloadMultiplier, _overheatSpreadPenalty); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveHeatMode(_shotsToOverheat, _heatWindow, _perStackDamageMultiplier, _perStackSpeedMultiplier, _overheatDuration, _overheatReloadMultiplier, _overheatSpreadPenalty); } } internal sealed class MarkedPreyEffect : CardEffectModule { private readonly int _marksApplied; private readonly int _maxStacks; private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly float _healFraction; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "disruption", "precision" }; internal MarkedPreyEffect(int marksApplied, int maxStacks, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, float healFraction) { _marksApplied = marksApplied; _maxStacks = maxStacks; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _healFraction = healFraction; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddMarkedPrey(_marksApplied, _maxStacks, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _healFraction); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveMarkedPrey(_marksApplied, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _healFraction); } } internal sealed class HitDisruptionEffect : CardEffectModule { private readonly float _duration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "disruption" }; internal override int MechanicScore => 3; internal HitDisruptionEffect(float duration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _duration = duration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddHitDisruption(_duration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveHitDisruption(_duration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal sealed class HitExposureEffect : CardEffectModule { private readonly float _duration; private readonly float _damageMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "disruption", "precision" }; internal override int MechanicScore => 4; internal HitExposureEffect(float duration, float damageMultiplier) { _duration = duration; _damageMultiplier = damageMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddHitExposure(_duration, _damageMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveHitExposure(_duration, _damageMultiplier); } } internal sealed class BlockZoneEffect : CardEffectModule { private readonly float _duration; private readonly float _radius; private readonly float _pulseInterval; private readonly int _marksApplied; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "hazard", "disruption" }; internal override int MechanicScore => 4; internal BlockZoneEffect(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _duration = duration; _radius = radius; _pulseInterval = pulseInterval; _marksApplied = marksApplied; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddBlockZone(_duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveBlockZone(_duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal sealed class KillZoneEffect : CardEffectModule { private readonly float _duration; private readonly float _radius; private readonly float _pulseInterval; private readonly int _marksApplied; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "hazard", "disruption" }; internal override int MechanicScore => 4; internal KillZoneEffect(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _duration = duration; _radius = radius; _pulseInterval = pulseInterval; _marksApplied = marksApplied; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddKillZone(_duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveKillZone(_duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal sealed class ReloadModeEffect : CardEffectModule { private readonly int _shotsPerReload; private readonly float _damageMultiplier; private readonly float _speedMultiplier; private readonly float _projectileSizeMultiplier; private readonly bool _ignoreWalls; private readonly int _extraProjectiles; private readonly float _spreadPenalty; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "weapon_mode", "precision" }; internal override int MechanicScore => 4; internal ReloadModeEffect(int shotsPerReload, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool ignoreWalls, int extraProjectiles, float spreadPenalty) { _shotsPerReload = shotsPerReload; _damageMultiplier = damageMultiplier; _speedMultiplier = speedMultiplier; _projectileSizeMultiplier = projectileSizeMultiplier; _ignoreWalls = ignoreWalls; _extraProjectiles = extraProjectiles; _spreadPenalty = spreadPenalty; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddReloadMode(_shotsPerReload, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _ignoreWalls, _extraProjectiles, _spreadPenalty); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveReloadMode(_shotsPerReload, _damageMultiplier, _speedMultiplier, _projectileSizeMultiplier, _ignoreWalls, _extraProjectiles, _spreadPenalty); } } internal sealed class OrbitWardEffect : CardEffectModule { private readonly int _count; private readonly float _range; private readonly float _pulseInterval; private readonly int _marksApplied; private readonly float _disruptionDuration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[2] { "summon", "disruption" }; internal override int MechanicScore => 4; internal OrbitWardEffect(int count, float range, float pulseInterval, int marksApplied, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _count = count; _range = range; _pulseInterval = pulseInterval; _marksApplied = marksApplied; _disruptionDuration = disruptionDuration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddOrbitWard(_count, _range, _pulseInterval, _marksApplied, _disruptionDuration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveOrbitWard(_count, _range, _pulseInterval, _marksApplied, _disruptionDuration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal sealed class TrailZoneEffect : CardEffectModule { private readonly float _spawnInterval; private readonly float _duration; private readonly float _radius; private readonly float _pulseInterval; private readonly int _marksApplied; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[3] { "hazard", "position", "disruption" }; internal override int MechanicScore => 5; internal TrailZoneEffect(float spawnInterval, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _spawnInterval = spawnInterval; _duration = duration; _radius = radius; _pulseInterval = pulseInterval; _marksApplied = marksApplied; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddTrailZone(_spawnInterval, _duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveTrailZone(_spawnInterval, _duration, _radius, _pulseInterval, _marksApplied, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal sealed class BeaconDropEffect : CardEffectModule { private readonly float _spawnInterval; private readonly float _duration; private readonly float _range; private readonly float _pulseInterval; private readonly int _marksApplied; private readonly float _exposureDuration; private readonly float _exposureDamageMultiplier; private readonly float _disruptionDuration; private readonly float _movementMultiplier; private readonly float _reloadMultiplier; private readonly float _blockCooldownMultiplier; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[4] { "summon", "hazard", "disruption", "position" }; internal override int MechanicScore => 5; internal BeaconDropEffect(float spawnInterval, float duration, float range, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _spawnInterval = spawnInterval; _duration = duration; _range = range; _pulseInterval = pulseInterval; _marksApplied = marksApplied; _exposureDuration = exposureDuration; _exposureDamageMultiplier = exposureDamageMultiplier; _disruptionDuration = disruptionDuration; _movementMultiplier = movementMultiplier; _reloadMultiplier = reloadMultiplier; _blockCooldownMultiplier = blockCooldownMultiplier; } internal override void Apply(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.AddBeaconDrop(_spawnInterval, _duration, _range, _pulseInterval, _marksApplied, _exposureDuration, _exposureDamageMultiplier, _disruptionDuration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } internal override void Remove(CardEffectContext context) { PlayerAbilityController.GetOrAdd(context.Player)?.RemoveBeaconDrop(_spawnInterval, _duration, _range, _pulseInterval, _marksApplied, _exposureDuration, _exposureDamageMultiplier, _disruptionDuration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } } internal abstract class BalancedCard : CustomCard { protected abstract CardSpec Spec { get; } internal CardSpec Definition => Spec; internal virtual bool HasUniqueMechanic => false; internal virtual int MechanicScore => 0; internal virtual IReadOnlyList MechanicBuckets => Definition?.MechanicBuckets; protected virtual void ConfigureCard(CardInfo cardInfo, Gun gun, ApplyCardStats cardStats, CharacterStatModifiers statModifiers, Block block) { } protected abstract void ApplyBuild(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats); protected virtual void RemoveBuild(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { } public sealed override void SetupCard(CardInfo cardInfo, Gun gun, ApplyCardStats cardStats, CharacterStatModifiers statModifiers, Block block) { cardInfo.allowMultiple = Spec.AllowMultiple; ConfigureCard(cardInfo, gun, cardStats, statModifiers, block); } public sealed override void OnAddCard(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { ApplyBuild(player, gun, gunAmmo, data, health, gravity, block, characterStats); PlayerUpgradeVisualController.GetOrAdd(player)?.NotifyCardAdded(Spec); } public sealed override void OnRemoveCard(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { RemoveBuild(player, gun, gunAmmo, data, health, gravity, block, characterStats); PlayerUpgradeVisualController.GetOrAdd(player)?.NotifyCardRemoved(); } public override void OnRemoveCard() { } protected sealed override string GetTitle() { return Spec.DisplayTitle; } protected sealed override string GetDescription() { return Spec.TaggedDescription; } protected sealed override GameObject GetCardArt() { try { return CardArtFactory.Create(Spec); } catch { return null; } } protected sealed override Rarity GetRarity() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Spec.Rarity; } protected sealed override CardInfoStat[] GetStats() { return Spec.Stats; } protected sealed override CardThemeColorType GetTheme() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Spec.Theme; } public sealed override string GetModName() { return Spec.ToggleCategory; } } internal enum CardClass { Neutral, Marksman, Bulwark, Skirmisher, Trickster, Demolitionist, Brawler, Arcanist, Gambler, Wildcard, Troll } internal sealed class CardEffectContext { internal Player Player { get; } internal Gun Gun { get; } internal GunAmmo GunAmmo { get; } internal CharacterData Data { get; } internal HealthHandler Health { get; } internal Gravity Gravity { get; } internal Block Block { get; } internal CharacterStatModifiers CharacterStats { get; } internal CardEffectContext(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { Player = player; Gun = gun; GunAmmo = gunAmmo; Data = data; Health = health; Gravity = gravity; Block = block; CharacterStats = characterStats; } } internal abstract class CardEffectModule { internal virtual bool IsUniqueMechanic => false; internal virtual int MechanicScore { get { if (!IsUniqueMechanic) { return 0; } return 2; } } internal virtual string[] MechanicBuckets => Array.Empty(); internal abstract void Apply(CardEffectContext context); internal virtual void Remove(CardEffectContext context) { } } internal enum GunFloatStatKey { Damage, ProjectileSpeed, Spread, Knockback, Gravity, DestroyBulletAfter, ProjectileSize, BounceSpeedMultiplier, DamageAfterDistanceMultiplier, BounceDamageMultiplier, ProximityExplosionDamage, ProximityExplosionRange } internal enum GunIntStatKey { Reflects, NumberOfProjectiles, RandomBounces, SmartBounces } internal enum GunAmmoFloatStatKey { ReloadTimeMultiplier } internal enum GunAmmoIntStatKey { MaxAmmo } internal enum GunBoolStatKey { Unblockable, IgnoreWalls } internal enum CharacterDataFloatStatKey { MaxHealth } internal enum CharacterModifierFloatStatKey { MovementSpeed, Jump } internal enum BlockFloatStatKey { CooldownMultiplier } internal enum BlockIntStatKey { AdditionalBlocks } internal enum GravityFloatStatKey { GravityForce } internal static class CardEffects { internal static CardEffectModule MultiplyGunFloat(GunFloatStatKey key, float multiplier) { return new MultiplyGunFloatEffect(key, multiplier); } internal static CardEffectModule AddGunFloat(GunFloatStatKey key, float amount) { return new AddGunFloatEffect(key, amount); } internal static CardEffectModule SetGunFloat(GunFloatStatKey key, float value) { return new SetGunFloatEffect(key, value); } internal static CardEffectModule EnsureMinimumGunFloat(GunFloatStatKey key, float minimum) { return new EnsureMinimumGunFloatEffect(key, minimum); } internal static CardEffectModule AddGunInt(GunIntStatKey key, int amount) { return new AddGunIntEffect(key, amount); } internal static CardEffectModule ReverseKnockback(float magnitude) { return new ReverseKnockbackEffect(magnitude); } internal static CardEffectModule MultiplyGunAmmoFloat(GunAmmoFloatStatKey key, float multiplier) { return new MultiplyGunAmmoFloatEffect(key, multiplier); } internal static CardEffectModule AdjustGunAmmoInt(GunAmmoIntStatKey key, int amount, int minimum) { return new AdjustGunAmmoIntEffect(key, amount, minimum); } internal static CardEffectModule SetGunAmmoInt(GunAmmoIntStatKey key, int value) { return new SetGunAmmoIntEffect(key, value); } internal static CardEffectModule SetGunBool(GunBoolStatKey key, bool value) { return new SetGunBoolEffect(key, value); } internal static CardEffectModule MultiplyCharacterDataFloat(CharacterDataFloatStatKey key, float multiplier) { return new MultiplyCharacterDataFloatEffect(key, multiplier); } internal static CardEffectModule MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey key, float multiplier) { return new MultiplyCharacterModifierFloatEffect(key, multiplier); } internal static CardEffectModule MultiplyBlockFloat(BlockFloatStatKey key, float multiplier) { return new MultiplyBlockFloatEffect(key, multiplier); } internal static CardEffectModule AddBlockInt(BlockIntStatKey key, int amount) { return new AddBlockIntEffect(key, amount); } internal static CardEffectModule MultiplyGravityFloat(GravityFloatStatKey key, float multiplier) { return new MultiplyGravityFloatEffect(key, multiplier); } } internal sealed class MultiplyGunFloatEffect : CardEffectModule { private readonly GunFloatStatKey _key; private readonly float _multiplier; internal override bool IsUniqueMechanic { get { if (_key != GunFloatStatKey.DamageAfterDistanceMultiplier && _key != GunFloatStatKey.ProximityExplosionDamage) { return _key == GunFloatStatKey.ProximityExplosionRange; } return true; } } internal override string[] MechanicBuckets { get { switch (_key) { case GunFloatStatKey.DamageAfterDistanceMultiplier: return new string[1] { "precision" }; case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return new string[1] { "hazard" }; default: return Array.Empty(); } } } internal override int MechanicScore { get { switch (_key) { case GunFloatStatKey.ProjectileSize: case GunFloatStatKey.BounceSpeedMultiplier: case GunFloatStatKey.BounceDamageMultiplier: return 1; case GunFloatStatKey.DamageAfterDistanceMultiplier: case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return 2; default: return 0; } } } internal MultiplyGunFloatEffect(GunFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { switch (_key) { case GunFloatStatKey.Damage: { Gun gun6 = context.Gun; gun6.damage *= _multiplier; break; } case GunFloatStatKey.ProjectileSpeed: { Gun gun5 = context.Gun; gun5.projectileSpeed *= _multiplier; break; } case GunFloatStatKey.Spread: { Gun gun4 = context.Gun; gun4.spread *= _multiplier; break; } case GunFloatStatKey.Knockback: { Gun gun3 = context.Gun; gun3.knockback *= _multiplier; break; } case GunFloatStatKey.Gravity: { Gun gun2 = context.Gun; gun2.gravity *= _multiplier; break; } case GunFloatStatKey.DestroyBulletAfter: { Gun gun = context.Gun; gun.destroyBulletAfter *= _multiplier; break; } case GunFloatStatKey.ProjectileSize: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "projectileSize", _multiplier); break; case GunFloatStatKey.BounceSpeedMultiplier: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "speedMOnBounce", _multiplier); break; case GunFloatStatKey.DamageAfterDistanceMultiplier: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "damageAfterDistanceMultiplier", _multiplier); break; case GunFloatStatKey.BounceDamageMultiplier: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "dmgMOnBounce", _multiplier); break; case GunFloatStatKey.ProximityExplosionDamage: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "explodeNearEnemyDamage", _multiplier); break; case GunFloatStatKey.ProximityExplosionRange: HiddenGunFieldUtility.MultiplyFloat(context.Gun, "explodeNearEnemyRange", _multiplier); break; } } } internal sealed class AddGunFloatEffect : CardEffectModule { private readonly GunFloatStatKey _key; private readonly float _amount; internal override bool IsUniqueMechanic { get { if (_key != GunFloatStatKey.DamageAfterDistanceMultiplier && _key != GunFloatStatKey.ProximityExplosionDamage) { return _key == GunFloatStatKey.ProximityExplosionRange; } return true; } } internal override string[] MechanicBuckets { get { switch (_key) { case GunFloatStatKey.DamageAfterDistanceMultiplier: return new string[1] { "precision" }; case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return new string[1] { "hazard" }; default: return Array.Empty(); } } } internal override int MechanicScore { get { switch (_key) { case GunFloatStatKey.ProjectileSize: case GunFloatStatKey.BounceSpeedMultiplier: case GunFloatStatKey.BounceDamageMultiplier: return 1; case GunFloatStatKey.DamageAfterDistanceMultiplier: case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return 2; default: return 0; } } } internal AddGunFloatEffect(GunFloatStatKey key, float amount) { _key = key; _amount = amount; } internal override void Apply(CardEffectContext context) { switch (_key) { case GunFloatStatKey.Damage: { Gun gun6 = context.Gun; gun6.damage += _amount; break; } case GunFloatStatKey.ProjectileSpeed: { Gun gun5 = context.Gun; gun5.projectileSpeed += _amount; break; } case GunFloatStatKey.Spread: { Gun gun4 = context.Gun; gun4.spread += _amount; break; } case GunFloatStatKey.Knockback: { Gun gun3 = context.Gun; gun3.knockback += _amount; break; } case GunFloatStatKey.Gravity: { Gun gun2 = context.Gun; gun2.gravity += _amount; break; } case GunFloatStatKey.DestroyBulletAfter: { Gun gun = context.Gun; gun.destroyBulletAfter += _amount; break; } case GunFloatStatKey.ProjectileSize: HiddenGunFieldUtility.AddFloat(context.Gun, "projectileSize", _amount); break; case GunFloatStatKey.BounceSpeedMultiplier: HiddenGunFieldUtility.AddFloat(context.Gun, "speedMOnBounce", _amount); break; case GunFloatStatKey.DamageAfterDistanceMultiplier: HiddenGunFieldUtility.AddFloat(context.Gun, "damageAfterDistanceMultiplier", _amount); break; case GunFloatStatKey.BounceDamageMultiplier: HiddenGunFieldUtility.AddFloat(context.Gun, "dmgMOnBounce", _amount); break; case GunFloatStatKey.ProximityExplosionDamage: HiddenGunFieldUtility.AddFloat(context.Gun, "explodeNearEnemyDamage", _amount); break; case GunFloatStatKey.ProximityExplosionRange: HiddenGunFieldUtility.AddFloat(context.Gun, "explodeNearEnemyRange", _amount); break; } } } internal sealed class SetGunFloatEffect : CardEffectModule { private readonly GunFloatStatKey _key; private readonly float _value; internal override bool IsUniqueMechanic { get { if (_key != GunFloatStatKey.DamageAfterDistanceMultiplier && _key != GunFloatStatKey.ProximityExplosionDamage) { return _key == GunFloatStatKey.ProximityExplosionRange; } return true; } } internal override string[] MechanicBuckets { get { switch (_key) { case GunFloatStatKey.DamageAfterDistanceMultiplier: return new string[1] { "precision" }; case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return new string[1] { "hazard" }; default: return Array.Empty(); } } } internal override int MechanicScore { get { switch (_key) { case GunFloatStatKey.ProjectileSize: case GunFloatStatKey.BounceSpeedMultiplier: case GunFloatStatKey.BounceDamageMultiplier: return 1; case GunFloatStatKey.DamageAfterDistanceMultiplier: case GunFloatStatKey.ProximityExplosionDamage: case GunFloatStatKey.ProximityExplosionRange: return 2; default: return 0; } } } internal SetGunFloatEffect(GunFloatStatKey key, float value) { _key = key; _value = value; } internal override void Apply(CardEffectContext context) { switch (_key) { case GunFloatStatKey.Damage: context.Gun.damage = _value; break; case GunFloatStatKey.ProjectileSpeed: context.Gun.projectileSpeed = _value; break; case GunFloatStatKey.Spread: context.Gun.spread = _value; break; case GunFloatStatKey.Knockback: context.Gun.knockback = _value; break; case GunFloatStatKey.Gravity: context.Gun.gravity = _value; break; case GunFloatStatKey.DestroyBulletAfter: context.Gun.destroyBulletAfter = _value; break; case GunFloatStatKey.ProjectileSize: HiddenGunFieldUtility.SetFloat(context.Gun, "projectileSize", _value); break; case GunFloatStatKey.BounceSpeedMultiplier: HiddenGunFieldUtility.SetFloat(context.Gun, "speedMOnBounce", _value); break; case GunFloatStatKey.DamageAfterDistanceMultiplier: HiddenGunFieldUtility.SetFloat(context.Gun, "damageAfterDistanceMultiplier", _value); break; case GunFloatStatKey.BounceDamageMultiplier: HiddenGunFieldUtility.SetFloat(context.Gun, "dmgMOnBounce", _value); break; case GunFloatStatKey.ProximityExplosionDamage: HiddenGunFieldUtility.SetFloat(context.Gun, "explodeNearEnemyDamage", _value); break; case GunFloatStatKey.ProximityExplosionRange: HiddenGunFieldUtility.SetFloat(context.Gun, "explodeNearEnemyRange", _value); break; } } } internal sealed class EnsureMinimumGunFloatEffect : CardEffectModule { private readonly GunFloatStatKey _key; private readonly float _minimum; internal EnsureMinimumGunFloatEffect(GunFloatStatKey key, float minimum) { _key = key; _minimum = minimum; } internal override void Apply(CardEffectContext context) { if (_key == GunFloatStatKey.DestroyBulletAfter && (context.Gun.destroyBulletAfter == 0f || context.Gun.destroyBulletAfter < _minimum)) { context.Gun.destroyBulletAfter = _minimum; } } } internal sealed class AddGunIntEffect : CardEffectModule { private readonly GunIntStatKey _key; private readonly int _amount; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "precision" }; internal AddGunIntEffect(GunIntStatKey key, int amount) { _key = key; _amount = amount; } internal override void Apply(CardEffectContext context) { switch (_key) { case GunIntStatKey.Reflects: { Gun gun2 = context.Gun; gun2.reflects += _amount; break; } case GunIntStatKey.NumberOfProjectiles: { Gun gun = context.Gun; gun.numberOfProjectiles += _amount; break; } case GunIntStatKey.RandomBounces: HiddenGunFieldUtility.AddInt(context.Gun, "randomBounces", _amount); break; case GunIntStatKey.SmartBounces: HiddenGunFieldUtility.AddInt(context.Gun, "smartBounce", _amount); break; } } } internal sealed class ReverseKnockbackEffect : CardEffectModule { private readonly float _magnitude; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "disruption" }; internal ReverseKnockbackEffect(float magnitude) { _magnitude = magnitude; } internal override void Apply(CardEffectContext context) { context.Gun.knockback = (0f - Math.Abs(context.Gun.knockback)) * _magnitude; } } internal sealed class MultiplyGunAmmoFloatEffect : CardEffectModule { private readonly GunAmmoFloatStatKey _key; private readonly float _multiplier; internal MultiplyGunAmmoFloatEffect(GunAmmoFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { if (_key == GunAmmoFloatStatKey.ReloadTimeMultiplier) { GunAmmo gunAmmo = context.GunAmmo; gunAmmo.reloadTimeMultiplier *= _multiplier; } } } internal sealed class AdjustGunAmmoIntEffect : CardEffectModule { private readonly GunAmmoIntStatKey _key; private readonly int _amount; private readonly int _minimum; internal AdjustGunAmmoIntEffect(GunAmmoIntStatKey key, int amount, int minimum) { _key = key; _amount = amount; _minimum = minimum; } internal override void Apply(CardEffectContext context) { if (_key == GunAmmoIntStatKey.MaxAmmo) { context.GunAmmo.maxAmmo = Math.Max(_minimum, context.GunAmmo.maxAmmo + _amount); } } } internal sealed class SetGunAmmoIntEffect : CardEffectModule { private readonly GunAmmoIntStatKey _key; private readonly int _value; internal SetGunAmmoIntEffect(GunAmmoIntStatKey key, int value) { _key = key; _value = value; } internal override void Apply(CardEffectContext context) { if (_key == GunAmmoIntStatKey.MaxAmmo) { context.GunAmmo.maxAmmo = _value; } } } internal sealed class SetGunBoolEffect : CardEffectModule { private readonly GunBoolStatKey _key; private readonly bool _value; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets { get { if (_key == GunBoolStatKey.IgnoreWalls) { return new string[2] { "weapon_mode", "precision" }; } return new string[1] { "precision" }; } } internal SetGunBoolEffect(GunBoolStatKey key, bool value) { _key = key; _value = value; } internal override void Apply(CardEffectContext context) { switch (_key) { case GunBoolStatKey.Unblockable: HiddenGunFieldUtility.SetBool(context.Gun, "unblockable", _value); break; case GunBoolStatKey.IgnoreWalls: HiddenGunFieldUtility.SetBool(context.Gun, "ignoreWalls", _value); break; } } } internal sealed class MultiplyCharacterDataFloatEffect : CardEffectModule { private readonly CharacterDataFloatStatKey _key; private readonly float _multiplier; internal MultiplyCharacterDataFloatEffect(CharacterDataFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { if (_key == CharacterDataFloatStatKey.MaxHealth) { CharacterData data = context.Data; data.maxHealth *= _multiplier; } } } internal sealed class MultiplyCharacterModifierFloatEffect : CardEffectModule { private readonly CharacterModifierFloatStatKey _key; private readonly float _multiplier; internal MultiplyCharacterModifierFloatEffect(CharacterModifierFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { switch (_key) { case CharacterModifierFloatStatKey.MovementSpeed: { CharacterStatModifiers characterStats2 = context.CharacterStats; characterStats2.movementSpeed *= _multiplier; break; } case CharacterModifierFloatStatKey.Jump: { CharacterStatModifiers characterStats = context.CharacterStats; characterStats.jump *= _multiplier; break; } } } } internal sealed class MultiplyBlockFloatEffect : CardEffectModule { private readonly BlockFloatStatKey _key; private readonly float _multiplier; internal MultiplyBlockFloatEffect(BlockFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { if (_key == BlockFloatStatKey.CooldownMultiplier) { Block block = context.Block; block.cdMultiplier *= _multiplier; } } } internal sealed class AddBlockIntEffect : CardEffectModule { private readonly BlockIntStatKey _key; private readonly int _amount; internal AddBlockIntEffect(BlockIntStatKey key, int amount) { _key = key; _amount = amount; } internal override void Apply(CardEffectContext context) { if (_key == BlockIntStatKey.AdditionalBlocks) { Block block = context.Block; block.additionalBlocks += _amount; } } } internal sealed class MultiplyGravityFloatEffect : CardEffectModule { private readonly GravityFloatStatKey _key; private readonly float _multiplier; internal MultiplyGravityFloatEffect(GravityFloatStatKey key, float multiplier) { _key = key; _multiplier = multiplier; } internal override void Apply(CardEffectContext context) { if (_key == GravityFloatStatKey.GravityForce) { Gravity gravity = context.Gravity; gravity.gravityForce *= _multiplier; } } } internal static class HiddenGunFieldUtility { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; internal static void MultiplyFloat(Gun gun, string memberName, float multiplier) { if (TryGetFloat(gun, memberName, out var value)) { SetFloat(gun, memberName, value * multiplier); } } internal static void AddFloat(Gun gun, string memberName, float amount) { if (TryGetFloat(gun, memberName, out var value)) { SetFloat(gun, memberName, value + amount); } } internal static void SetFloat(Gun gun, string memberName, float value) { SetMemberValue(gun, memberName, value); } internal static void SetBool(Gun gun, string memberName, bool value) { SetMemberValue(gun, memberName, value); } internal static void AddInt(Gun gun, string memberName, int amount) { if (TryGetInt(gun, memberName, out var value)) { SetInt(gun, memberName, value + amount); } } internal static void SetInt(Gun gun, string memberName, int value) { SetMemberValue(gun, memberName, value); } private static bool TryGetFloat(Gun gun, string memberName, out float value) { value = 0f; if (gun == null) { return false; } object memberValue = GetMemberValue(gun, memberName); if (memberValue == null) { return false; } try { value = Convert.ToSingle(memberValue); return true; } catch { return false; } } private static bool TryGetInt(Gun gun, string memberName, out int value) { value = 0; if (gun == null) { return false; } object memberValue = GetMemberValue(gun, memberName); if (memberValue == null) { return false; } try { value = Convert.ToInt32(memberValue); return true; } catch { return false; } } private static object GetMemberValue(object target, string memberName) { Type type = target?.GetType(); if (type == null) { return null; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(target, null); } return null; } private static void SetMemberValue(object target, string memberName, T value) { Type type = target?.GetType(); if (type == null) { return; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(target, value); return; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); } } } internal sealed class CardSpec { private string[] _draftTags; private string[] _mechanicBuckets; internal string Title { get; } internal string Description { get; } internal Rarity Rarity { get; } internal CardThemeColorType Theme { get; } internal CardInfoStat[] Stats { get; } internal CardClass CardClass { get; } internal CardTrack Track { get; } internal int Tier { get; } internal string Family { get; } internal string Role { get; } internal bool AllowMultiple { get; } internal int MinimumClassPips { get; } internal bool AllowOffClassWithoutPips { get; } internal bool IsWildcard { get { if (CardClass != CardClass.Wildcard) { return Track == CardTrack.Wildcard; } return true; } } internal bool IsTroll { get { if (CardClass != CardClass.Troll) { return Track == CardTrack.Troll; } return true; } } internal bool IsManagedClass { get { if (!IsWildcard && !IsTroll) { return CardClass != CardClass.Neutral; } return false; } } internal string ToggleCategory => BuildToggleCategory(); internal string ClassLabel => GetClassLabel(); internal string ShortLabel => GetShortLabel(); internal string DisplayTitle => BuildDisplayTitle(); internal string TaggedDescription => BuildTaggedDescription(); internal IReadOnlyList AuthoredDraftTags => ExplicitDraftTags; internal IReadOnlyList DraftTags => _draftTags ?? (_draftTags = BuildDraftTags()); internal IReadOnlyList MechanicBuckets => _mechanicBuckets ?? (_mechanicBuckets = BuildMechanicBuckets()); private string[] ExplicitDraftTags { get; } internal CardSpec(string title, string description, Rarity rarity, CardThemeColorType theme, CardInfoStat[] stats, CardClass cardClass, CardTrack track, int tier, string family, string role, bool allowMultiple = false, int? minimumClassPips = null, bool? allowOffClassWithoutPips = null, params string[] draftTags) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Title = title; Description = description; Rarity = rarity; Theme = theme; Stats = stats; CardClass = cardClass; Track = track; Tier = tier; Family = family; Role = role; AllowMultiple = allowMultiple; MinimumClassPips = minimumClassPips ?? GetDefaultMinimumClassPips(track); AllowOffClassWithoutPips = allowOffClassWithoutPips ?? GetDefaultOffClassSplash(track); ExplicitDraftTags = NormalizeDraftTags(draftTags); } private static int GetDefaultMinimumClassPips(CardTrack track) { return track switch { CardTrack.Payoff => 1, CardTrack.Signature => 2, CardTrack.Capstone => 3, _ => 0, }; } private static bool GetDefaultOffClassSplash(CardTrack track) { if ((uint)track <= 2u || (uint)(track - 6) <= 1u) { return true; } return false; } private string BuildToggleCategory() { if (IsWildcard) { return "BR - Wildcard"; } if (IsTroll) { return "BR - Troll"; } if (CardClass == CardClass.Neutral) { return "BR - Neutral"; } return "BR - " + GetClassLabel(); } private string GetClassLabel() { if (IsWildcard) { return "Wildcard"; } if (IsTroll) { return "Troll"; } return CardClass.ToString(); } private string GetShortLabel() { if (IsWildcard) { return "WC"; } if (IsTroll) { return "TR"; } return CardClass switch { CardClass.Marksman => "MK", CardClass.Bulwark => "BW", CardClass.Skirmisher => "SK", CardClass.Trickster => "TK", CardClass.Demolitionist => "DM", CardClass.Brawler => "BR", CardClass.Arcanist => "AR", CardClass.Gambler => "GB", _ => "NT", }; } private string BuildDisplayTitle() { if (IsWildcard || IsTroll || CardClass == CardClass.Neutral) { return "[" + ShortLabel + "] " + Title; } return Title; } private string BuildTaggedDescription() { string text = $"{ClassLabel} / {Track} / T{Tier}"; string text2 = BuildRulesSummary(); if (string.IsNullOrWhiteSpace(text2)) { return text + "\n" + Description; } if (string.IsNullOrWhiteSpace(Description)) { return text + "\n" + text2; } return text + "\n" + text2 + "\n" + Description; } private string BuildRulesSummary() { if (Stats == null || Stats.Length == 0) { return null; } string[] array = (from @group in Stats.Where((CardInfoStat stat) => stat != null && !string.IsNullOrWhiteSpace(stat.stat) && !string.IsNullOrWhiteSpace(stat.amount)).GroupBy((CardInfoStat stat) => stat.stat.Trim(), StringComparer.OrdinalIgnoreCase) select FormatRulesGroup(@group.First().stat.Trim(), @group.Select((CardInfoStat stat) => stat.amount)) into @group where !string.IsNullOrWhiteSpace(@group) select @group).ToArray(); if (array.Length == 0) { return null; } string text = string.Join("; ", array); if (!text.EndsWith(".") && !text.EndsWith("!") && !text.EndsWith("?")) { text += "."; } return "Rules: " + text; } private static string FormatRulesGroup(string statName, IEnumerable amounts) { string[] array = (from amount in amounts where !string.IsNullOrWhiteSpace(amount) select amount.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); if (array.Length == 0) { return null; } if (array.Length == 1) { string text = array[0]; if (!StartsWithValueToken(text)) { return statName + ": " + text; } return statName + " " + text; } return statName + ": " + string.Join(", ", array); } private static bool StartsWithValueToken(string text) { if (string.IsNullOrEmpty(text)) { return false; } char c = text[0]; if (c != '+' && c != '-') { return char.IsDigit(c); } return true; } private string[] BuildDraftTags() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); AddTrackTags(hashSet); AddClassTags(hashSet); AddKeywordTags(hashSet, BuildSearchText()); string[] explicitDraftTags = ExplicitDraftTags; foreach (string text in explicitDraftTags) { if (!string.IsNullOrWhiteSpace(text)) { hashSet.Add(text.Trim().ToLowerInvariant()); } } return hashSet.OrderBy((string tag) => tag, StringComparer.Ordinal).ToArray(); } private string[] BuildMechanicBuckets() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); IReadOnlyList explicitDraftTags = ExplicitDraftTags; AddBucketIfAny(hashSet, explicitDraftTags, "hazard", "hazard", "explosion", "zone", "mine", "remnant", "spawn"); AddBucketIfAny(hashSet, explicitDraftTags, "summon", "summon", "orbit", "ward", "helper", "minion", "familiar", "beacon", "relay"); AddBucketIfAny(hashSet, explicitDraftTags, "weapon_mode", "weapon mode", "chamber", "heat", "overheat", "cadence", "last shell"); AddBucketIfAny(hashSet, explicitDraftTags, "disruption", "disruption", "jam", "cripple", "weaken", "expose", "exposure"); AddBucketIfAny(hashSet, explicitDraftTags, "mark", "mark"); AddBucketIfAny(hashSet, explicitDraftTags, "position", "control", "pull", "push"); AddBucketIfAny(hashSet, explicitDraftTags, "block", "block", "parry", "shield"); AddBucketIfAny(hashSet, explicitDraftTags, "stance", "air", "wall", "ground", "stance"); AddBucketIfAny(hashSet, explicitDraftTags, "precision", "precision", "range", "patient"); AddBucketIfAny(hashSet, explicitDraftTags, "economy", "draft", "economy"); return hashSet.OrderBy((string bucket) => bucket, StringComparer.Ordinal).ToArray(); } private void AddTrackTags(ISet tags) { switch (Track) { case CardTrack.Payoff: tags.Add("payoff"); break; case CardTrack.Signature: tags.Add("signature"); break; case CardTrack.Capstone: tags.Add("capstone"); break; } if (Tier >= 4) { tags.Add("upper-tier"); } } private void AddClassTags(ISet tags) { if (IsWildcard) { tags.Add("wildcard"); } else if (IsTroll) { tags.Add("troll"); } else if (CardClass == CardClass.Neutral) { tags.Add("neutral"); } else { tags.Add(CardClass.ToString().ToLowerInvariant()); } } private string BuildSearchText() { return string.Join(" ", new string[6] { Title, Description, Family, Role, CardClass.ToString(), Track.ToString() }.Where((string value) => !string.IsNullOrWhiteSpace(value))).ToLowerInvariant(); } private static void AddKeywordTags(ISet tags, string text) { AddTagIfMatch(tags, text, "projectile", "projectile", "bullet", "buckshot", "pellet", "scatter", "volley", "crossfire", "splitfire", "shotgun", "shrapnel", "dust", "size"); AddTagIfMatch(tags, text, "bounce", "ricochet", "reflect", "rebound", "pinball", "bank", "corner", "wall", "backboard", "mirror"); AddTagIfMatch(tags, text, "block", "block", "guard", "shield", "aegis", "rampart", "bulwark", "fort", "stronghold", "parry"); AddTagIfMatch(tags, text, "air", "air", "sky", "moon", "gravity", "jump", "orbit", "softstep", "lift", "airborne"); AddTagIfMatch(tags, text, "pull", "pull", "tractor", "drag", "hook", "harpoon", "snare", "tether", "reel", "latch"); AddTagIfMatch(tags, text, "push", "push", "ram", "piledriver", "hammer", "slam", "shove", "impact", "knockback"); AddTagIfMatch(tags, text, "reload", "reload", "ammo", "mag", "rack", "chamber", "cycle", "tempo", "reserve", "stockpile"); AddTagIfMatch(tags, text, "chamber", "chamber", "first bullet", "last bullet", "nth shot", "every", "loaded", "cylinder", "shell"); AddTagIfMatch(tags, text, "mark", "mark", "marked", "tagged", "prey", "claim", "receipt", "bounty", "brand"); AddTagIfMatch(tags, text, "kill", "kill", "killing", "execute", "execution", "finisher", "body count", "collection notice", "red ledger"); AddTagIfMatch(tags, text, "miss", "miss", "whiff", "stray", "cold read", "mercy window"); AddTagIfMatch(tags, text, "survival", "hp", "health", "stonehide", "bastion", "lifesteal", "heal", "blood", "pain", "anchor", "fortress", "last stand", "dying", "dead", "wake", "comeback"); AddTagIfMatch(tags, text, "precision", "marksman", "ballistics", "needle", "range", "sight", "deadeye", "patient", "breath", "sniper"); AddTagIfMatch(tags, text, "burst", "burst", "heatcheck", "glass", "fault", "gambler", "opener", "closer", "first shot", "last bullet", "chain", "charge"); AddTagIfMatch(tags, text, "control", "control", "drag", "pull", "push", "knockback", "wall", "space", "position"); AddTagIfMatch(tags, text, "draft", "draft", "pick", "rounds", "future round", "next round", "layaway", "debt", "dividend"); AddTagIfMatch(tags, text, "explosion", "explosion", "explode", "blast", "mine", "hazard", "proxy", "detonate", "radius", "bloom"); AddTagIfMatch(tags, text, "disruption", "disrupt", "disruption", "slow", "jam", "reload time", "block cooldown", "cripple", "weaken", "exposed", "exposure", "damage taken"); AddTagIfMatch(tags, text, "summon", "ward", "sprite", "orbit", "helper", "familiar", "summon", "beacon", "relay", "witness", "post"); AddTagIfMatch(tags, text, "zone", "field", "zone", "aura", "lease", "pool", "wake", "beacon"); AddTagIfMatch(tags, text, "weapon mode", "weapon mode", "mode", "full reload", "next 2 shots", "next 3 shots"); AddTagIfMatch(tags, text, "ground", "grounded", "standing your ground", "anchor", "floor"); } private static void AddTagIfMatch(ISet tags, string text, string tag, params string[] keywords) { if (keywords.Any((string keyword) => text.IndexOf(keyword, StringComparison.Ordinal) >= 0)) { tags.Add(tag); } } private static void AddBucketIfAny(ISet buckets, IEnumerable tags, string bucket, params string[] candidates) { if (tags.Any((string tag) => candidates.Any((string candidate) => string.Equals(tag, candidate, StringComparison.OrdinalIgnoreCase)))) { buckets.Add(bucket); } } private static string[] NormalizeDraftTags(string[] tags) { if (tags == null || tags.Length == 0) { return Array.Empty(); } return (from tag in tags where !string.IsNullOrWhiteSpace(tag) select tag.Trim().ToLowerInvariant()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } internal static class CardStats { internal static CardInfoStat Positive(string stat, string amount, SimpleAmount simpleAmount) { //IL_0000: 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) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new CardInfoStat { positive = true, stat = stat, amount = amount, simepleAmount = simpleAmount }; } internal static CardInfoStat Negative(string stat, string amount, SimpleAmount simpleAmount) { //IL_0000: 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) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new CardInfoStat { positive = false, stat = stat, amount = amount, simepleAmount = simpleAmount }; } } internal enum CardTrack { Prototype, Starter, Core, Payoff, Signature, Capstone, Wildcard, Troll } internal static class DraftCardEffects { internal static CardEffectModule FutureExtraPicks(int futureRoundDraws, int extraPicksPerDraw) { return new FutureExtraPicksEffect(futureRoundDraws, extraPicksPerDraw); } } internal sealed class FutureExtraPicksEffect : CardEffectModule { private readonly int _futureRoundDraws; private readonly int _extraPicksPerDraw; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "economy" }; internal FutureExtraPicksEffect(int futureRoundDraws, int extraPicksPerDraw) { _futureRoundDraws = futureRoundDraws; _extraPicksPerDraw = extraPicksPerDraw; } internal override void Apply(CardEffectContext context) { DraftEconomyController.GetOrAdd(context.Player)?.QueueFutureRoundExtraPicks(_futureRoundDraws, _extraPicksPerDraw); } internal override void Remove(CardEffectContext context) { DraftEconomyController.GetOrAdd(context.Player)?.RemoveQueuedFutureRoundExtraPicks(_futureRoundDraws, _extraPicksPerDraw); } } internal sealed class GeneratedTemplateCard : BalancedCard { private static readonly FieldInfo CardNameField = typeof(CardInfo).GetField("cardName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo CardNameProperty = typeof(CardInfo).GetProperty("cardName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly CardSpec FallbackSpec = new CardSpec("Template Card", "Uninitialized template card.", (Rarity)0, (CardThemeColorType)2, Array.Empty(), CardClass.Neutral, CardTrack.Prototype, 1, "Template", "Fallback", false, null, null); private TemplateCardDefinition TemplateDefinition => ResolveTemplateDefinition(); protected override CardSpec Spec => TemplateDefinition?.Spec ?? FallbackSpec; internal override bool HasUniqueMechanic => TemplateDefinition?.HasUniqueMechanic ?? false; internal override int MechanicScore => TemplateDefinition?.MechanicScore ?? 0; internal override IReadOnlyList MechanicBuckets { get { object obj = TemplateDefinition?.MechanicBuckets; if (obj == null) { CardSpec spec = Spec; if (spec == null) { return null; } obj = spec.MechanicBuckets; } return (IReadOnlyList)obj; } } protected override void ApplyBuild(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { TemplateDefinition?.Apply(new CardEffectContext(player, gun, gunAmmo, data, health, gravity, block, characterStats)); } protected override void RemoveBuild(Player player, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers characterStats) { TemplateDefinition?.Remove(new CardEffectContext(player, gun, gunAmmo, data, health, gravity, block, characterStats)); } private TemplateCardDefinition ResolveTemplateDefinition() { if (((Component)this).gameObject.GetComponent(typeof(TemplateCardState)) is TemplateCardState templateCardState && templateCardState.Definition != null) { return templateCardState.Definition; } string objectName = ((((Component)this).gameObject != null) ? ((Object)((Component)this).gameObject).name : null); string title = ResolveCardTitle(); if (CardMetadataRegistry.TryGetTemplate(objectName, title, out var definition)) { return definition; } return null; } private string ResolveCardTitle() { if (((CustomCard)this).cardInfo == null) { return null; } if (CardNameField != null) { return CardNameField.GetValue(((CustomCard)this).cardInfo) as string; } if (CardNameProperty != null) { return CardNameProperty.GetValue(((CustomCard)this).cardInfo, null) as string; } return null; } } internal sealed class TemplateCardState : MonoBehaviour { internal TemplateCardDefinition Definition { get; set; } } internal sealed class TemplateCardDefinition { internal CardSpec Spec { get; } internal CardEffectModule[] Effects { get; } internal bool HasUniqueMechanic { get { CardEffectModule[] effects = Effects; foreach (CardEffectModule cardEffectModule in effects) { if (cardEffectModule != null && cardEffectModule.IsUniqueMechanic) { return true; } } return false; } } internal int MechanicScore { get { int num = 0; CardEffectModule[] effects = Effects; foreach (CardEffectModule cardEffectModule in effects) { if (cardEffectModule != null) { num += cardEffectModule.MechanicScore; } } return num; } } internal IReadOnlyList MechanicBuckets { get { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); CardEffectModule[] effects = Effects; foreach (CardEffectModule cardEffectModule in effects) { if (cardEffectModule?.MechanicBuckets == null) { continue; } string[] mechanicBuckets = cardEffectModule.MechanicBuckets; foreach (string text in mechanicBuckets) { if (!string.IsNullOrWhiteSpace(text)) { hashSet.Add(text); } } } if (hashSet.Count == 0 && Spec?.MechanicBuckets != null) { foreach (string mechanicBucket in Spec.MechanicBuckets) { if (!string.IsNullOrWhiteSpace(mechanicBucket)) { hashSet.Add(mechanicBucket); } } } return hashSet.OrderBy((string bucket) => bucket, StringComparer.Ordinal).ToArray(); } } internal TemplateCardDefinition(CardSpec spec, params CardEffectModule[] effects) { Spec = spec ?? throw new ArgumentNullException("spec"); Effects = effects ?? Array.Empty(); } internal void Apply(CardEffectContext context) { CardEffectModule[] effects = Effects; for (int i = 0; i < effects.Length; i++) { effects[i]?.Apply(context); } } internal void Remove(CardEffectContext context) { for (int num = Effects.Length - 1; num >= 0; num--) { Effects[num]?.Remove(context); } } } internal static class VanillaCardEffects { internal static CardEffectModule AddProjectileSpawnFromCard(string sourceCardName, string requiredComponentTypeName) { return new AddProjectileSpawnFromCardEffect(sourceCardName, requiredComponentTypeName); } internal static CardEffectModule AddProjectileSpawnByComponent(string requiredComponentTypeName, params string[] mechanicBuckets) { return new AddProjectileSpawnByComponentEffect(requiredComponentTypeName, null, mechanicBuckets); } internal static CardEffectModule AddProjectileSpawnByComponent(string requiredComponentTypeName, IEnumerable preferredSourceCardNames, params string[] mechanicBuckets) { return new AddProjectileSpawnByComponentEffect(requiredComponentTypeName, preferredSourceCardNames, mechanicBuckets); } internal static CardEffectModule AddRoomBounce(params string[] mechanicBuckets) { return AddProjectileSpawnFromCard("TargetBounce", "ScreenEdgeBounce"); } internal static CardEffectModule AddRetargetBounce(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("BounceEffectRetarget", new string[2] { "TargetBounce", "Bounce" }, mechanicBuckets); } internal static CardEffectModule AddTeleportShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("TeleportToOpponent", new string[2] { "BlinkStep", "Teleport" }, mechanicBuckets); } internal static CardEffectModule AddReflectShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("RayHitReflect", new string[2] { "Reflect", "Mirror" }, mechanicBuckets); } internal static CardEffectModule AddReflectEventShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("ReflectEvent", new string[2] { "Reflect", "Mirror" }, mechanicBuckets); } internal static CardEffectModule AddDrillShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("RayHitDrill", new string[1] { "Drill" }, mechanicBuckets); } internal static CardEffectModule AddOrbitShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("Orbit", new string[1] { "Orbit" }, mechanicBuckets); } internal static CardEffectModule AddSurfaceShieldShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("ProjectileHitSurfaceShield", new string[3] { "Shield Charge", "ShieldCharge", "Shield" }, mechanicBuckets); } internal static CardEffectModule AddShieldChargeShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("ShieldCharge", new string[3] { "Shield Charge", "ShieldCharge", "Shield" }, mechanicBuckets); } internal static CardEffectModule AddMinionShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("SpawnMinion", new string[4] { "Minion", "Minions", "Drone", "Drones" }, mechanicBuckets); } internal static CardEffectModule AddStaticRemnantShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("SpawnStaticRemnant", new string[3] { "Phoenix", "Remnant", "Static Remnant" }, mechanicBuckets); } internal static CardEffectModule AddDamageSpawnShot(params string[] mechanicBuckets) { return AddProjectileSpawnByComponent("SpawnObjectOnDealDamage", new string[3] { "Spawn", "Summon", "Parasite" }, mechanicBuckets); } } internal sealed class AddProjectileSpawnFromCardEffect : CardEffectModule { private readonly string _sourceCardName; private readonly string _requiredComponentTypeName; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets => new string[1] { "precision" }; internal AddProjectileSpawnFromCardEffect(string sourceCardName, string requiredComponentTypeName) { _sourceCardName = sourceCardName; _requiredComponentTypeName = requiredComponentTypeName; } internal override void Apply(CardEffectContext context) { VanillaCardObjectUtility.TryAppendProjectileSpawnFromCard(context.Gun, _sourceCardName, _requiredComponentTypeName); } } internal sealed class AddProjectileSpawnByComponentEffect : CardEffectModule { private readonly string _requiredComponentTypeName; private readonly string[] _preferredSourceCardNames; private readonly string[] _mechanicBuckets; internal override bool IsUniqueMechanic => true; internal override string[] MechanicBuckets { get { if (_mechanicBuckets.Length != 0) { return _mechanicBuckets; } return new string[1] { "precision" }; } } internal AddProjectileSpawnByComponentEffect(string requiredComponentTypeName, IEnumerable preferredSourceCardNames, IEnumerable mechanicBuckets) { _requiredComponentTypeName = requiredComponentTypeName; _preferredSourceCardNames = preferredSourceCardNames?.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty(); _mechanicBuckets = mechanicBuckets?.Where((string bucket) => !string.IsNullOrWhiteSpace(bucket)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty(); } internal override void Apply(CardEffectContext context) { VanillaCardObjectUtility.TryAppendProjectileSpawnByComponent(context.Gun, _requiredComponentTypeName, _preferredSourceCardNames); } } internal static class VanillaCardObjectUtility { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly HashSet MissingComponentWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); internal static bool TryAppendProjectileSpawnFromCard(Gun targetGun, string sourceCardName, string requiredComponentTypeName) { if (targetGun == null || string.IsNullOrWhiteSpace(sourceCardName) || string.IsNullOrWhiteSpace(requiredComponentTypeName)) { return false; } CardInfo val = ResolveCard(sourceCardName); Array objectsToSpawn = GetObjectsToSpawn((val == null) ? null : ((Component)val).GetComponent()); if (objectsToSpawn == null || objectsToSpawn.Length == 0) { return false; } object obj = null; for (int i = 0; i < objectsToSpawn.Length; i++) { object value = objectsToSpawn.GetValue(i); if (HasProjectileComponent(value, requiredComponentTypeName)) { obj = value; break; } } if (obj == null) { return false; } Array objectsToSpawn2 = GetObjectsToSpawn(targetGun); if (ContainsProjectileComponent(objectsToSpawn2, requiredComponentTypeName)) { return true; } Type? elementType = objectsToSpawn.GetType().GetElementType(); int num = objectsToSpawn2?.Length ?? 0; Array array = Array.CreateInstance(elementType, num + 1); for (int j = 0; j < num; j++) { array.SetValue(objectsToSpawn2.GetValue(j), j); } array.SetValue(obj, num); SetMemberValue(targetGun, "objectsToSpawn", array); return true; } internal static bool TryAppendProjectileSpawnByComponent(Gun targetGun, string requiredComponentTypeName, IEnumerable preferredSourceCardNames) { if (targetGun == null || string.IsNullOrWhiteSpace(requiredComponentTypeName)) { return false; } Array objectsToSpawn = GetObjectsToSpawn(targetGun); if (ContainsProjectileComponent(objectsToSpawn, requiredComponentTypeName)) { return true; } if (!TryFindProjectileSpawnByComponent(requiredComponentTypeName, preferredSourceCardNames, out var sourceObjects, out var selected)) { LogMissingComponent(requiredComponentTypeName); return false; } Type? elementType = sourceObjects.GetType().GetElementType(); int num = objectsToSpawn?.Length ?? 0; Array array = Array.CreateInstance(elementType, num + 1); for (int i = 0; i < num; i++) { array.SetValue(objectsToSpawn.GetValue(i), i); } array.SetValue(selected, num); SetMemberValue(targetGun, "objectsToSpawn", array); return true; } private static CardInfo ResolveCard(string cardName) { CardInfo cardInfoWithName = CardManager.GetCardInfoWithName(cardName); if (cardInfoWithName != null) { return cardInfoWithName; } string text = Normalize(cardName); foreach (KeyValuePair card in CardManager.cards) { CardInfo val = card.Value?.cardInfo; if (val != null && Normalize(((Object)((Component)val).gameObject).name) == text) { return val; } } return null; } private static bool TryFindProjectileSpawnByComponent(string requiredComponentTypeName, IEnumerable preferredSourceCardNames, out Array sourceObjects, out object selected) { sourceObjects = null; selected = null; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (preferredSourceCardNames != null) { foreach (string preferredSourceCardName in preferredSourceCardNames) { CardInfo val = ResolveCard(preferredSourceCardName); if (val != null) { hashSet.Add(((Object)((Component)val).gameObject).name); if (TryFindProjectileSpawnInCard(val, requiredComponentTypeName, out sourceObjects, out selected)) { return true; } } } } foreach (KeyValuePair card in CardManager.cards) { CardInfo val2 = card.Value?.cardInfo; if (val2 != null && hashSet.Add(((Object)((Component)val2).gameObject).name) && TryFindProjectileSpawnInCard(val2, requiredComponentTypeName, out sourceObjects, out selected)) { return true; } } return false; } private static void LogMissingComponent(string requiredComponentTypeName) { if (!string.IsNullOrWhiteSpace(requiredComponentTypeName) && MissingComponentWarnings.Add(requiredComponentTypeName)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("BetterRounds could not resolve a projectile spawn with component '" + requiredComponentTypeName + "'. The related vanilla-style graft card will fall back to its stat package.")); } } } private static bool TryFindProjectileSpawnInCard(CardInfo sourceCard, string requiredComponentTypeName, out Array sourceObjects, out object selected) { sourceObjects = null; selected = null; Gun gun = ((sourceCard == null) ? null : ((Component)sourceCard).GetComponent()); sourceObjects = GetObjectsToSpawn(gun); if (sourceObjects == null || sourceObjects.Length == 0) { return false; } for (int i = 0; i < sourceObjects.Length; i++) { object value = sourceObjects.GetValue(i); if (HasProjectileComponent(value, requiredComponentTypeName)) { selected = value; return true; } } sourceObjects = null; return false; } private static Array GetObjectsToSpawn(Gun gun) { return GetMemberValue(gun, "objectsToSpawn") as Array; } private static bool ContainsProjectileComponent(Array objects, string requiredComponentTypeName) { if (objects == null) { return false; } for (int i = 0; i < objects.Length; i++) { if (HasProjectileComponent(objects.GetValue(i), requiredComponentTypeName)) { return true; } } return false; } private static bool HasProjectileComponent(object objectToSpawn, string requiredComponentTypeName) { object memberValue = GetMemberValue(objectToSpawn, "AddToProjectile"); GameObject val = (GameObject)((memberValue is GameObject) ? memberValue : null); if (val == null) { return false; } foreach (object componentsInChild in GetComponentsInChildren(val)) { if (componentsInChild != null) { Type type = componentsInChild.GetType(); string text = type.FullName ?? string.Empty; if (string.Equals(type.Name, requiredComponentTypeName, StringComparison.OrdinalIgnoreCase) || string.Equals(text, requiredComponentTypeName, StringComparison.OrdinalIgnoreCase) || text.EndsWith("." + requiredComponentTypeName, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } private static string Normalize(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } List list = new List(value.Length); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { list.Add(char.ToLowerInvariant(c)); } } return new string(list.ToArray()); } private static IEnumerable GetComponentsInChildren(GameObject gameObject) { if (gameObject == null) { return Array.Empty(); } MethodInfo method = ((object)gameObject).GetType().GetMethod("GetComponentsInChildren", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(Type), typeof(bool) }, null); if (method != null) { return (method.Invoke(gameObject, new object[2] { typeof(MonoBehaviour), true }) as IEnumerable) ?? Array.Empty(); } MethodInfo method2 = ((object)gameObject).GetType().GetMethod("GetComponentsInChildren", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); if (method2 != null) { return (method2.Invoke(gameObject, new object[1] { typeof(MonoBehaviour) }) as IEnumerable) ?? Array.Empty(); } return Array.Empty(); } private static object GetMemberValue(object target, string memberName) { Type type = target?.GetType(); if (type == null) { return null; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(target, null); } return null; } private static void SetMemberValue(object target, string memberName, T value) { Type type = target?.GetType(); if (type == null) { return; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(target, value); return; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); } } } } namespace RoundsPartyPack.Draft { internal static class CardMetadataRegistry { private static readonly Dictionary MetadataByCardName = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary TemplatesByObjectName = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary TemplatesByTitle = new Dictionary(StringComparer.Ordinal); internal static void Register(CardInfo cardInfo) { if (cardInfo != null && ((Component)cardInfo).gameObject.GetComponent(typeof(BalancedCard)) is BalancedCard balancedCard) { CardSpec definition = balancedCard.Definition; MetadataByCardName[((Object)((Component)cardInfo).gameObject).name] = new ManagedCardMetadata(definition, balancedCard.HasUniqueMechanic, balancedCard.MechanicScore, balancedCard.MechanicBuckets); } } internal static bool TryGet(CardInfo cardInfo, out CardSpec spec) { if (TryGetMetadata(cardInfo, out var metadata)) { spec = metadata.Spec; return true; } spec = null; return false; } internal static bool TryGet(string cardObjectName, out CardSpec spec) { if (TryGetMetadata(cardObjectName, out var metadata)) { spec = metadata.Spec; return true; } spec = null; return false; } internal static bool TryGetMetadata(CardInfo cardInfo, out ManagedCardMetadata metadata) { if (cardInfo == null) { metadata = null; return false; } return MetadataByCardName.TryGetValue(((Object)((Component)cardInfo).gameObject).name, out metadata); } internal static bool TryGetMetadata(string cardObjectName, out ManagedCardMetadata metadata) { return MetadataByCardName.TryGetValue(cardObjectName, out metadata); } internal static bool IsManagedCard(CardInfo cardInfo) { CardSpec spec; return TryGet(cardInfo, out spec); } internal static void RegisterTemplate(string objectName, TemplateCardDefinition definition) { if (!string.IsNullOrEmpty(objectName) && definition != null) { TemplatesByObjectName[objectName] = definition; TemplatesByObjectName[BuildRuntimeObjectName(definition.Spec.ToggleCategory, definition.Spec.DisplayTitle)] = definition; TemplatesByObjectName[BuildRuntimeObjectName(definition.Spec.ToggleCategory, definition.Spec.Title)] = definition; TemplatesByTitle[definition.Spec.DisplayTitle] = definition; TemplatesByTitle[definition.Spec.Title] = definition; } } internal static bool TryGetTemplate(string objectName, string title, out TemplateCardDefinition definition) { if (!string.IsNullOrEmpty(objectName)) { if (TemplatesByObjectName.TryGetValue(objectName, out definition)) { return true; } string text = TrimCloneSuffix(objectName); if (!string.Equals(text, objectName, StringComparison.Ordinal) && TemplatesByObjectName.TryGetValue(text, out definition)) { return true; } } if (!string.IsNullOrEmpty(title)) { if (TemplatesByTitle.TryGetValue(title, out definition)) { return true; } string text2 = TrimCloneSuffix(title); if (!string.Equals(text2, title, StringComparison.Ordinal) && TemplatesByTitle.TryGetValue(text2, out definition)) { return true; } } definition = null; return false; } private static string TrimCloneSuffix(string value) { if (string.IsNullOrEmpty(value)) { return value; } int num = value.IndexOf("(Clone)", StringComparison.Ordinal); if (num < 0) { return value; } int num2; for (num2 = num; num2 > 0; num2--) { char c = value[num2 - 1]; if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { break; } } return value.Substring(0, num2); } private static string BuildRuntimeObjectName(string modName, string title) { return "__" + modName + "__" + title; } } internal sealed class ManagedCardMetadata { internal CardSpec Spec { get; } internal bool HasUniqueMechanic { get; } internal int MechanicScore { get; } internal IReadOnlyList MechanicBuckets { get; } internal ManagedCardMetadata(CardSpec spec, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) { Spec = spec; HasUniqueMechanic = hasUniqueMechanic; MechanicScore = mechanicScore; MechanicBuckets = mechanicBuckets ?? spec?.MechanicBuckets; } } internal sealed class DraftEconomyController : MonoBehaviour { private readonly List _futureRoundExtraPickBonuses = new List(); private Player _player; internal static DraftEconomyController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void QueueFutureRoundExtraPicks(int futureRoundDraws, int extraPicksPerDraw) { int num = Math.Max(0, futureRoundDraws); int num2 = Math.Max(0, extraPicksPerDraw); if (num != 0 && num2 != 0) { for (int i = 0; i < num; i++) { _futureRoundExtraPickBonuses.Add(num2); } } } internal void RemoveQueuedFutureRoundExtraPicks(int futureRoundDraws, int extraPicksPerDraw) { int num = Math.Max(0, futureRoundDraws); int num2 = Math.Max(0, extraPicksPerDraw); if (num == 0 || num2 == 0 || _futureRoundExtraPickBonuses.Count == 0) { return; } int num3 = 0; int num4 = _futureRoundExtraPickBonuses.Count - 1; while (num4 >= 0 && num3 < num) { if (_futureRoundExtraPickBonuses[num4] == num2) { _futureRoundExtraPickBonuses.RemoveAt(num4); num3++; } num4--; } } internal int ConsumeFutureRoundExtraPicks() { if (_futureRoundExtraPickBonuses.Count == 0) { return 0; } int result = Math.Max(0, _futureRoundExtraPickBonuses[0]); _futureRoundExtraPickBonuses.RemoveAt(0); return result; } internal int PeekQueuedRoundBonuses() { return _futureRoundExtraPickBonuses.Count; } private void Awake() { _player = ((Component)this).GetComponent(); } } internal static class DraftEngine { private static readonly CardInfo[] EmptyPool = Array.Empty(); private static CardInfo[] _masterPickPool = EmptyPool; private static bool _initialized; internal static void Initialize() { if (!_initialized) { _initialized = true; GameModeManager.AddHook("PickStart", (Func)CaptureMasterPickPool, 800); GameModeManager.AddHook("PickEnd", (Func)RestoreMasterPickPool, 0); GameModeManager.AddHook("RoundStart", (Func)ResetState, 800); GameModeManager.AddHook("GameStart", (Func)ResetState, 800); GameModeManager.AddHook("GameEnd", (Func)ResetState, 800); } } internal static void PreparePickPool(CardChoice cardChoice, int pickerId) { if (cardChoice == null) { return; } Player val = PlayerManager.instance?.players?.FirstOrDefault((Func)((Player candidate) => candidate.playerID == pickerId)); if (val == null) { return; } CardInfo[] basePool = GetBasePool(cardChoice); if (basePool.Length != 0) { CardInfo[] array = BuildCuratedPool(val, basePool); if (array.Length != 0) { cardChoice.cards = array; } } } private static CardInfo[] GetBasePool(CardChoice cardChoice) { if (_masterPickPool.Length != 0) { return _masterPickPool; } _masterPickPool = cardChoice.cards?.ToArray() ?? EmptyPool; return _masterPickPool; } private static CardInfo[] BuildCuratedPool(Player player, CardInfo[] fullPool) { DraftPlayerState draftPlayerState = DraftPlayerState.FromPlayer(player); bool flag = BetterRoundsSettings.UseBetterRoundsOnlyDraftPool(); List list = new List(); List<(CardInfo, CardSpec, int, bool, int, IReadOnlyList)> list2 = new List<(CardInfo, CardSpec, int, bool, int, IReadOnlyList)>(); foreach (CardInfo val in fullPool) { if (!CardMetadataRegistry.TryGetMetadata(val, out var metadata)) { if (!flag) { list.Add(val); } continue; } CardSpec spec = metadata.Spec; int num = ScoreManagedCard(spec, draftPlayerState, metadata.HasUniqueMechanic, metadata.MechanicScore, metadata.MechanicBuckets); if (num > 0) { list2.Add((val, spec, num, metadata.HasUniqueMechanic, metadata.MechanicScore, metadata.MechanicBuckets)); } } if (list2.Count == 0) { return fullPool; } int num2 = (flag ? Math.Max(44, Math.Min(84, list2.Count * 3 / 5)) : Math.Max(36, Math.Min(72, list2.Count / 2))); List<(CardInfo, CardSpec, int, bool, int, IReadOnlyList)> source = list2.OrderByDescending<(CardInfo, CardSpec, int, bool, int, IReadOnlyList), int>(((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.score).ThenBy<(CardInfo, CardSpec, int, bool, int, IReadOnlyList), string>(((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => ((Object)((Component)entry.card).gameObject).name, StringComparer.Ordinal).ToList(); List list3 = new List(num2); HashSet selectedNames = new HashSet(StringComparer.Ordinal); HashSet selectedBuckets = new HashSet(StringComparer.Ordinal); int earlyMechanicBudget = GetEarlyMechanicBudget(draftPlayerState, num2, flag); int supportMechanicBudget = GetSupportMechanicBudget(draftPlayerState, num2, flag); int flexibleMechanicBudget = GetFlexibleMechanicBudget(draftPlayerState, num2, flag); int controlBridgeBudget = GetControlBridgeBudget(draftPlayerState, num2, flag); TakeBudget(source, list3, selectedNames, selectedBuckets, earlyMechanicBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.hasUniqueMechanic && entry.spec.Tier <= 3 && !entry.spec.IsTroll); TakeBudget(source, list3, selectedNames, selectedBuckets, supportMechanicBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => !entry.hasUniqueMechanic && entry.mechanicScore > 0 && entry.spec.Tier <= 3 && !entry.spec.IsTroll); TakeBudget(source, list3, selectedNames, selectedBuckets, controlBridgeBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.spec.Tier <= 3 && !entry.spec.IsTroll && entry.mechanicBuckets != null && entry.mechanicBuckets.Any((string bucket) => string.Equals(bucket, "hazard", StringComparison.OrdinalIgnoreCase) || string.Equals(bucket, "summon", StringComparison.OrdinalIgnoreCase) || string.Equals(bucket, "disruption", StringComparison.OrdinalIgnoreCase))); TakeBudget(source, list3, selectedNames, selectedBuckets, flexibleMechanicBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.hasUniqueMechanic && !entry.spec.IsTroll); int num3 = Math.Max(0, num2 - list3.Count); int num4 = Math.Min(10, Math.Max(4, num3 / 6)); int num5 = ((draftPlayerState.TotalManagedCards >= 2) ? Math.Min(4, Math.Max(1, num3 / 20)) : 0); int neutralBudget = GetNeutralBudget(draftPlayerState, num3, flag); int num6 = Math.Max(8, num3 - num4 - num5 - neutralBudget); if (flag) { num6 += 4; } int foundationBudget = GetFoundationBudget(draftPlayerState, num2, flag); TakeBudget(source, list3, selectedNames, selectedBuckets, num4, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.spec.IsWildcard); TakeBudget(source, list3, selectedNames, selectedBuckets, num5, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.spec.IsTroll); TakeBudget(source, list3, selectedNames, selectedBuckets, neutralBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => !entry.spec.IsManagedClass && !entry.spec.IsWildcard && !entry.spec.IsTroll); TakeBudget(source, list3, selectedNames, selectedBuckets, num6, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => entry.spec.IsManagedClass && (entry.hasUniqueMechanic || entry.mechanicScore > 0 || entry.spec.Tier >= 3)); TakeBudget(source, list3, selectedNames, selectedBuckets, foundationBudget, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => IsFoundationEntry(entry.spec, entry.hasUniqueMechanic, entry.mechanicScore)); TakeBudget(source, list3, selectedNames, selectedBuckets, num2 - list3.Count, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => !IsFoundationEntry(entry.spec, entry.hasUniqueMechanic, entry.mechanicScore)); TakeBudget(source, list3, selectedNames, selectedBuckets, num2 - list3.Count, ((CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) entry) => true); IEnumerable source2; if (!flag) { source2 = list.Concat(list3); } else { IEnumerable enumerable = list3; source2 = enumerable; } CardInfo[] array = (from @group in source2.GroupBy((CardInfo card) => ((Object)((Component)card).gameObject).name, StringComparer.Ordinal) select @group.First()).ToArray(); if (array.Length != 0) { return array; } return fullPool; } private static int ScoreManagedCard(CardSpec spec, DraftPlayerState state, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) { int synergyScore = GetSynergyScore(spec, state); int bucketScore = GetBucketScore(mechanicBuckets, state); int pivotScore = GetPivotScore(spec, state, hasUniqueMechanic, mechanicScore, mechanicBuckets); int tierTimingScore = GetTierTimingScore(spec, state); int uniqueMechanicScore = GetUniqueMechanicScore(spec, state, hasUniqueMechanic, mechanicScore); int weakUpperTierPenalty = GetWeakUpperTierPenalty(spec, state, hasUniqueMechanic, mechanicScore); int foundationPenalty = GetFoundationPenalty(spec, state, hasUniqueMechanic, mechanicScore); if (spec.IsWildcard) { return 86 + ((state.ActiveClassCount > 1) ? 8 : 0) + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; } if (spec.IsTroll) { if (state.TotalManagedCards < 2) { return 0; } return ((state.TrollCards == 0) ? 38 : 22) + Math.Min(8, synergyScore / 2) + Math.Max(0, uniqueMechanicScore / 2) + Math.Max(0, bucketScore / 2) + foundationPenalty; } if (!spec.IsManagedClass) { return ((state.TotalManagedCards < 3) ? 82 : 76) + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; } int pips = state.GetPips(spec.CardClass); int num = GetTrackScore(spec.Track); if (pips >= spec.MinimumClassPips) { if (state.DominantClass == spec.CardClass) { num += 12; } num += Math.Min(4, pips) * 4; num += synergyScore; num += bucketScore; num += pivotScore; num += tierTimingScore; return num + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; } if (!spec.AllowOffClassWithoutPips || pips > 0) { if (synergyScore < 16 || state.TotalManagedCards < 2) { return 0; } return Math.Max(18, num - 30 + synergyScore + bucketScore + pivotScore + tierTimingScore / 2 + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty); } if (state.TotalManagedCards == 0) { return 56 + synergyScore + bucketScore + pivotScore + uniqueMechanicScore + foundationPenalty; } if (state.TotalManagedCards < 3) { return 38 + synergyScore + bucketScore + pivotScore + uniqueMechanicScore + foundationPenalty; } switch (spec.Track) { case CardTrack.Payoff: return ((state.ActiveClassCount > 1) ? 30 : 20) + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; case CardTrack.Signature: return ((state.ActiveClassCount > 1) ? 26 : 18) + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; case CardTrack.Capstone: if (synergyScore < 18) { return 0; } return 14 + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; default: return ((state.ActiveClassCount > 1) ? 28 : 20) + synergyScore + bucketScore + pivotScore + tierTimingScore + uniqueMechanicScore + weakUpperTierPenalty + foundationPenalty; } } private static int GetBucketScore(IReadOnlyList mechanicBuckets, DraftPlayerState state) { if (mechanicBuckets == null || mechanicBuckets.Count == 0) { return 0; } int num = 0; foreach (string mechanicBucket in mechanicBuckets) { int bucketCount = state.GetBucketCount(mechanicBucket); if (bucketCount > 0) { num += Math.Min(2, bucketCount) * 5; } } if (mechanicBuckets.Count > 1) { num += 2; } return Math.Min(20, num); } private static int GetPivotScore(CardSpec spec, DraftPlayerState state, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) { if (mechanicBuckets == null || mechanicBuckets.Count == 0) { return 0; } if (state.TotalManagedCards < 2 || spec.Tier >= 4) { return 0; } int num = mechanicBuckets.Count((string bucket) => state.GetBucketCount(bucket) <= 0); if (num <= 0) { return 0; } if (hasUniqueMechanic) { return Math.Min(6, num * 3); } if (mechanicScore >= 2) { return Math.Min(4, num * 2); } return 0; } private static int GetFoundationPenalty(CardSpec spec, DraftPlayerState state, bool hasUniqueMechanic, int mechanicScore) { if (!IsFoundationEntry(spec, hasUniqueMechanic, mechanicScore)) { return 0; } int num = 0; if (!spec.IsManagedClass) { num -= ((state.TotalManagedCards <= 1) ? 6 : ((state.TotalManagedCards <= 3) ? 12 : 18)); } else if (spec.Track == CardTrack.Core) { num -= ((state.TotalManagedCards <= 2) ? 4 : 8); } if (spec.Tier == 1 && state.TotalManagedCards >= 3) { num -= 6; } else if (spec.Tier == 2 && state.TotalManagedCards >= 5) { num -= 4; } return num; } private static int GetWeakUpperTierPenalty(CardSpec spec, DraftPlayerState state, bool hasUniqueMechanic, int mechanicScore) { if (spec == null || spec.Tier < 4) { return 0; } if (hasUniqueMechanic || mechanicScore >= 2) { return 0; } if (spec.Track == CardTrack.Capstone) { if (state.TotalManagedCards >= 5) { return -18; } return -26; } if (spec.Track == CardTrack.Signature) { if (state.TotalManagedCards >= 4) { return -12; } return -18; } return -8; } private static int GetUniqueMechanicScore(CardSpec spec, DraftPlayerState state, bool hasUniqueMechanic, int mechanicScore) { if (spec == null) { return 0; } if (mechanicScore <= 0) { if (state.TotalManagedCards == 0) { if (spec.Tier != 1) { return -3; } return 0; } if (state.TotalManagedCards <= 2) { if (spec.Tier != 1) { return -3; } return -5; } if (spec.Tier == 1) { return -14; } if (spec.Tier == 2) { return -10; } if (spec.Track != CardTrack.Core) { return -2; } return -5; } int num; if (hasUniqueMechanic) { num = ((spec.Tier > 3) ? ((state.TotalManagedCards <= 3) ? 6 : 10) : ((state.TotalManagedCards <= 3) ? 16 : 11)); int num2 = Math.Max(0, mechanicScore - 2); num += Math.Min(8, num2 * 2); if (spec.Track == CardTrack.Core || spec.Track == CardTrack.Payoff) { num += 4; } else if (spec.Track == CardTrack.Signature || spec.Track == CardTrack.Capstone) { num += 2; } if (!spec.IsManagedClass) { num -= 2; } return num; } num = ((spec.Tier <= 2) ? 8 : 5); if (state.TotalManagedCards <= 2) { num += 3; } else if (state.TotalManagedCards >= 5) { num--; } num += Math.Min(4, mechanicScore * 2); if (spec.Track == CardTrack.Core) { num += 2; } if (!spec.IsManagedClass) { num--; } return num; } private static int GetEarlyMechanicBudget(DraftPlayerState state, int managedBudget, bool betterRoundsOnly) { if (managedBudget <= 0) { return 0; } if (state.TotalManagedCards <= 1) { if (!betterRoundsOnly) { return Math.Min(14, Math.Max(10, managedBudget / 3)); } return Math.Min(18, Math.Max(12, managedBudget / 3)); } if (state.TotalManagedCards <= 3) { if (!betterRoundsOnly) { return Math.Min(16, Math.Max(12, managedBudget / 3)); } return Math.Min(18, Math.Max(13, managedBudget / 3)); } if (state.TotalManagedCards <= 5) { if (!betterRoundsOnly) { return Math.Min(14, Math.Max(10, managedBudget / 4)); } return Math.Min(16, Math.Max(11, managedBudget / 4)); } if (!betterRoundsOnly) { return Math.Min(10, Math.Max(6, managedBudget / 5)); } return Math.Min(12, Math.Max(7, managedBudget / 5)); } private static int GetFlexibleMechanicBudget(DraftPlayerState state, int managedBudget, bool betterRoundsOnly) { if (managedBudget <= 0) { return 0; } if (state.TotalManagedCards <= 3) { if (!betterRoundsOnly) { return Math.Min(8, Math.Max(4, managedBudget / 8)); } return Math.Min(10, Math.Max(5, managedBudget / 8)); } if (!betterRoundsOnly) { return Math.Min(6, Math.Max(3, managedBudget / 10)); } return Math.Min(8, Math.Max(4, managedBudget / 10)); } private static int GetControlBridgeBudget(DraftPlayerState state, int managedBudget, bool betterRoundsOnly) { if (managedBudget <= 0) { return 0; } int num = 0; if (state.GetBucketCount("hazard") <= 0) { num++; } if (state.GetBucketCount("summon") <= 0) { num++; } if (state.GetBucketCount("disruption") <= 0) { num++; } if (num == 0) { return 0; } if (state.TotalManagedCards <= 2) { if (!betterRoundsOnly) { return Math.Min(4, Math.Max(2, num + 1)); } return Math.Min(5, Math.Max(3, num + 1)); } if (state.TotalManagedCards <= 5) { if (!betterRoundsOnly) { return Math.Min(3, Math.Max(1, num)); } return Math.Min(4, Math.Max(2, num)); } return 1; } private static int GetSupportMechanicBudget(DraftPlayerState state, int managedBudget, bool betterRoundsOnly) { if (managedBudget <= 0) { return 0; } if (state.TotalManagedCards <= 2) { if (!betterRoundsOnly) { return Math.Min(6, Math.Max(4, managedBudget / 10)); } return Math.Min(7, Math.Max(4, managedBudget / 9)); } if (state.TotalManagedCards <= 5) { if (!betterRoundsOnly) { return Math.Min(5, Math.Max(3, managedBudget / 12)); } return Math.Min(6, Math.Max(3, managedBudget / 11)); } if (!betterRoundsOnly) { return Math.Min(4, Math.Max(2, managedBudget / 14)); } return Math.Min(5, Math.Max(2, managedBudget / 12)); } private static int GetNeutralBudget(DraftPlayerState state, int remainingBudget, bool betterRoundsOnly) { if (remainingBudget <= 0) { return 0; } if (state.TotalManagedCards <= 1) { if (!betterRoundsOnly) { return Math.Min(10, Math.Max(6, remainingBudget / 5)); } return Math.Min(7, Math.Max(3, remainingBudget / 8)); } if (state.TotalManagedCards <= 3) { if (!betterRoundsOnly) { return Math.Min(8, Math.Max(5, remainingBudget / 7)); } return Math.Min(6, Math.Max(2, remainingBudget / 10)); } if (!betterRoundsOnly) { return Math.Min(6, Math.Max(4, remainingBudget / 9)); } return Math.Min(4, Math.Max(1, remainingBudget / 12)); } private static int GetFoundationBudget(DraftPlayerState state, int managedBudget, bool betterRoundsOnly) { if (managedBudget <= 0) { return 0; } if (state.TotalManagedCards <= 1) { if (!betterRoundsOnly) { return Math.Min(12, Math.Max(8, managedBudget / 5)); } return Math.Min(8, Math.Max(5, managedBudget / 7)); } if (state.TotalManagedCards <= 3) { if (!betterRoundsOnly) { return Math.Min(8, Math.Max(5, managedBudget / 8)); } return Math.Min(6, Math.Max(3, managedBudget / 10)); } if (!betterRoundsOnly) { return Math.Min(5, Math.Max(3, managedBudget / 12)); } return Math.Min(4, Math.Max(2, managedBudget / 14)); } private static int GetTrackScore(CardTrack track) { return track switch { CardTrack.Starter => 45, CardTrack.Core => 55, CardTrack.Payoff => 80, CardTrack.Signature => 95, CardTrack.Capstone => 110, CardTrack.Wildcard => 65, CardTrack.Troll => 30, _ => 40, }; } private static int GetSynergyScore(CardSpec spec, DraftPlayerState state) { if (spec?.AuthoredDraftTags == null || spec.AuthoredDraftTags.Count == 0) { return 0; } int num = 0; int num2 = 0; foreach (string authoredDraftTag in spec.AuthoredDraftTags) { int tagCount = state.GetTagCount(authoredDraftTag); if (tagCount > 0) { num2++; num += Math.Min(3, tagCount) * 5; } } if (num2 >= 2) { num += 5; } if (num2 >= 3) { num += 4; } if (spec.IsWildcard && num2 >= 2) { num += 3; } return Math.Min(30, num); } private static int GetTierTimingScore(CardSpec spec, DraftPlayerState state) { int totalManagedCards = state.TotalManagedCards; if (totalManagedCards <= 1) { if (spec.Tier > 2) { return -10; } return 6; } if (totalManagedCards <= 3) { if (spec.Tier == 3) { return 4; } if (spec.Tier < 4) { return 0; } return -2; } if (totalManagedCards <= 5) { if (spec.Tier >= 4) { return 6; } if (spec.Tier != 1) { return 0; } return -6; } if (spec.Tier >= 4) { return 10; } if (spec.Tier != 1) { if (spec.Tier != 2) { return 2; } return -3; } return -10; } private static IEnumerator CaptureMasterPickPool(IGameModeHandler gameModeHandler) { _masterPickPool = CardChoice.instance?.cards?.ToArray() ?? EmptyPool; yield break; } private static IEnumerator RestoreMasterPickPool(IGameModeHandler gameModeHandler) { if (_masterPickPool.Length != 0 && CardChoice.instance != null) { CardChoice.instance.cards = _masterPickPool.ToArray(); } _masterPickPool = EmptyPool; yield break; } private static IEnumerator ResetState(IGameModeHandler gameModeHandler) { _masterPickPool = EmptyPool; yield break; } private static void TakeBudget(IEnumerable<(CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets)> source, ICollection destination, ISet selectedNames, ISet selectedBuckets, int budget, Func<(CardInfo card, CardSpec spec, int score, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets), bool> predicate) { if (budget <= 0) { return; } int num = 0; foreach (var item in source) { if (num >= budget) { break; } if (!predicate(item)) { continue; } string diversityKey = GetDiversityKey(item.spec, item.hasUniqueMechanic, item.mechanicScore, item.mechanicBuckets); if (!string.IsNullOrEmpty(diversityKey) && selectedBuckets.Contains(diversityKey)) { continue; } string name = ((Object)((Component)item.card).gameObject).name; if (selectedNames.Add(name)) { if (!string.IsNullOrEmpty(diversityKey)) { selectedBuckets.Add(diversityKey); } destination.Add(item.card); num++; } } foreach (var item2 in source) { if (num >= budget) { break; } if (predicate(item2)) { string name2 = ((Object)((Component)item2.card).gameObject).name; if (selectedNames.Add(name2)) { destination.Add(item2.card); num++; } } } } private static bool IsFoundationEntry(CardSpec spec, bool hasUniqueMechanic, int mechanicScore) { if (spec != null && !hasUniqueMechanic && mechanicScore <= 0 && spec.Tier <= 2 && !spec.IsWildcard) { return !spec.IsTroll; } return false; } private static string GetDiversityKey(CardSpec spec, bool hasUniqueMechanic, int mechanicScore, IReadOnlyList mechanicBuckets) { if (spec == null) { return null; } if (spec.IsWildcard || spec.IsTroll) { return spec.Title; } if (string.IsNullOrWhiteSpace(spec.Family)) { return spec.Title; } string text = mechanicBuckets?.FirstOrDefault((string bucket) => !string.IsNullOrWhiteSpace(bucket)); if (spec.Tier >= 4 && !hasUniqueMechanic && mechanicScore <= 1) { return $"weak-upper|{spec.CardClass}|{text ?? spec.Family}"; } if ((hasUniqueMechanic || mechanicScore >= 2) && !string.IsNullOrWhiteSpace(text)) { if (spec.Tier <= 2) { return $"bridge|{spec.CardClass}|{text}"; } if (spec.Tier == 3) { return $"payoff|{spec.CardClass}|{text}"; } } if (mechanicScore > 0 && !hasUniqueMechanic && !string.IsNullOrWhiteSpace(text)) { return $"support|{spec.CardClass}|{text}"; } if (!hasUniqueMechanic && mechanicScore <= 0 && spec.Tier <= 2 && spec.AuthoredDraftTags != null && spec.AuthoredDraftTags.Count > 0) { string text2 = spec.AuthoredDraftTags.FirstOrDefault((string tag) => !string.IsNullOrWhiteSpace(tag)); if (!string.IsNullOrWhiteSpace(text2)) { return $"foundation|{spec.CardClass}|{spec.Track}|{text2}"; } } return $"{spec.Family}|{spec.Track}"; } } internal sealed class DraftPlayerState { private readonly Dictionary _classPips = new Dictionary(); private readonly Dictionary _tagCounts = new Dictionary(); private readonly Dictionary _bucketCounts = new Dictionary(); internal int TotalManagedCards { get; private set; } internal int WildcardCards { get; private set; } internal int TrollCards { get; private set; } internal int ActiveClassCount => _classPips.Count((KeyValuePair pair) => pair.Value > 0); internal CardClass? DominantClass => ((IEnumerable>)(from pair in _classPips where pair.Value > 0 orderby pair.Value descending, pair.Key.ToString() select pair)).Select((Func, CardClass?>)((KeyValuePair pair) => pair.Key)).FirstOrDefault(); private DraftPlayerState() { } internal int GetPips(CardClass cardClass) { if (!_classPips.TryGetValue(cardClass, out var value)) { return 0; } return value; } internal int GetTagCount(string tag) { if (!string.IsNullOrWhiteSpace(tag)) { if (!_tagCounts.TryGetValue(tag, out var value)) { return 0; } return value; } return 0; } internal int GetBucketCount(string bucket) { if (!string.IsNullOrWhiteSpace(bucket)) { if (!_bucketCounts.TryGetValue(bucket, out var value)) { return 0; } return value; } return 0; } internal static DraftPlayerState FromPlayer(Player player) { DraftPlayerState draftPlayerState = new DraftPlayerState(); if (player?.data?.currentCards == null) { return draftPlayerState; } foreach (CardInfo currentCard in player.data.currentCards) { if (!CardMetadataRegistry.TryGetMetadata(currentCard, out var metadata) || metadata?.Spec == null) { continue; } CardSpec spec = metadata.Spec; draftPlayerState.TotalManagedCards++; draftPlayerState.AddTags(metadata); if (spec.IsWildcard) { draftPlayerState.WildcardCards++; } else if (spec.IsTroll) { draftPlayerState.TrollCards++; } else if (spec.IsManagedClass) { if (!draftPlayerState._classPips.ContainsKey(spec.CardClass)) { draftPlayerState._classPips[spec.CardClass] = 0; } draftPlayerState._classPips[spec.CardClass]++; } } return draftPlayerState; } private void AddTags(ManagedCardMetadata metadata) { CardSpec cardSpec = metadata?.Spec; if (cardSpec == null) { return; } foreach (string item in new HashSet(cardSpec.AuthoredDraftTags)) { if (!_tagCounts.ContainsKey(item)) { _tagCounts[item] = 0; } _tagCounts[item]++; } foreach (string item2 in new HashSet(metadata.MechanicBuckets ?? cardSpec.MechanicBuckets)) { if (!_bucketCounts.ContainsKey(item2)) { _bucketCounts[item2] = 0; } _bucketCounts[item2]++; } } } internal static class PickFlowController { private struct QueuedPicker : IEquatable { internal int PickerId { get; } internal PickerType PickerType { get; } internal QueuedPicker(int pickerId, PickerType pickerType) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) PickerId = pickerId; PickerType = pickerType; } public bool Equals(QueuedPicker other) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (PickerId == other.PickerId) { return PickerType == other.PickerType; } return false; } public override bool Equals(object obj) { if (obj is QueuedPicker other) { return Equals(other); } return false; } public override int GetHashCode() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected I4, but got Unknown return (PickerId * 397) ^ PickerType; } } private const float DelayBetweenExtraPicks = 0.1f; private static bool _extraPicksInProgress; private static bool _initialized; private static bool _starterPickPending; private static QueuedPicker? _currentPicker; internal static bool IsStarterPickPhaseActive { get; private set; } internal static void Initialize() { if (!_initialized) { _initialized = true; GameModeManager.AddHook("GameStart", (Func)QueueStarterPickPhase, 800); GameModeManager.AddHook("GameEnd", (Func)ResetPhaseState, 800); GameModeManager.AddHook("PickStart", (Func)BeginPickPhase, 800); GameModeManager.AddHook("PlayerPickEnd", (Func)ExecuteExtraPicks, 600); GameModeManager.AddHook("PickEnd", (Func)FinalizePickPhase, 0); } } internal static void TrackPicker(int pickerId, PickerType pickerType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) _currentPicker = new QueuedPicker(pickerId, pickerType); } [UnboundRPC] public static void RPC_RequestSync(int requestingPlayer) { NetworkingManager.RPC(typeof(PickFlowController), "RPC_SyncResponse", new object[2] { requestingPlayer, PhotonNetwork.LocalPlayer.ActorNumber }); } [UnboundRPC] public static void RPC_SyncResponse(int requestingPlayer, int readyPlayer) { if (Plugin.Instance != null && PhotonNetwork.LocalPlayer.ActorNumber == requestingPlayer) { MonoBehaviourExtensions.RemovePendingRequest((MonoBehaviour)(object)Plugin.Instance, readyPlayer, "RPC_RequestSync"); } } private static IEnumerator QueueStarterPickPhase(IGameModeHandler gameModeHandler) { _starterPickPending = true; IsStarterPickPhaseActive = false; yield break; } private static IEnumerator ResetPhaseState(IGameModeHandler gameModeHandler) { _starterPickPending = false; IsStarterPickPhaseActive = false; _extraPicksInProgress = false; _currentPicker = null; yield break; } private static IEnumerator BeginPickPhase(IGameModeHandler gameModeHandler) { if (!_extraPicksInProgress) { _currentPicker = null; } IsStarterPickPhaseActive = _starterPickPending; yield break; } private static IEnumerator ExecuteExtraPicks(IGameModeHandler gameModeHandler) { int cardsSelectedPerDraw = GetCardsSelectedPerDrawForCurrentPhase(); if (_extraPicksInProgress || !_currentPicker.HasValue) { yield break; } _extraPicksInProgress = true; QueuedPicker picker = _currentPicker.Value; if (!IsStarterPickPhaseActive) { int num = DraftEconomyController.GetOrAdd(ResolvePickerPlayer(picker.PickerId))?.ConsumeFutureRoundExtraPicks() ?? 0; cardsSelectedPerDraw += num; if (num > 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"BetterRounds granted {num} queued bonus pick(s) to picker {picker.PickerId}."); } } } if (cardsSelectedPerDraw <= 1) { _extraPicksInProgress = false; yield break; } try { for (int pickIndex = 1; pickIndex < cardsSelectedPerDraw; pickIndex++) { if (CardChoice.instance == null || CardChoiceVisuals.instance == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"BetterRounds cancelled extra picks because card choice visuals were unavailable."); } break; } ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"BetterRounds running extra pick {pickIndex + 1}/{cardsSelectedPerDraw} for picker {picker.PickerId}."); } yield return GameModeManager.TriggerHook("PlayerPickStart"); CardChoiceVisuals.instance.Show(picker.PickerId, true); yield return CardChoice.instance.DoPick(1, picker.PickerId, picker.PickerType); yield return (object)new WaitForSecondsRealtime(0.1f); yield return GameModeManager.TriggerHook("PlayerPickEnd"); yield return (object)new WaitForSecondsRealtime(0.1f); } } finally { _extraPicksInProgress = false; } } private static IEnumerator FinalizePickPhase(IGameModeHandler gameModeHandler) { if (IsStarterPickPhaseActive) { _starterPickPending = false; } IsStarterPickPhaseActive = false; _currentPicker = null; yield break; } private static int GetCardsSelectedPerDrawForCurrentPhase() { return BetterRoundsSettings.GetCardsSelectedPerDraw(IsStarterPickPhaseActive); } private static Player ResolvePickerPlayer(int pickerId) { if (PlayerManager.instance?.players == null) { return null; } foreach (Player player in PlayerManager.instance.players) { if (player != null && player.playerID == pickerId) { return player; } } return null; } private static IEnumerator WaitForSyncUp() { if (Plugin.Instance != null && !PhotonNetwork.OfflineMode && PhotonNetwork.CurrentRoom != null) { yield return MonoBehaviourExtensions.SyncMethod((MonoBehaviour)(object)Plugin.Instance, "RPC_RequestSync", (int[])null, new object[1] { PhotonNetwork.LocalPlayer.ActorNumber }); } } } internal static class PickOfferLayout { private const float DefaultMaxXWorld = 25f; private const float MaxXScreen = 0.85f; private const float ZDepth = -5f; private const int MaxCardsPerRow = 10; internal static int GetDisplayedCardCount() { int cardsShownPerDraw = BetterRoundsSettings.GetCardsShownPerDraw(PickFlowController.IsStarterPickPhaseActive); CardInfo[] array = CardChoice.instance?.cards; if (array == null || array.Length == 0) { return cardsShownPerDraw; } return Mathf.Clamp(cardsShownPerDraw, 1, array.Length); } internal static List GetWorldPositions(int count, float offset = 0f) { return GetNormalizedPositions(count, offset).Select(WorldPoint).ToList(); } internal static List GetRotations(int count) { return (from position in GetNormalizedPositions(count) select Quaternion.Euler(0f, 0f, ArcAngle(position.x))).ToList(); } internal static Vector3 GetScale(int count) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0101: 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_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) float num = 1.04f * XWorldPoint(0.85f) / 25f; if (count == 5) { return Vector3.one * num; } if (count < 5) { return Vector3.one * num * (1f + 1f / (2f * (float)count)); } if (count > 10) { int num2 = Mathf.CeilToInt((float)count / 10f); int num3 = Mathf.CeilToInt((float)count / (float)num2); float num4 = Mathf.Clamp(5f / Mathf.Max(1f, (float)num3 - 1f), 0.38f, 1f); float num5 = Mathf.Clamp(1f / (1f + (float)(num2 - 1) * 0.15f), 0.42f, 1f); return Vector3.one * num * num4 * num5; } return Vector3.one * num * Mathf.Clamp(5f / ((float)count - 1f), 0.6f, 1f); } private static List GetNormalizedPositions(int count, float offset = 0f) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (count <= 0) { throw new ArgumentOutOfRangeException("count", "At least one card must be shown."); } if (count == 1) { return new List { new Vector3(0.5f, 0.5f, -5f) }; } if (count == 3) { offset -= 0.025f; } else if (count > 10) { int num = Mathf.CeilToInt((float)count / 10f); int num2 = count / num; int num3 = count % num; float rowSpacing = GetRowSpacing(num); float num4 = offset - rowSpacing * (float)(num - 1) * 0.5f; List list = new List(count); for (int i = 0; i < num; i++) { int count2 = num2 + ((i < num3) ? 1 : 0); float offset2 = num4 + rowSpacing * (float)i; list.AddRange(GetSingleRowPositions(count2, offset2)); } return list; } return GetSingleRowPositions(count, offset); } private static List GetSingleRowPositions(int count, float offset) { float num = ((count >= 4) ? Mathf.Clamp(0.85f + 0.025f * (float)(count - 5), 0.85f, 0.925f) : 0.75f); List list = new List(); if (count % 2 != 0) { list.Add(0.5f); int num2 = count / 2; float num3 = (num - 0.5f) / (float)num2; float num4 = 0.5f; for (int i = 0; i < num2; i++) { num4 += num3; list.Add(num4); list.Add(1f - num4); } } else { float num5 = 1f - num; float num6 = (num - num5) / (float)(count - 1); float num7 = num5; for (int j = 0; j < count; j++) { list.Add(num7); num7 += num6; } } list.Sort(); return ((IEnumerable)list).Select((Func)((float x) => new Vector3(x, Arc(x, offset), -5f))).ToList(); } private static float GetRowSpacing(int rowCount) { if (rowCount <= 2) { return 0.28f; } return Mathf.Lerp(0.22f, 0.08f, Mathf.InverseLerp(3f, 10f, (float)rowCount)); } private static float Arc(float x, float offset = 0f) { if (offset == 0f) { return -1.9591837f * x * x + 1.9591837f * x + 0.21020408f; } if (offset < 0f) { return 0f - Mathf.Sqrt(1f + 4f * Mathf.Pow(x - 0.5f, 2f)) + 1.7f + offset; } return 0f - Mathf.Sqrt(1f + 2f * Mathf.Pow(x - 0.5f, 2f)) + 1.7f + offset; } private static float ArcAngle(float x) { return -42.85714f * (x - 0.5f); } private static Vector3 WorldPoint(Vector3 normalizedPoint) { //IL_0055: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Camera val = ((MainCam.instance == null) ? null : ((Component)((Component)MainCam.instance).transform).GetComponent()); if (val == null) { return new Vector3(0f, 0f, -5f); } normalizedPoint.x *= Screen.width; normalizedPoint.y *= Screen.height; Vector3 result = val.ScreenToWorldPoint(normalizedPoint); result.z = -5f; return result; } private static float XWorldPoint(float x) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return WorldPoint(new Vector3(x, 0f, 0f)).x; } } } namespace RoundsPartyPack.Cards { internal static class PrototypeTemplateCatalog { private static readonly TemplateCardDefinition[] Definitions; private static TemplateCardDefinition[] _allDefinitions; private static readonly CardTrack[] StandardTracks; private static readonly int[] StandardTiers; private static readonly CardTrack[] FoundationTracks; private static readonly int[] FoundationTiers; private static readonly Rarity[] FoundationRarities; private static readonly Rarity[] StandardRarities; private static readonly CardTrack[] SevenTracks; private static readonly int[] SevenTiers; private static readonly CardTrack[] FoundationSevenTracks; private static readonly int[] FoundationSevenTiers; private static readonly Rarity[] FoundationSevenRarities; private static readonly Rarity[] SevenRarities; private static readonly CardTrack[] EightTracks; private static readonly int[] EightTiers; private static readonly CardTrack[] FoundationEightTracks; private static readonly int[] FoundationEightTiers; private static readonly Rarity[] FoundationEightRarities; private static readonly Rarity[] EightRarities; internal static TemplateCardDefinition[] All => _allDefinitions ?? (_allDefinitions = CombineAll()); private static TemplateCardDefinition[] CombineAll() { TemplateCardDefinition[] array = BuildExpandedDefinitions(); List list = new List(Definitions.Length + array.Length); list.AddRange(Definitions); list.AddRange(array); return list.ToArray(); } private static TemplateCardDefinition[] BuildExpandedDefinitions() { List list = new List(308); list.AddRange(BuildMarksmanAdditions()); list.AddRange(BuildBulwarkAdditions()); list.AddRange(BuildSkirmisherAdditions()); list.AddRange(BuildTricksterAdditions()); list.AddRange(BuildDemolitionistAdditions()); list.AddRange(BuildBrawlerCards()); list.AddRange(BuildArcanistCards()); list.AddRange(BuildGamblerCards()); list.AddRange(BuildNeutralTractorFamily()); list.AddRange(BuildWildcardCards()); list.AddRange(BuildTrollCards()); return list.ToArray(); } private static IEnumerable BuildMarksmanAdditions() { return BuildTempoFamily(new string[7] { "Sightline Loop", "Flint Lens", "Threaded Barrel", "Needle Rain", "Linebreaker", "Horizon Claim", "Last Word" }, "Lean into cleaner tempo shooting and push every pick farther down the lane.", (CardThemeColorType)0, "Marksman", "Tempo pressure", CardClass.Marksman, FoundationSevenTracks, FoundationSevenTiers, FoundationSevenRarities, 0.89f, 0.03f, 1.08f, 0.02f, 0.97f, 0.01f); } private static IEnumerable BuildBulwarkAdditions() { return BuildBastionFamily(new string[7] { "Guard Rails", "Stone Stance", "Anchor Habit", "Plate Logic", "Iron Chapel", "Rampart Heart", "Unmoved" }, "Stack survivability and defensive uptime, then dare the round to move without you.", (CardThemeColorType)1, "Bulwark", "Defensive anchor", CardClass.Bulwark, FoundationSevenTracks, FoundationSevenTiers, FoundationSevenRarities, 1.15f, 0.03f, 0.97f, 0.02f, 0.97f, 0.02f, 0.95f, 0.015f); } private static IEnumerable BuildSkirmisherAdditions() { return BuildAerialFamily(new string[8] { "Slipstream", "Wall Skip", "Air Thread", "Feather Brake", "Crosswind", "Ghost Sprint", "Floor Is Gone", "Afterimage" }, "Win the spacing war with air time and repositioning, then accept the thinner margin for error.", (CardThemeColorType)2, "Skirmisher", "Mobility pressure", CardClass.Skirmisher, FoundationEightTracks, FoundationEightTiers, FoundationEightRarities, 0.88f, 0.035f, 1.06f, 0.02f, 1.08f, 0.025f, 0.96f, 0.015f, 0.98f, 0.012f); } private static IEnumerable BuildTricksterAdditions() { return BuildRicochetFamily(new string[8] { "Mirror Bite", "Pinball Logic", "Crooked Path", "Angle Tax", "Trapdoor", "Doorframe", "False Corner", "Exit Tax" }, "Turn walls and edges into extra pressure instead of dead space.", (CardThemeColorType)3, "Trickster", "Geometry abuse", CardClass.Trickster, FoundationEightTracks, FoundationEightTiers, FoundationEightRarities, 1, 1, 1.04f, 0.018f, 0.96f, 0.014f, 0.4f, 0.25f); } private static IEnumerable BuildDemolitionistAdditions() { return BuildVolleyFamily(new string[8] { "Breach Powder", "Fuse Rack", "Buckshot Mill", "Dustwake", "Flak Loom", "Shrapnel Bloom", "Crater Script", "Blast Orchard" }, "Broaden the cone, add metal to the air, and make each lane feel smaller.", (CardThemeColorType)4, "Demolitionist", "Area denial", CardClass.Demolitionist, FoundationEightTracks, FoundationEightTiers, FoundationEightRarities, 1, 1, 0.1f, 0.04f, 0.87f, 0.025f, 1.05f, 0.025f, 0.99f, 0.015f); } private static IEnumerable BuildBrawlerCards() { return BuildBruiserFamily(new string[10] { "Heavy Hands", "Closeout", "Shoulder Check", "Scrap Step", "Ring Rush", "Street Clinch", "Bruise Engine", "Elbow Room", "Corner Pressure", "Final Bell" }, "Get paid for forcing short, ugly fights where the other player never stabilizes.", (CardThemeColorType)4, "Brawler", "Close-range bully", CardClass.Brawler, FoundationTracks, FoundationTiers, FoundationRarities, 1.07f, 0.04f, 1.1f, 0.05f, 1.03f, 0.015f, 0.96f, 0.015f, 1.04f, 0.02f); } private static IEnumerable BuildArcanistCards() { return BuildMoonFamily(new string[10] { "Hex Sight", "Star Salt", "Rune Loop", "Lantern Math", "Pale Orbit", "Tide Sigil", "Astral Pin", "Gravity Hymn", "Spell Battery", "Oracle Burn" }, "Tilt gravity, tune jump timing, and make fights feel slightly wrong for everyone else.", (CardThemeColorType)3, "Arcanist", "Physics distortion", CardClass.Arcanist, FoundationTracks, FoundationTiers, FoundationRarities, 0.9f, 0.03f, 1.04f, 0.015f, 1.08f, 0.02f, 0.97f, 0.015f); } private static IEnumerable BuildGamblerCards() { return BuildFaultFamily(new string[10] { "Loaded Coin", "Red Thread", "Lucky Brake", "Double or Drift", "Snake Eyes", "Tilt Table", "House Edge", "Last Stake", "Wild Hand", "Jackpot Spine" }, "Each pick pays hard in one direction and invoices you in another.", (CardThemeColorType)3, "Gambler", "Volatile edge", CardClass.Gambler, FoundationTracks, FoundationTiers, FoundationRarities, 1.08f, 0.05f, 1.04f, 0.025f, 0.97f, 0.02f, 1.03f, 0.02f); } private static IEnumerable BuildNeutralTempoFamily() { return BuildTempoFamily(new string[10] { "Hotwire", "Quick Fuse", "Dry Click", "Snap Cycle", "Rush Job", "Loose Timing", "Clean Reset", "Rapid Habit", "Tight Loop", "Overclocked Trigger" }, "Push reload tempo hard and keep the lane warm, but shave off some raw punch.", (CardThemeColorType)2, "Tempo", "Reload tempo", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 0.92f, 0.028f, 1.04f, 0.017f, 0.98f, 0.012f); } private static IEnumerable BuildNeutralBallisticsFamily() { return BuildPrecisionFamily(new string[10] { "Sight Glass", "Long Rail", "Blue Horizon", "Straight Line", "Far Thread", "Ranging Wire", "Quiet Arc", "Low Drop", "Needle Air", "Far Reach" }, "Make your bullets act more honestly at range, but accept the tighter magazine and slower cycling.", (CardThemeColorType)1, "Ballistics", "Flat trajectory", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.06f, 0.02f, 0.88f, 0.08f, 1.02f, 0.018f); } private static IEnumerable BuildNeutralStockpileFamily() { return BuildAmmoFamily(new string[10] { "Cargo Pockets", "Reserve Belt", "Spare Rack", "Loaded Vest", "Deep Pouches", "Reserve Bin", "Full Shelf", "Overflow Pocket", "Pack Mule", "Supply Chain" }, "Carry more answers in the gun and trade a little speed to do it.", (CardThemeColorType)4, "Stockpile", "Ammo banking", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1, 1, 1.04f, 0.02f, 0.99f, 0.01f, 0.98f, 0.012f); } private static IEnumerable BuildNeutralSplitfireFamily() { return BuildVolleyFamily(new string[10] { "Buckbrush", "Crowd Noise", "Scatter Net", "Wide Mouth", "Pellet Storm", "Forked Cone", "Rush Spray", "Dust Fan", "Crowd Control", "Broken Cloud" }, "Make the whole lane unsafe by spending single-target damage on volume and angle.", (CardThemeColorType)0, "Splitfire", "Wide pressure", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1, 1, 0.1f, 0.035f, 0.86f, 0.025f, 1.04f, 0.02f, 1f, 0.01f); } private static IEnumerable BuildNeutralReboundFamily() { return BuildRicochetFamily(new string[10] { "Sidewall", "Corner Kiss", "Mirror Skip", "Rail Tap", "Billiard Habit", "Bank Ledger", "Echo Wall", "Glass Rail", "Ricochet Budget", "Backboard" }, "Force extra decisions from every wall touch, then cash them in over long lanes.", (CardThemeColorType)3, "Rebound", "Wall value", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1, 1, 1.03f, 0.015f, 0.97f, 0.012f, 0.35f, 0.22f); } private static IEnumerable BuildNeutralTractorFamily() { return BuildPullFamily(new string[10] { "Anchor Hook", "Drag Script", "Pull Note", "Mag Line", "Hook Debt", "Tug Current", "Net Pull", "Latch Beam", "Cinch Line", "Harpoon Math" }, "Give up some raw damage to put enemies in bad places instead of merely hurting them.", (CardThemeColorType)3, "Tractor", "Positional control", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1.2f, 0.1f, 1.03f, 0.015f, 0.97f, 0.01f, 1.02f, 0.015f); } private static IEnumerable BuildNeutralPiledriverFamily() { return BuildPushFamily(new string[10] { "Ram Plate", "Shoulder Ram", "Force Heel", "Hard Stop", "Pushline", "Impact Habit", "Hammer Lane", "Crowd Shove", "Brake Slam", "Drive Home" }, "Take space by forcing bodies backward and making every hit reposition the fight.", (CardThemeColorType)4, "Piledriver", "Knockback control", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1.14f, 0.05f, 1.04f, 0.03f, 1.03f, 0.012f, 0.98f, 0.015f); } private static IEnumerable BuildNeutralSoftstepFamily() { return BuildAerialFamily(new string[10] { "Loose Knees", "Feather Soles", "Sky Toes", "Soft Hop", "Thin Air", "Light Touch", "Float Shoes", "Cloud Step", "Late Landing", "Air Debt" }, "Drift longer, land later, and trade bruiser stats for movement answers.", (CardThemeColorType)2, "Softstep", "Aerial drift", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 0.92f, 0.03f, 1.04f, 0.014f, 1.05f, 0.018f, 0.97f, 0.012f, 0.99f, 0.01f); } private static IEnumerable BuildNeutralStonehideFamily() { return BuildTankFamily(new string[10] { "Thick Skin", "Weight Vest", "Hard Core", "Dense Bone", "Lead Spine", "Quiet Mass", "Stone Blood", "Slow Heart", "Heavy Weather", "Buried Feet" }, "Buy a wider error bar by accepting that the map belongs to faster people.", (CardThemeColorType)1, "Stonehide", "Bulk conversion", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.14f, 0.04f, 0.97f, 0.02f, 0.98f, 0.015f); } private static IEnumerable BuildNeutralLiftKitFamily() { return BuildMoonFamily(new string[10] { "Skyhook", "Moon Cable", "Lift Belt", "Updraft", "Cloud Jack", "Gravity Lease", "Rise Kit", "Soft Orbit", "Pale Winch", "Lunar Crane" }, "Lower the burden of gravity and spend the saved time on movement and vertical pressure.", (CardThemeColorType)1, "Lift Kit", "Gravity tuning", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 0.91f, 0.03f, 1.03f, 0.014f, 1.06f, 0.018f, 0.98f, 0.012f); } private static IEnumerable BuildNeutralGuardlineFamily() { return BuildBlockFamily(new string[10] { "Safe Block", "Second Answer", "Steel Nerves", "Guard Habit", "Cold Hands", "Last Defense", "No Panic", "Deny Window", "One More Block", "Closed Door" }, "Turn block timing into a resource advantage and accept that it costs momentum.", (CardThemeColorType)1, "Guardline", "Block economy", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1, 1, 0.98f, 0.02f, 0.98f, 0.015f, 0.99f, 0.01f); } private static IEnumerable BuildNeutralRangefinderFamily() { return BuildRangeFamily(new string[10] { "Long Fuse", "Wide Lane", "Far Window", "Open Sight", "Deep Reach", "Late Drop", "Room Length", "Whole Screen", "End Line", "Back Fence" }, "Stretch travel time and flatten drop so the fight starts earlier than the other player expects.", (CardThemeColorType)1, "Rangefinder", "Long-range shaping", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities, 1.04f, 0.018f, 0.94f, 0.06f, 0.35f, 0.25f, 1.02f, 0.015f); } private static IEnumerable BuildNeutralGlassmakerFamily() { return BuildGlassFamily(new string[10] { "Glass Jaw", "Thin Ice", "Sharp Trade", "Short Life", "Red Margin", "Risk Appetite", "Open Rib", "Paper Armor", "Crack Habit", "Skinny Win" }, "Keep taking trades the hard way: faster kills for a smaller body and worse recovery.", (CardThemeColorType)4, "Glassmaker", "Fragile damage", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.1f, 0.05f, 0.96f, 0.03f, 1.02f, 0.02f); } private static IEnumerable BuildNeutralBastionFamily() { return BuildBastionFamily(new string[10] { "Fort Kit", "Sandbag", "Wall Clock", "Hold Ground", "Cold Shelter", "Guard House", "Brick Lunch", "Heavy Door", "Lockdown", "Stay Put" }, "Convert the round into a stand-up fight and drag the other player into your pace.", (CardThemeColorType)1, "Bastion", "Stand-and-fight", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.12f, 0.03f, 0.99f, 0.022f, 0.98f, 0.015f, 0.98f, 0.012f); } private static IEnumerable BuildNeutralCrossfireFamily() { return BuildCrossfireFamily(new string[10] { "Split Lane", "Side Sweep", "Broad Cut", "Hall Spray", "Wide Wake", "Half Circle", "Fan Room", "Arc Sweep", "Street Broom", "Full Room" }, "Trade honesty for pressure and make dodging require perfect reads instead of good ones.", (CardThemeColorType)0, "Crossfire", "Lateral coverage", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities); } private static IEnumerable BuildNeutralHeatcheckFamily() { return BuildBurstFamily(new string[10] { "Warm Barrel", "Test Shot", "Burn Rate", "Hot Start", "Nerve Shot", "Risk Tap", "Brass Taste", "Short Spark", "Pressure Pull", "Heatcheck" }, "Compress your output into meaner, shorter windows and hope the window is enough.", (CardThemeColorType)4, "Heatcheck", "Burst tempo", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.08f, 0.04f, 0.96f, 0.02f, 1.01f, 0.012f); } private static IEnumerable BuildNeutralTuneUpFamily() { return BuildFlowFamily(new string[10] { "Fresh Oil", "Square Shoulders", "Quiet Work", "Fine Adjust", "Clean Hands", "Smooth Step", "Bench Tune", "Field Service", "Good Bearings", "Ready Shape" }, "Small good decisions stacked together: cleaner movement, cleaner bullets, cleaner follow-through.", (CardThemeColorType)2, "Tune-Up", "General efficiency", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.02f, 0.012f, 1.02f, 0.012f, 1.02f, 0.012f, 1.01f, 0.01f); } private static IEnumerable BuildNeutralFaultlineFamily() { return BuildFaultFamily(new string[10] { "Sharp Budget", "Thin Margin", "Red Figure", "Cracked Plan", "Broken Ceiling", "Hard Swing", "Risk Ladder", "Loose Floor", "Debt Ceiling", "All Inlet" }, "These are strong, but every one of them asks what you are willing to make worse to cash the upside.", (CardThemeColorType)3, "Faultline", "Sharp tradeoff", CardClass.Neutral, FoundationTracks, FoundationTiers, FoundationRarities, 1.07f, 0.05f, 1.03f, 0.025f, 0.98f, 0.02f, 1.02f, 0.02f); } private static IEnumerable BuildNeutralDraglineFamily() { return BuildDraglineFamily(new string[10] { "Catch Loop", "Snare Wire", "Tether Grip", "Lock Reel", "Slip Knot", "Latch Debt", "Tow Thread", "Grip Tax", "Clasp Cable", "Hard Hitch" }, "Half control, half pursuit. The point is not just to hit people, but to keep them answerable.", (CardThemeColorType)3, "Dragline", "Sticky control", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities); } private static IEnumerable BuildNeutralBackspinFamily() { return BuildBackspinFamily(new string[10] { "Backwash", "Reverse English", "Side Current", "Rear Wake", "Off Roll", "Late Spin", "Wrong Turn", "Hook Spin", "Backdraft", "Counter Curl" }, "Take ordinary shots and make their aftermath feel uncomfortable and uneven.", (CardThemeColorType)3, "Backspin", "After-contact weirdness", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities); } private static IEnumerable BuildNeutralOddjobFamily() { return BuildOddjobFamily(new string[10] { "Spare Change", "Late Plan", "Second Wind-Up", "Loose Lease", "Borrowed Time", "Quiet Riot", "Pocket Ladder", "Cheap Thunder", "Useful Mess", "Odd Job" }, "These are the general weird cards: playable, useful, but never loyal to a single lane.", (CardThemeColorType)2, "Oddjob", "Utility spread", CardClass.Neutral, StandardTracks, StandardTiers, StandardRarities); } private static IEnumerable BuildWildcardCards() { yield return Wildcard("Aegis Scope", "Borrow the patience of a tank and the lane control of a marksman.", (Rarity)1, (CardThemeColorType)1, 2, Stats(PositivePercent("HP", 18f), PositivePercent("Bullet Speed", 14f), NegativePercent("Movement Speed", 8f)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.18f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.14f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f)); yield return Wildcard("Riot Step", "Use skirmisher feet to deliver brawler pressure faster than anyone wants.", (Rarity)1, (CardThemeColorType)4, 2, Stats(PositivePercent("Movement Speed", 14f), PositivePercent("Damage", 12f), NegativePercent("Bullet Speed", 8f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.14f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.92f)); yield return Wildcard("Glass Shield", "A defensive shell around a reckless core.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositiveFlat("Additional Blocks", 1), CardStats.Positive("After Block", "2s tempo burst", (SimpleAmount)0), PositivePercent("Damage", 10f), NegativePercent("HP", 10f), NegativePercent("Block Cooldown", 10f)), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.9f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.1f), AbilityCardEffects.BlockTempo(2f, 1.18f, 0.75f)); yield return Wildcard("Spell Buckshot", "Make wizard bullets behave like demolitionist pellets.", (Rarity)1, (CardThemeColorType)3, 3, Stats(PositiveFlat("Projectiles", 2), PositivePercent("Projectile Size", 20f), PositivePercent("Jump", 8f), NegativePercent("Damage", 18f)), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 2), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.2f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.82f)); yield return Wildcard("Loaded Rampart", "Carry extra rounds without giving up the right to stand your ground.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositiveFlat("Ammo", 2), PositivePercent("HP", 10f), CardStats.Positive("After Block", "+1 Ammo", (SimpleAmount)0), NegativePercent("Reload Speed", 8f)), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 2, 1), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.1f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.BlockAmmo(1)); yield return Wildcard("Sky Hook", "A trickster control card that lives in the skirmisher's airspace.", (Rarity)1, (CardThemeColorType)2, 3, Stats(PullStat(1.8f), CardStats.Positive("Airborne", "+30% Damage", (SimpleAmount)0), PositivePercent("Jump", 12f), NegativePercent("Damage", 8f)), CardEffects.ReverseKnockback(1.8f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), AbilityCardEffects.AirborneBonus(1.3f, 1.15f)); yield return Wildcard("Arc Clinch", "Short-fight power wrapped in slightly cursed physics.", (Rarity)1, (CardThemeColorType)3, 4, Stats(PositivePercent("Damage", 16f), PositivePercent("Gravity", 18f), CardStats.Positive("After Taking Damage", "2.5s rush", (SimpleAmount)0), NegativePercent("HP", 8f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.16f), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.82f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.PainRush(2.5f, 1.22f, 0.8f)); yield return Wildcard("Pinball Boots", "You play the room like a trickster, but your feet do most of the work.", (Rarity)1, (CardThemeColorType)3, 4, Stats(PositiveFlat("Reflects", 2), PositivePercent("Movement Speed", 12f), PositivePercent("Projectile Size", 20f), NegativePercent("HP", 8f)), CardEffects.AddGunInt(GunIntStatKey.Reflects, 2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.2f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f)); yield return Wildcard("Moon Hammer", "The floor means less and each hit means more.", (Rarity)2, (CardThemeColorType)3, 4, Stats(PositivePercent("Jump", 16f), PositivePercent("Knockback", 28f), PositivePercent("Projectile Size", 35f), NegativePercent("Reload Speed", 10f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.16f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Knockback, 1.28f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.35f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), AbilityCardEffects.AirborneBonus(1.25f, 1.12f)); yield return Wildcard("Needle Door", "Clean angles with just enough defense to survive the answer.", (Rarity)2, (CardThemeColorType)1, 4, Stats(CardStats.Positive("If Idle 1s", "Next shot +70% Damage", (SimpleAmount)0), PositivePercent("Block Cooldown", 12f), PositivePercent("Bullet Speed", 8f), NegativeFlat("Ammo", 1)), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 0.88f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), AbilityCardEffects.PatientShot(1f, 1.7f, 1.2f)); yield return Wildcard("Fortune Wall", "A gambler card for people who would rather be unlucky slowly.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositivePercent("HP", 14f), PositivePercent("Damage", 10f), CardStats.Positive("After Taking Damage", "2s rush", (SimpleAmount)0), NegativePercent("Movement Speed", 6f)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.16f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.94f), AbilityCardEffects.PainRush(2f, 1.18f, 0.8f)); yield return Wildcard("Dust Prophet", "Read the screen like an arcanist and fill it like a demolitionist.", (Rarity)2, (CardThemeColorType)4, 5, Stats(PositiveFlat("Projectiles", 1), PositivePercent("Projectile Size", 45f), PositivePercent("Gravity", 20f), PositivePercent("Damage", 12f), NegativePercent("Movement Speed", 12f)), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.45f), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.8f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.12f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.88f)); yield return Wildcard("Cheap Trick", "Low commitment, ugly geometry, and enough speed to get out.", (Rarity)1, (CardThemeColorType)3, 2, Stats(PositiveFlat("Reflects", 1), PositivePercent("Movement Speed", 10f), NegativePercent("Damage", 8f)), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f)); yield return Wildcard("Super Ball", "A straight vanilla-style room card: let the walls do half the aiming and the rest of the bullying.", (Rarity)1, (CardThemeColorType)4, 3, Stats(PositiveFlat("Reflects", 5), PositivePercent("Per Bounce Speed", 25f), CardStats.Negative("Per Bounce Damage", "-25%", (SimpleAmount)1), PositiveSeconds("Bullet Life", 2f)), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 5), CardEffects.SetGunFloat(GunFloatStatKey.BounceSpeedMultiplier, 1.25f), CardEffects.SetGunFloat(GunFloatStatKey.BounceDamageMultiplier, 0.75f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 2f)); yield return Wildcard("Bullet Sermon", "A little holiness, a little recoil, a lot of certainty.", (Rarity)2, (CardThemeColorType)0, 5, Stats(CardStats.Positive("Bullets", "Unblockable", (SimpleAmount)0), PositivePercent("Bullet Speed", 16f), PositivePercent("Damage", 10f), NegativeFlat("Ammo", 1), NegativePercent("Reload Speed", 12f)), CardEffects.SetGunBool(GunBoolStatKey.Unblockable, value: true), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.16f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.12f)); yield return Wildcard("Street Ritual", "Close-range conviction with a little spellwork under the hood.", (Rarity)2, (CardThemeColorType)4, 4, Stats(PositivePercent("Damage", 12f), PositivePercent("Jump", 10f), CardStats.Positive("On Hit", "Next shot charged", (SimpleAmount)0), NegativePercent("HP", 8f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.12f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.HitCharge(1.35f, 1.12f)); yield return Wildcard("Ricochet Chapel", "Walls become part of the liturgy.", (Rarity)2, (CardThemeColorType)3, 5, Stats(PositiveFlat("Reflects", 3), PositivePercent("Projectile Size", 30f), PositivePercent("Bullet Speed", 12f), NegativePercent("Damage", 6f)), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 3), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.3f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 2f)); yield return Wildcard("Spare Hex", "More rounds than a mage deserves, but every cast takes a breath.", (Rarity)1, (CardThemeColorType)3, 3, Stats(PositiveFlat("Ammo", 2), PositivePercent("Gravity", 12f), CardStats.Positive("If Idle 1.1s", "Next shot +60% Damage", (SimpleAmount)0), NegativePercent("Reload Speed", 12f)), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 2, 1), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.88f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.12f), AbilityCardEffects.PatientShot(1.1f, 1.6f, 1.1f)); yield return Wildcard("Slip Plate", "A bulwark card that would rather sidestep than stare you down.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositivePercent("Movement Speed", 10f), PositivePercent("HP", 12f), CardStats.Positive("After Block", "1.6s tempo burst", (SimpleAmount)0), NegativePercent("Jump", 8f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.12f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 0.92f), AbilityCardEffects.BlockTempo(1.6f, 1.12f, 0.85f)); yield return Wildcard("Lucky Fortress", "The gambler's version of discipline is still sturdier than most.", (Rarity)2, (CardThemeColorType)1, 5, Stats(PositivePercent("HP", 18f), PositivePercent("Damage", 12f), CardStats.Positive("Heal on Hit", "12% Damage Returned", (SimpleAmount)0), NegativePercent("Reload Speed", 10f)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.18f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.12f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), AbilityCardEffects.Lifesteal(0.12f)); yield return Wildcard("Wall Tax", "Collect damage through cover instead of politely walking around it.", (Rarity)2, (CardThemeColorType)2, 5, Stats(CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Damage Falloff", "Removed", (SimpleAmount)0), PositivePercent("Bullet Speed", 10f), NegativeFlat("Ammo", 1), NegativePercent("Damage", 8f)), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.SetGunFloat(GunFloatStatKey.DamageAfterDistanceMultiplier, 1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f)); yield return Wildcard("Second Geometry", "If the first angle was wrong, make the room find you a better one.", (Rarity)2, (CardThemeColorType)3, 4, Stats(PositiveFlat("Reflects", 1), PositiveFlat("Random Bounces", 1), PositiveFlat("Smart Bounce", 1), CardStats.Positive("Per Bounce", "+15% Damage", (SimpleAmount)0), NegativePercent("Damage", 10f)), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.AddGunInt(GunIntStatKey.RandomBounces, 1), CardEffects.AddGunInt(GunIntStatKey.SmartBounces, 1), CardEffects.SetGunFloat(GunFloatStatKey.BounceSpeedMultiplier, 1.1f), CardEffects.SetGunFloat(GunFloatStatKey.BounceDamageMultiplier, 1.15f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 1.5f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f)); yield return Wildcard("Proxy Saint", "Big rounds that threaten space, not just the point they actually hit.", (Rarity)2, (CardThemeColorType)4, 5, Stats(PositivePercent("Projectile Size", 25f), CardStats.Positive("Near Enemy", "Explodes for 12 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.8m", (SimpleAmount)0), PositivePercent("Bullet Speed", 8f), NegativePercent("Reload Speed", 12f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.25f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 12f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.8f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.12f)); yield return Wildcard("Long Walk Home", "A patient lane card for anyone who wants the next shot to matter from anywhere.", (Rarity)2, (CardThemeColorType)0, 4, Stats(CardStats.Positive("Damage Falloff", "Removed", (SimpleAmount)0), CardStats.Positive("If Idle 1.1s", "Next shot +60% Damage", (SimpleAmount)0), CardStats.Positive("If Idle 1.1s", "Next shot +15% Bullet Speed", (SimpleAmount)0), PositivePercent("Bullet Speed", 10f), NegativePercent("Movement Speed", 8f)), CardEffects.SetGunFloat(GunFloatStatKey.DamageAfterDistanceMultiplier, 1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f), AbilityCardEffects.PatientShot(1.1f, 1.6f, 1.15f)); yield return Wildcard("Last Carnival", "If the card makes sense immediately, it failed.", (Rarity)2, (CardThemeColorType)3, 5, Stats(PositivePercent("Movement Speed", 12f), PositiveFlat("Projectiles", 1), PositiveFlat("Reflects", 1), CardStats.Positive("Chain Shots", "Up to 4 stacks", (SimpleAmount)0), NegativePercent("Damage", 10f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.12f), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f), AbilityCardEffects.ChainShot(0.8f, 1.15f, 1.12f, 4)); } private static IEnumerable BuildTrollCards() { yield return Troll("Brick Shoes", "Become a monument to bad footwork.", (Rarity)0, (CardThemeColorType)4, 1, Stats(PositivePercent("HP", 30f), NegativePercent("Movement Speed", 35f), NegativePercent("Jump", 25f)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.3f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.65f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 0.75f)); yield return Troll("Dinner Bell", "Hit harder and announce it slowly.", (Rarity)0, (CardThemeColorType)4, 1, Stats(PositivePercent("Damage", 28f), NegativePercent("Reload Speed", 24f), NegativePercent("Bullet Speed", 18f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.28f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.24f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.82f)); yield return Troll("Wrong Ammo", "More shells, less reason to fire them.", (Rarity)0, (CardThemeColorType)0, 1, Stats(PositiveFlat("Ammo", 3), NegativePercent("Damage", 22f), NegativePercent("Bullet Speed", 12f)), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 3, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.78f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.88f)); yield return Troll("Big Target", "Everyone appreciates your honesty.", (Rarity)0, (CardThemeColorType)1, 1, Stats(PositivePercent("HP", 18f), NegativePercent("Movement Speed", 18f), NegativePercent("Block Cooldown", 18f)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.18f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.82f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.18f)); yield return Troll("Loud Jacket", "You dressed like a problem and now you are one.", (Rarity)0, (CardThemeColorType)3, 2, Stats(PositivePercent("Movement Speed", 10f), NegativePercent("HP", 22f), NegativePercent("Damage", 12f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.78f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.88f)); yield return Troll("Wet Powder", "The concept is strong. The execution is damp.", (Rarity)0, (CardThemeColorType)4, 2, Stats(PositiveFlat("Projectiles", 1), NegativePercent("Damage", 28f), NegativePercent("Reload Speed", 18f)), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.72f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.18f)); yield return Troll("Panic Jump", "Great verticals, terrible life choices.", (Rarity)0, (CardThemeColorType)2, 2, Stats(PositivePercent("Jump", 35f), NegativePercent("HP", 18f), NegativePercent("Bullet Speed", 10f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.35f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.82f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.9f)); yield return Troll("Tin Halo", "Technically magical. Emotionally disappointing.", (Rarity)0, (CardThemeColorType)3, 2, Stats(PositivePercent("Gravity", 18f), NegativePercent("Damage", 16f), NegativePercent("Ammo", 1f)), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.82f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.84f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1)); yield return Troll("Bad Math", "The numbers work out if nobody checks.", (Rarity)1, (CardThemeColorType)3, 3, Stats(PositivePercent("Damage", 24f), PositivePercent("Movement Speed", 12f), NegativePercent("HP", 24f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.24f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.12f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.76f)); yield return Troll("Dead Stop", "Incredible if the fight politely walks into you.", (Rarity)1, (CardThemeColorType)4, 3, Stats(PositivePercent("Damage", 22f), NegativePercent("Movement Speed", 28f), NegativePercent("Jump", 18f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.22f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.72f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 0.82f)); yield return Troll("Broken Scope", "Cleaner aim, worse everything else.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositivePercent("Bullet Gravity", 100f), NegativePercent("Reload Speed", 18f), NegativePercent("Movement Speed", 12f)), CardEffects.SetGunFloat(GunFloatStatKey.Gravity, 0f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.18f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.88f)); yield return Troll("Friendly Gravity", "The floor helps you, but never for free.", (Rarity)1, (CardThemeColorType)1, 3, Stats(PositivePercent("Gravity", 28f), NegativePercent("HP", 20f), NegativePercent("Damage", 10f)), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.72f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.8f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f)); yield return Troll("Rent Due", "You got the upside; the landlord got the rest.", (Rarity)1, (CardThemeColorType)4, 4, Stats(PositivePercent("Damage", 30f), NegativePercent("HP", 26f), NegativePercent("Reload Speed", 16f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.3f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.74f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.16f)); yield return Troll("Snail Trail", "You move like a plan that took too long.", (Rarity)1, (CardThemeColorType)1, 4, Stats(PositiveFlat("Additional Blocks", 1), NegativePercent("Movement Speed", 32f), NegativePercent("Bullet Speed", 12f)), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.68f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.88f)); yield return Troll("Paper Cannon", "A respectable plan held together by feelings.", (Rarity)1, (CardThemeColorType)0, 4, Stats(PositiveFlat("Projectiles", 2), NegativePercent("HP", 28f), NegativePercent("Damage", 18f)), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 2), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.72f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.82f)); yield return Troll("Cursed Magazine", "Too many bullets for too little confidence.", (Rarity)2, (CardThemeColorType)3, 5, Stats(PositiveFlat("Ammo", 4), NegativePercent("Reload Speed", 24f), NegativePercent("Damage", 16f)), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 4, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.24f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.84f)); yield return Troll("Late Reload", "The worst part is how often it matters.", (Rarity)2, (CardThemeColorType)4, 5, Stats(PositivePercent("Damage", 24f), NegativePercent("Reload Speed", 35f), NegativePercent("Movement Speed", 10f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.24f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.35f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.9f)); yield return Troll("Overripe", "More float than plan.", (Rarity)2, (CardThemeColorType)1, 5, Stats(PositivePercent("Jump", 24f), PositivePercent("Gravity", 30f), NegativePercent("HP", 24f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.24f), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.7f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.76f)); yield return Troll("Open Casket", "Huge damage, tiny biography.", (Rarity)2, (CardThemeColorType)4, 5, Stats(PositivePercent("Damage", 40f), NegativePercent("HP", 32f), NegativePercent("Ammo", 1f)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.4f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.68f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1)); yield return Troll("Walk It Off", "You can, but you probably shouldn't.", (Rarity)2, (CardThemeColorType)4, 5, Stats(PositivePercent("Movement Speed", 18f), PositivePercent("Knockback", 18f), NegativePercent("HP", 26f)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.18f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Knockback, 1.18f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.74f)); } private static IEnumerable BuildTempoFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float reloadBase, float reloadStep, float speedBase, float speedStep, float damageBase, float damageStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = ClampMultiplier(reloadBase - reloadStep * (float)i, 0.55f); float multiplier2 = speedBase + speedStep * (float)i; float multiplier3 = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.65f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(DownIsGoodStat("Reload Speed", multiplier), UpStat("Bullet Speed", multiplier2), UpStat("Damage", multiplier3)), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier2), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier3)); } } private static IEnumerable BuildPrecisionFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float speedBase, float speedStep, float gravityBase, float gravityStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = speedBase + speedStep * (float)i; float num = ClampMultiplier(gravityBase - gravityStep * (float)i, 0f); float multiplier2 = SoftenPenalty(reloadBase + reloadStep * (float)i); int num2 = ((i >= 8) ? (-1) : 0); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, (num2 == 0) ? Stats(DownIsGoodStat("Bullet Gravity", num), UpStat("Bullet Speed", multiplier), DownIsGoodStat("Reload Speed", multiplier2)) : Stats(DownIsGoodStat("Bullet Gravity", num), UpStat("Bullet Speed", multiplier), NegativeFlat("Ammo", -num2), DownIsGoodStat("Reload Speed", multiplier2)), (num <= 0.05f) ? CardEffects.SetGunFloat(GunFloatStatKey.Gravity, 0f) : CardEffects.MultiplyGunFloat(GunFloatStatKey.Gravity, num), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), (num2 == 0) ? CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier2) : CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, num2, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier2)); } } private static IEnumerable BuildTankFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float healthBase, float healthStep, float speedBase, float speedStep, float jumpBase, float jumpStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = healthBase + healthStep * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(speedBase - speedStep * (float)i, 0.6f)); float multiplier3 = SoftenPenalty(ClampMultiplier(jumpBase - jumpStep * (float)i, 0.65f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("HP", multiplier), UpStat("Movement Speed", multiplier2), UpStat("Jump", multiplier3)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, multiplier3)); } } private static IEnumerable BuildBastionFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float healthBase, float healthStep, float blockBase, float blockStep, float speedBase, float speedStep, float bulletSpeedBase, float bulletSpeedStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = healthBase + healthStep * (float)i; float multiplier2 = ClampMultiplier(blockBase - blockStep * (float)i, 0.7f); float multiplier3 = SoftenPenalty(ClampMultiplier(speedBase - speedStep * (float)i, 0.6f)); float multiplier4 = SoftenPenalty(ClampMultiplier(bulletSpeedBase - bulletSpeedStep * (float)i, 0.7f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("HP", multiplier), DownIsGoodStat("Block Cooldown", multiplier2), UpStat("Movement Speed", multiplier3), UpStat("Bullet Speed", multiplier4)), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier4)); } } private static IEnumerable BuildAerialFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float gravityBase, float gravityStep, float speedBase, float speedStep, float jumpBase, float jumpStep, float healthBase, float healthStep, float bulletSpeedBase, float bulletSpeedStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = ClampMultiplier(gravityBase - gravityStep * (float)i, 0.55f); float multiplier2 = speedBase + speedStep * (float)i; float multiplier3 = jumpBase + jumpStep * (float)i; float multiplier4 = SoftenPenalty(ClampMultiplier(healthBase - healthStep * (float)i, 0.7f)); float multiplier5 = SoftenPenalty(ClampMultiplier(bulletSpeedBase - bulletSpeedStep * (float)i, 0.8f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(DownIsGoodStat("Gravity", multiplier), UpStat("Movement Speed", multiplier2), UpStat("Jump", multiplier3), UpStat("HP", multiplier4), UpStat("Bullet Speed", multiplier5)), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, multiplier3), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier4), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier5)); } } private static IEnumerable BuildRicochetFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, int reflectsBase, int reflectsStep, float speedBase, float speedStep, float damageBase, float damageStep, float lifeSecondsBase, float lifeSecondsStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { int amount = reflectsBase + reflectsStep * (i / 2); float multiplier = speedBase + speedStep * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.7f)); float num = lifeSecondsBase + lifeSecondsStep * (float)i; yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositiveFlat("Reflects", amount), UpStat("Bullet Speed", multiplier), NegativePercent("Damage", PercentLoss(multiplier2)), PositiveSeconds("Bullet Life", num)), CardEffects.AddGunInt(GunIntStatKey.Reflects, amount), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 4f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, num)); } } private static IEnumerable BuildPullFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float pullBase, float pullStep, float speedBase, float speedStep, float damageBase, float damageStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float magnitude = pullBase + pullStep * (float)i; float multiplier = speedBase + speedStep * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.75f)); float multiplier3 = SoftenPenalty(reloadBase + reloadStep * (float)i); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PullStat(magnitude), UpStat("Bullet Speed", multiplier), UpStat("Damage", multiplier2), DownIsGoodStat("Reload Speed", multiplier3)), CardEffects.ReverseKnockback(magnitude), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier3)); } } private static IEnumerable BuildPushFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float knockbackBase, float knockbackStep, float damageBase, float damageStep, float speedBase, float speedStep, float bulletSpeedBase, float bulletSpeedStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = knockbackBase + knockbackStep * (float)i; float multiplier2 = damageBase + damageStep * (float)i; float multiplier3 = speedBase + speedStep * (float)i; float multiplier4 = SoftenPenalty(ClampMultiplier(bulletSpeedBase - bulletSpeedStep * (float)i, 0.75f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Knockback", multiplier), UpStat("Damage", multiplier2), UpStat("Movement Speed", multiplier3), UpStat("Bullet Speed", multiplier4)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Knockback, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier4)); } } private static IEnumerable BuildVolleyFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, int projectileBase, int projectileStep, float spreadBase, float spreadStep, float damageBase, float damageStep, float reloadBase, float reloadStep, float speedBase, float speedStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { int amount = projectileBase + projectileStep * (i / 2); float num = spreadBase + spreadStep * (float)i; float multiplier = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.55f)); float multiplier2 = SoftenPenalty(reloadBase + reloadStep * (float)i); float multiplier3 = SoftenPenalty(ClampMultiplier(speedBase - speedStep * (float)i, 0.78f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositiveFlat("Projectiles", amount), PositivePercent("Spread", num * 100f), NegativePercent("Damage", PercentLoss(multiplier)), NegativePercent("Reload Speed", PercentGain(multiplier2)), UpStat("Movement Speed", multiplier3)), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, amount), CardEffects.AddGunFloat(GunFloatStatKey.Spread, num), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3)); } } private static IEnumerable BuildBruiserFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float damageBase, float damageStep, float knockbackBase, float knockbackStep, float moveBase, float moveStep, float bulletSpeedBase, float bulletSpeedStep, float healthBase, float healthStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = damageBase + damageStep * (float)i; float multiplier2 = knockbackBase + knockbackStep * (float)i; float multiplier3 = moveBase + moveStep * (float)i; float multiplier4 = SoftenPenalty(ClampMultiplier(bulletSpeedBase - bulletSpeedStep * (float)i, 0.75f)); float multiplier5 = healthBase + healthStep * (float)i; yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Damage", multiplier), UpStat("Knockback", multiplier2), UpStat("Movement Speed", multiplier3), UpStat("Bullet Speed", multiplier4), UpStat("HP", multiplier5)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Knockback, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier4), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier5)); } } private static IEnumerable BuildAmmoFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, int ammoBase, int ammoStep, float reloadBase, float reloadStep, float damageBase, float damageStep, float moveBase, float moveStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { int amount = ammoBase + ammoStep * (i / 2); float multiplier = SoftenPenalty(reloadBase + reloadStep * (float)i); float multiplier2 = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.78f)); float multiplier3 = SoftenPenalty(ClampMultiplier(moveBase - moveStep * (float)i, 0.8f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositiveFlat("Ammo", amount), NegativePercent("Reload Speed", PercentGain(multiplier)), UpStat("Damage", multiplier2), UpStat("Movement Speed", multiplier3)), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, amount, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3)); } } private static IEnumerable BuildBlockFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, int blocksBase, int blocksStep, float cooldownBase, float cooldownStep, float moveBase, float moveStep, float damageBase, float damageStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { int amount = blocksBase + blocksStep * (i / 3); float multiplier = ClampMultiplier(cooldownBase - cooldownStep * (float)i, 0.72f); float multiplier2 = SoftenPenalty(ClampMultiplier(moveBase - moveStep * (float)i, 0.76f)); float multiplier3 = SoftenPenalty(ClampMultiplier(damageBase - damageStep * (float)i, 0.82f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositiveFlat("Additional Blocks", amount), DownIsGoodStat("Block Cooldown", multiplier), UpStat("Movement Speed", multiplier2), UpStat("Damage", multiplier3)), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, amount), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier2), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier3)); } } private static IEnumerable BuildRangeFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float speedBase, float speedStep, float gravityBase, float gravityStep, float lifeBase, float lifeStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = speedBase + speedStep * (float)i; float multiplier2 = ClampMultiplier(gravityBase - gravityStep * (float)i, 0.1f); float num = lifeBase + lifeStep * (float)i; float multiplier3 = SoftenPenalty(reloadBase + reloadStep * (float)i); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Bullet Speed", multiplier), DownIsGoodStat("Bullet Gravity", multiplier2), PositiveSeconds("Bullet Life", num), NegativePercent("Reload Speed", PercentGain(multiplier3))), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Gravity, multiplier2), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 4f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, num), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier3)); } } private static IEnumerable BuildGlassFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float damageBase, float damageStep, float healthBase, float healthStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = damageBase + damageStep * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(healthBase - healthStep * (float)i, 0.68f)); float multiplier3 = SoftenPenalty(reloadBase + reloadStep * (float)i); int num = ((i >= 8) ? (-1) : 0); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, (num == 0) ? Stats(UpStat("Damage", multiplier), UpStat("HP", multiplier2), NegativePercent("Reload Speed", PercentGain(multiplier3))) : Stats(UpStat("Damage", multiplier), UpStat("HP", multiplier2), NegativePercent("Reload Speed", PercentGain(multiplier3)), NegativeFlat("Ammo", 1)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier2), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier3), (num == 0) ? null : CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, num, 1)); } } private static IEnumerable BuildMoonFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float gravityBase, float gravityStep, float moveBase, float moveStep, float jumpBase, float jumpStep, float healthBase, float healthStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = ClampMultiplier(gravityBase - gravityStep * (float)i, 0.55f); float multiplier2 = moveBase + moveStep * (float)i; float multiplier3 = jumpBase + jumpStep * (float)i; float multiplier4 = SoftenPenalty(ClampMultiplier(healthBase - healthStep * (float)i, 0.72f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(DownIsGoodStat("Gravity", multiplier), UpStat("Movement Speed", multiplier2), UpStat("Jump", multiplier3), UpStat("HP", multiplier4)), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, multiplier3), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier4)); } } private static IEnumerable BuildBurstFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float damageBase, float damageStep, float reloadBase, float reloadStep, float moveBase, float moveStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = damageBase + damageStep * (float)i; float multiplier2 = ClampMultiplier(reloadBase - reloadStep * (float)i, 0.66f); float multiplier3 = moveBase + moveStep * (float)i; int num = ((i >= 6) ? (-1) : 0); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, (num == 0) ? Stats(UpStat("Damage", multiplier), DownIsGoodStat("Reload Speed", multiplier2), UpStat("Movement Speed", multiplier3)) : Stats(UpStat("Damage", multiplier), DownIsGoodStat("Reload Speed", multiplier2), UpStat("Movement Speed", multiplier3), NegativeFlat("Ammo", 1)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3), (num == 0) ? null : CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, num, 1)); } } private static IEnumerable BuildFlowFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float damageBase, float damageStep, float speedBase, float speedStep, float moveBase, float moveStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = damageBase + damageStep * (float)i; float multiplier2 = speedBase + speedStep * (float)i; float multiplier3 = moveBase + moveStep * (float)i; float multiplier4 = ClampMultiplier(reloadBase - reloadStep * (float)i, 0.82f); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Damage", multiplier), UpStat("Bullet Speed", multiplier2), UpStat("Movement Speed", multiplier3), DownIsGoodStat("Reload Speed", multiplier4)), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier2), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier3), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier4)); } } private static IEnumerable BuildFaultFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities, float damageBase, float damageStep, float moveBase, float moveStep, float healthBase, float healthStep, float reloadBase, float reloadStep) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = damageBase + damageStep * (float)i; float multiplier2 = moveBase + moveStep * (float)i; float multiplier3 = SoftenPenalty(ClampMultiplier(healthBase - healthStep * (float)i, 0.72f)); float multiplier4 = SoftenPenalty(reloadBase + reloadStep * (float)i); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Damage", multiplier), UpStat("Movement Speed", multiplier2), UpStat("HP", multiplier3), NegativePercent("Reload Speed", PercentGain(multiplier4))), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier2), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier3), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier4)); } } private static IEnumerable BuildCrossfireFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float num = 0.14f + 0.02f * (float)i; float multiplier = 1.02f + 0.015f * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(0.94f - 0.015f * (float)i, 0.72f)); float multiplier3 = 1.04f + 0.03f * (float)i; yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositivePercent("Spread", num * 100f), UpStat("Bullet Speed", multiplier), NegativePercent("Damage", PercentLoss(multiplier2)), UpStat("Knockback", multiplier3)), CardEffects.AddGunFloat(GunFloatStatKey.Spread, num), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2), CardEffects.MultiplyGunFloat(GunFloatStatKey.Knockback, multiplier3)); } } private static IEnumerable BuildDraglineFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float magnitude = 1.15f + 0.08f * (float)i; float multiplier = 1.02f + 0.012f * (float)i; float multiplier2 = 0.98f - 0.01f * (float)i; float multiplier3 = SoftenPenalty(ClampMultiplier(0.98f - 0.012f * (float)i, 0.78f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PullStat(magnitude), UpStat("Movement Speed", multiplier), DownIsGoodStat("Reload Speed", multiplier2), UpStat("HP", multiplier3)), CardEffects.ReverseKnockback(magnitude), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, multiplier2), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, multiplier3)); } } private static IEnumerable BuildBackspinFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { int amount = 1 + i / 3; float magnitude = 1.1f + 0.06f * (float)i; float multiplier = 1.01f + 0.015f * (float)i; float multiplier2 = SoftenPenalty(ClampMultiplier(0.98f - 0.012f * (float)i, 0.78f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(PositiveFlat("Reflects", amount), PullStat(magnitude), UpStat("Bullet Speed", multiplier), UpStat("Damage", multiplier2)), CardEffects.AddGunInt(GunIntStatKey.Reflects, amount), CardEffects.ReverseKnockback(magnitude), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier2)); } } private static IEnumerable BuildOddjobFamily(string[] titles, string description, CardThemeColorType theme, string family, string role, CardClass cardClass, CardTrack[] tracks, int[] tiers, Rarity[] rarities) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < titles.Length; i++) { float multiplier = 1.02f + 0.01f * (float)i; float multiplier2 = 1.03f + 0.012f * (float)i; float multiplier3 = 1.02f + 0.01f * (float)i; float multiplier4 = SoftenPenalty(ClampMultiplier(0.98f - 0.008f * (float)i, 0.84f)); yield return ManagedCard(titles[i], description, rarities[i], theme, cardClass, tracks[i], tiers[i], family, role, Stats(UpStat("Movement Speed", multiplier), UpStat("Jump", multiplier2), UpStat("Bullet Speed", multiplier3), UpStat("Damage", multiplier4)), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, multiplier), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, multiplier2), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, multiplier3), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, multiplier4)); } } private static TemplateCardDefinition ManagedCard(string title, string description, Rarity rarity, CardThemeColorType theme, CardClass cardClass, CardTrack track, int tier, string family, string role, CardInfoStat[] stats, params CardEffectModule[] effects) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return new TemplateCardDefinition(new CardSpec(title, description, rarity, theme, stats, cardClass, track, tier, family, role, false, null, null), FilterEffects(effects)); } private static TemplateCardDefinition Wildcard(string title, string description, Rarity rarity, CardThemeColorType theme, int tier, CardInfoStat[] stats, params CardEffectModule[] effects) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return new TemplateCardDefinition(new CardSpec(title, description, rarity, theme, stats, CardClass.Wildcard, CardTrack.Wildcard, tier, "Wildcard", "Bridge pick", false, null, null), FilterEffects(effects)); } private static TemplateCardDefinition Troll(string title, string description, Rarity rarity, CardThemeColorType theme, int tier, CardInfoStat[] stats, params CardEffectModule[] effects) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return new TemplateCardDefinition(new CardSpec(title, description, rarity, theme, stats, CardClass.Troll, CardTrack.Troll, tier, "Troll", "High-volatility", false, null, null), FilterEffects(effects)); } private static CardEffectModule[] FilterEffects(CardEffectModule[] effects) { List list = new List(effects.Length); for (int i = 0; i < effects.Length; i++) { if (effects[i] != null) { list.Add(effects[i]); } } return list.ToArray(); } private static CardInfoStat[] Stats(params CardInfoStat[] stats) { return stats; } private static CardInfoStat UpStat(string label, float multiplier) { if (multiplier >= 1f) { return PositivePercent(label, PercentGain(multiplier)); } return NegativePercent(label, PercentLoss(multiplier)); } private static CardInfoStat DownIsGoodStat(string label, float multiplier) { if (multiplier <= 1f) { return PositivePercent(label, PercentLoss(multiplier)); } return NegativePercent(label, PercentGain(multiplier)); } private static CardInfoStat PositivePercent(string label, float percent) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) return CardStats.Positive(label, FormatPercent(percent), PositiveSimple(percent)); } private static CardInfoStat NegativePercent(string label, float percent) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) return CardStats.Negative(label, FormatNegativePercent(percent), NegativeSimple(percent)); } private static CardInfoStat PositiveFlat(string label, int amount) { return CardStats.Positive(label, (amount > 0) ? $"+{amount}" : amount.ToString(), (SimpleAmount)0); } private static CardInfoStat NegativeFlat(string label, int amount) { return CardStats.Negative(label, $"-{Math.Abs(amount)}", (SimpleAmount)0); } private static CardInfoStat PositiveSeconds(string label, float seconds) { return CardStats.Positive(label, $"+{Math.Round(seconds, 1):0.0}s", (SimpleAmount)3); } private static CardInfoStat PullStat(float magnitude) { return CardStats.Positive("Knockback", $"-{Math.Round(magnitude, 1):0.0}x", (SimpleAmount)0); } private static float ClampMultiplier(float value, float minimum) { return Math.Max(minimum, value); } private static float SoftenPenalty(float multiplier, float reduction = 0.3f) { if (Math.Abs(multiplier - 1f) < 0.001f) { return 1f; } if (multiplier < 1f) { return 1f - (1f - multiplier) * (1f - reduction); } return 1f + (multiplier - 1f) * (1f - reduction); } private static float PercentGain(float multiplier) { return (multiplier - 1f) * 100f; } private static float PercentLoss(float multiplier) { return (1f - multiplier) * 100f; } private static string FormatPercent(float percent) { return $"+{Math.Round(percent):0}%"; } private static string FormatNegativePercent(float percent) { return $"-{Math.Round(percent):0}%"; } private static SimpleAmount PositiveSimple(float percent) { if (!(percent >= 40f)) { if (!(percent >= 20f)) { _ = 8f; return (SimpleAmount)3; } return (SimpleAmount)4; } return (SimpleAmount)4; } private static SimpleAmount NegativeSimple(float percent) { if (!(percent >= 25f)) { if (!(percent >= 10f)) { return (SimpleAmount)2; } return (SimpleAmount)1; } return (SimpleAmount)5; } static PrototypeTemplateCatalog() { TemplateCardDefinition[] array = new TemplateCardDefinition[111]; array[0] = new TemplateCardDefinition(new CardSpec("Quickdraw", "Cycle shots faster and flatter, but trim the damage a little.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("Reload Speed", "+15%", (SimpleAmount)3), CardStats.Positive("Bullet Speed", "+20%", (SimpleAmount)3), CardStats.Negative("Damage", "-10%", (SimpleAmount)2) }, CardClass.Marksman, CardTrack.Starter, 1, "Marksman", "Tempo opener", false, null, null), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.85f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.2f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f)); CardInfoStat[] stats = new CardInfoStat[6] { CardStats.Positive("Damage", "+95%", (SimpleAmount)4), CardStats.Positive("Reload Speed", "+25%", (SimpleAmount)4), CardStats.Positive("After Full Reload", "Next shot ignores walls", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +28% Projectile Size", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot Unblockable", (SimpleAmount)0), CardStats.Negative("Ammo", "Set to 1", (SimpleAmount)0) }; string[] draftTags = new string[6] { "reload", "weapon mode", "chamber", "wall", "precision", "marksman" }; array[1] = new TemplateCardDefinition(new CardSpec("One in the Chamber", "Turn the whole gun into a one-shell weapon mode. Every reload becomes a fresh chamber for a round that ignores cover and lands like it means it.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats, CardClass.Marksman, CardTrack.Signature, 4, "Marksman", "All-in duelist", allowMultiple: false, null, null, draftTags), CardEffects.SetGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.95f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.75f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), AbilityCardEffects.ReloadMode(1, 1.08f, 1.08f, 1.28f, ignoreWalls: true, 0, 0f)); CardInfoStat[] stats2 = new CardInfoStat[7] { CardStats.Positive("Bullet Gravity", "Removed", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+20%", (SimpleAmount)3), CardStats.Positive("First Bullet", "+28% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+12% Projectile Size", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Reload Speed", "-10%", (SimpleAmount)2) }; draftTags = new string[5] { "chamber", "precision", "range", "reload", "marksman" }; array[2] = new TemplateCardDefinition(new CardSpec("Deadeye Rig", "Turn your weapon into a cleaner long-range tool, then make the first chamber in every magazine feel like the shot you were actually loading for.", (Rarity)1, (CardThemeColorType)0, (CardInfoStat[])(object)stats2, CardClass.Marksman, CardTrack.Signature, 4, "Marksman", "Build-defining marksman", allowMultiple: false, null, null, draftTags), CardEffects.SetGunFloat(GunFloatStatKey.Gravity, 0f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.2f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), AbilityCardEffects.ChamberedRound(1.28f, 1.12f, 1.12f, unblockable: false)); CardInfoStat[] stats3 = new CardInfoStat[4] { CardStats.Positive("First Bullet", "+28% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+10% Projectile Size", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[4] { "reload", "chamber", "precision", "marksman" }; array[3] = new TemplateCardDefinition(new CardSpec("Sighted Chamber", "A lower-end marksman bridge. The first round in each magazine comes out cleaner and heavier, which is enough to make pacing the mag matter.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)stats3, CardClass.Marksman, CardTrack.Core, 2, "Marksman", "Chamber bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), AbilityCardEffects.ChamberedRound(1.28f, 1.12f, 1.1f, unblockable: false)); CardInfoStat[] stats4 = new CardInfoStat[5] { CardStats.Positive("Orbit Ward", "1 ward, 4.6m scan", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 0.9s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("HP", "-5%", (SimpleAmount)1) }; draftTags = new string[5] { "summon", "orbit", "mark", "precision", "marksman" }; array[4] = new TemplateCardDefinition(new CardSpec("Spotter Mote", "A low-tier summon bridge for marksman runs. Let a small watcher paint targets and keep your lanes clean without needing every setup to come directly from the trigger.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)stats4, CardClass.Marksman, CardTrack.Core, 2, "Marksman", "Spotter bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.95f), AbilityCardEffects.OrbitWard(1, 4.6f, 2.4f, 1, 0.9f, 1f, 1.1f, 1.04f)); array[5] = new TemplateCardDefinition(new CardSpec("Backup Shield", "Carry one more answer, but cycle shields a little slower.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("Additional Blocks", "+1", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+15%", (SimpleAmount)3), CardStats.Negative("Movement Speed", "-5%", (SimpleAmount)1) }, CardClass.Bulwark, CardTrack.Starter, 1, "Bulwark", "Mistake buffer", false, null, null), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.15f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.95f)); CardInfoStat[] stats5 = new CardInfoStat[6] { CardStats.Positive("HP", "+30%", (SimpleAmount)4), CardStats.Positive("Grounded", "+14% Damage", (SimpleAmount)0), CardStats.Positive("Grounded", "+18% Reload Speed", (SimpleAmount)0), CardStats.Positive("Grounded", "-6% Move Speed", (SimpleAmount)0), CardStats.Negative("Movement Speed", "-12%", (SimpleAmount)2), CardStats.Negative("Jump", "-8%", (SimpleAmount)2) }; draftTags = new string[4] { "ground", "stance", "survival", "bulwark" }; array[6] = new TemplateCardDefinition(new CardSpec("Ballast", "Become harder to move and harder to kill, then turn planted feet into a real stance instead of just a slower version of normal.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats5, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Bruiser baseline", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.3f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.88f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 0.92f), AbilityCardEffects.GroundedStance(1.14f, 0.94f, 0.82f)); CardInfoStat[] stats6 = new CardInfoStat[3] { CardStats.Positive("After Block", "Next shot +22% Damage", (SimpleAmount)0), CardStats.Positive("After Block", "+1 Ammo", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "block", "tempo", "precision", "reload" }; array[7] = new TemplateCardDefinition(new CardSpec("Pocket Aegis", "Blocks should change the next decision, not just erase the last mistake.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats6, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Pocket punish", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.08f), AbilityCardEffects.BlockAmmo(1), AbilityCardEffects.CounterCharge(1.22f, 1.05f)); CardInfoStat[] stats7 = new CardInfoStat[5] { CardStats.Positive("After Block", "2.3m field for 2.4s", (SimpleAmount)0), CardStats.Positive("Field", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +14% Reload Time", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +10% Block Cooldown", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+10%", (SimpleAmount)3) }; draftTags = new string[5] { "block", "hazard", "zone", "control", "bulwark" }; array[8] = new TemplateCardDefinition(new CardSpec("Ward Post", "A lower-tier field card. A good block should leave behind a little territory, not just a number on the next shot.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats7, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Field bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.1f), AbilityCardEffects.BlockZone(2.4f, 2.3f, 0.45f, 1, 1f, 1.14f, 1.1f)); CardInfoStat[] stats8 = new CardInfoStat[4] { CardStats.Positive("Perfect Block", "Next shot +28% Damage", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +12% Projectile Size", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+6%", (SimpleAmount)3) }; draftTags = new string[4] { "block", "parry", "precision", "bulwark" }; array[9] = new TemplateCardDefinition(new CardSpec("Seal Return", "A small real parry bridge. Catch the block on time and the answer comes back quicker and wider instead of just safer.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats8, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Parry bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.06f), AbilityCardEffects.PerfectParry(0.32f, 1.28f, 1.12f, 1.12f, unblockable: false)); CardInfoStat[] stats9 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Leave a surface shield on hit", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+8%", (SimpleAmount)3), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("Block Cooldown", "+4%", (SimpleAmount)1) }; draftTags = new string[6] { "shield", "block", "projectile", "cover", "bulwark", "summon" }; array[10] = new TemplateCardDefinition(new CardSpec("Shield Brief", "A low-tier shield port. The shot leaves a little surface shield on first contact, which turns lane checks into actual cover instead of one more invisible stat buff.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats9, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Shield bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.04f), VanillaCardEffects.AddSurfaceShieldShot("block", "summon", "weapon_mode")); CardInfoStat[] stats10 = new CardInfoStat[6] { CardStats.Positive("Bullets", "Carry a shield-charge hit", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+14%", (SimpleAmount)4), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Additional Blocks", "+1", (SimpleAmount)0), CardStats.Negative("Damage", "-6%", (SimpleAmount)1), CardStats.Negative("Block Cooldown", "+8%", (SimpleAmount)3) }; draftTags = new string[6] { "shield", "block", "projectile", "bulwark", "control", "weapon mode" }; array[11] = new TemplateCardDefinition(new CardSpec("Charge Docket", "A real bulwark payoff port. The round carries a shield-charge collision rule so your firing lane starts behaving like part of your block kit.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)stats10, CardClass.Bulwark, CardTrack.Payoff, 3, "Bulwark", "Shield collision payoff", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.14f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.08f), VanillaCardEffects.AddShieldChargeShot("block", "summon", "weapon_mode")); array[12] = new TemplateCardDefinition(new CardSpec("Stronghold", "Slow the game down around you and dare people to crack you open.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("HP", "+25%", (SimpleAmount)3), CardStats.Positive("Block Cooldown", "-15%", (SimpleAmount)2), CardStats.Negative("Movement Speed", "-15%", (SimpleAmount)2), CardStats.Negative("Bullet Speed", "-10%", (SimpleAmount)2) }, CardClass.Bulwark, CardTrack.Payoff, 3, "Bulwark", "Anchor tank", false, null, null), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.25f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 0.85f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.85f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.9f)); array[13] = new TemplateCardDefinition(new CardSpec("Light Frame", "Win the position fight, but lose some margin for error.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("Movement Speed", "+20%", (SimpleAmount)3), CardStats.Positive("Jump", "+15%", (SimpleAmount)3), CardStats.Negative("HP", "-15%", (SimpleAmount)2) }, CardClass.Skirmisher, CardTrack.Starter, 1, "Skirmisher", "Repositioning", false, null, null), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.2f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.15f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.85f)); array[14] = new TemplateCardDefinition(new CardSpec("Moon Step", "Stay airborne longer and drift through space, but pay for it in health and shot speed.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Gravity", "-35%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+10%", (SimpleAmount)3), CardStats.Negative("HP", "-10%", (SimpleAmount)2), CardStats.Negative("Bullet Speed", "-10%", (SimpleAmount)2) }, CardClass.Skirmisher, CardTrack.Core, 2, "Skirmisher", "Aerial skirmisher", false, null, null), CardEffects.MultiplyGravityFloat(GravityFloatStatKey.GravityForce, 0.65f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.9f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.9f)); CardInfoStat[] stats11 = new CardInfoStat[4] { CardStats.Positive("Airborne", "+22% Damage", (SimpleAmount)0), CardStats.Positive("Airborne", "+10% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Jump", "+8%", (SimpleAmount)3), CardStats.Negative("HP", "-6%", (SimpleAmount)2) }; draftTags = new string[4] { "air", "tempo", "precision", "skirmisher" }; array[15] = new TemplateCardDefinition(new CardSpec("Slip Trigger", "Leave the floor and the next shot gets a little meaner. Nothing huge, just enough to make jump timing matter.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats11, CardClass.Skirmisher, CardTrack.Core, 2, "Skirmisher", "Air bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.08f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.AirborneBonus(1.22f, 1.1f)); CardInfoStat[] stats12 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Leave a surface shield on hit", (SimpleAmount)0), CardStats.Positive("Movement Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Jump", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("HP", "-4%", (SimpleAmount)1) }; draftTags = new string[6] { "shield", "summon", "wall", "position", "skirmisher", "cover" }; array[16] = new TemplateCardDefinition(new CardSpec("Slip Cover", "A skirmisher cover bridge. Shots leave a little surface shield behind, so your route through the fight can actually change the room instead of only your stats.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats12, CardClass.Skirmisher, CardTrack.Core, 2, "Skirmisher", "Cover bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.08f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.96f), VanillaCardEffects.AddSurfaceShieldShot("shield", "summon", "position")); CardInfoStat[] stats13 = new CardInfoStat[5] { CardStats.Positive("Witness Post", "Every 2.6s drop a 4.4m relay", (SimpleAmount)0), CardStats.Positive("Witness Post", "Enemy +16% Damage Taken", (SimpleAmount)0), CardStats.Positive("Movement Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Jump", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[7] { "beacon", "summon", "exposure", "skirmisher", "position", "control", "movement" }; array[17] = new TemplateCardDefinition(new CardSpec("Slip Relay", "A low-tier skirmisher post card. Keep moving and you keep leaving little witness relays behind, so anyone chasing your old route pays for it there instead of only at your barrel.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats13, CardClass.Skirmisher, CardTrack.Core, 2, "Skirmisher", "Witness bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.08f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.BeaconDrop(2.6f, 3.6f, 4.4f, 0.6f, 0, 0.95f, 1.16f, 0f, 1f, 1f, 1f)); CardInfoStat[] stats14 = new CardInfoStat[6] { CardStats.Positive("Wake Trail", "Every 1.8s drop a 2.0m field", (SimpleAmount)0), CardStats.Positive("Field", "Enemy -10% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Movement Speed", "+10%", (SimpleAmount)3), CardStats.Positive("Jump", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "hazard", "zone", "position", "skirmisher", "control", "movement" }; array[18] = new TemplateCardDefinition(new CardSpec("Ghost Lane", "A self-owned skirmisher room card. Every few beats you leave a short wake behind, so the way you route through the fight matters as much as the shot you finally take.", (Rarity)1, (CardThemeColorType)2, (CardInfoStat[])(object)stats14, CardClass.Skirmisher, CardTrack.Payoff, 3, "Skirmisher", "Wake route", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.TrailZone(1.8f, 1.8f, 2f, 0.45f, 0, 0.9f, 1.1f, 1.06f)); array[19] = new TemplateCardDefinition(new CardSpec("Bank Shot", "Play the walls instead of the lane.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("Reflects", "+2", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-15%", (SimpleAmount)2) }, CardClass.Trickster, CardTrack.Starter, 1, "Trickster", "Map-aware poke", false, null, null), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 2), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.85f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f)); CardInfoStat[] stats15 = new CardInfoStat[6] { CardStats.Positive("Knockback", "-1.5x", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 1.2s -10% Move Speed", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 1.2s +8% Reload Time", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy +10% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+5%", (SimpleAmount)3), CardStats.Negative("Damage", "-10%", (SimpleAmount)2) }; draftTags = new string[4] { "pull", "control", "disruption", "trickster" }; array[20] = new TemplateCardDefinition(new CardSpec("Hook Line", "Trade raw damage for control by dragging targets into bad spots and making the follow-up feel worse for them too.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats15, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Positional control", allowMultiple: false, null, null, draftTags), CardEffects.ReverseKnockback(1.5f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.05f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f), AbilityCardEffects.HitDisruption(1.2f, 0.9f, 1.08f, 1.1f)); CardInfoStat[] stats16 = new CardInfoStat[4] { CardStats.Positive("First Hit", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +20% Damage", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +8% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Damage", "-5%", (SimpleAmount)1) }; draftTags = new string[4] { "mark", "control", "tempo", "trickster" }; array[21] = new TemplateCardDefinition(new CardSpec("Chalk Mark", "A low-commitment tag card. Land one clean shot, then the follow-up starts carrying the idea further.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats16, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Light tag", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.95f), AbilityCardEffects.MarkedPrey(1, 1, 1.2f, 1.08f, 1.05f, 0f)); CardInfoStat[] stats17 = new CardInfoStat[5] { CardStats.Positive("Orbit Ward", "1 ward, 4.2m scan", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 0.8s -10% Move Speed", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 0.8s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("HP", "-5%", (SimpleAmount)1) }; draftTags = new string[5] { "summon", "orbit", "control", "disruption", "trickster" }; array[22] = new TemplateCardDefinition(new CardSpec("Pocket Witness", "A low-tier summon bridge. Let a small watcher do a little quiet harassment nearby so your control cards do not all have to live on direct hits.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats17, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Watcher bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.95f), AbilityCardEffects.OrbitWard(1, 4.2f, 2.1f, 0, 0.8f, 0.9f, 1.1f, 1.06f)); CardInfoStat[] stats18 = new CardInfoStat[5] { CardStats.Positive("On Hit", "Enemy 1.8s -6% Move Speed", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 1.8s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy +8% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-4%", (SimpleAmount)1) }; draftTags = new string[4] { "disruption", "control", "tempo", "trickster" }; array[23] = new TemplateCardDefinition(new CardSpec("Paper Trail", "A lower-end disruption card. Land one clean hit and make the reload and block math worse before the next exchange really starts.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats18, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Disruption bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.96f), AbilityCardEffects.HitDisruption(1.8f, 0.94f, 1.1f, 1.08f)); array[24] = new TemplateCardDefinition(new CardSpec("Scatter Core", "Turn each trigger pull into a wider cone of pressure.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Projectiles", "+2", (SimpleAmount)0), CardStats.Positive("Spread", "+20%", (SimpleAmount)3), CardStats.Negative("Damage", "-35%", (SimpleAmount)5), CardStats.Negative("Reload Speed", "-15%", (SimpleAmount)2) }, CardClass.Demolitionist, CardTrack.Starter, 1, "Demolitionist", "Shotgun pressure", false, null, null), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 2), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.2f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.65f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.15f)); CardInfoStat[] stats19 = new CardInfoStat[4] { CardStats.Positive("Near Enemy", "Explodes for 7 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.2m", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+10%", (SimpleAmount)3), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "explosion", "projectile", "control", "demolitionist" }; array[25] = new TemplateCardDefinition(new CardSpec("Fuse Bloom", "Give the shell a tiny blast radius so near misses still feel like pressure instead of dead air.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats19, CardClass.Demolitionist, CardTrack.Core, 2, "Demolitionist", "Blast seed", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.1f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 7f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.2f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f)); CardInfoStat[] stats20 = new CardInfoStat[5] { CardStats.Positive("On Kill", "1.9m field for 1.9s", (SimpleAmount)0), CardStats.Positive("Field", "Enemies -6% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+8%", (SimpleAmount)3), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[5] { "hazard", "zone", "kill", "control", "demolitionist" }; array[26] = new TemplateCardDefinition(new CardSpec("Ash Lease", "A smaller hazard bridge. Finish the target and keep that patch of floor awkward long enough to matter.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats20, CardClass.Demolitionist, CardTrack.Core, 2, "Demolitionist", "Hazard bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.08f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), AbilityCardEffects.KillZone(1.9f, 1.9f, 0.55f, 0, 0.94f, 1.1f, 1.04f)); CardInfoStat[] stats21 = new CardInfoStat[6] { CardStats.Positive("On Kill", "2.2m field for 2.4s", (SimpleAmount)0), CardStats.Positive("Field", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Field", "Enemies -8% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +12% Reload Time", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "kill", "zone", "hazard", "explosion", "control", "demolitionist" }; array[27] = new TemplateCardDefinition(new CardSpec("Vacancy Notice", "A lower-end hazard payoff. Land the kill, then make that patch of floor stay awkward for everyone else for a moment.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats21, CardClass.Demolitionist, CardTrack.Payoff, 3, "Demolitionist", "Hazard bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.KillZone(2.4f, 2.2f, 0.5f, 1, 0.92f, 1.12f, 1.06f)); CardInfoStat[] stats22 = new CardInfoStat[11] { CardStats.Positive("Projectiles", "+1", (SimpleAmount)0), CardStats.Positive("Damage", "+25%", (SimpleAmount)3), CardStats.Positive("Near Enemy", "Explodes for 11 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.6m", (SimpleAmount)0), CardStats.Positive("On Kill", "2.6m field for 2.8s", (SimpleAmount)0), CardStats.Positive("Field", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Field", "Enemies -8% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +14% Reload Time", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Movement Speed", "-18%", (SimpleAmount)5), CardStats.Negative("Reload Speed", "+12%", (SimpleAmount)3) }; draftTags = new string[7] { "explosion", "zone", "hazard", "kill", "projectile", "demolitionist", "capstone" }; array[28] = new TemplateCardDefinition(new CardSpec("Siege Engine", "A heavy artillery platform. The shell blooms on approach, and a kill should leave the impact lane hostile instead of simply empty.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats22, CardClass.Demolitionist, CardTrack.Capstone, 5, "Demolitionist", "Slow artillery", allowMultiple: false, null, null, draftTags), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.25f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 11f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.6f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.88f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.KillZone(2.8f, 2.6f, 0.45f, 1, 0.92f, 1.14f, 1.08f)); CardInfoStat[] stats23 = new CardInfoStat[4] { CardStats.Positive("Damage", "+85%", (SimpleAmount)4), CardStats.Positive("Movement Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage Falloff", "Extreme", (SimpleAmount)5), CardStats.Negative("Bullet Speed", "-10%", (SimpleAmount)2) }; draftTags = new string[3] { "close", "brawler", "pressure" }; array[29] = new TemplateCardDefinition(new CardSpec("Close Quarters", "A real point-blank card: huge upfront damage, but the shot starts losing its nerve almost immediately.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats23, CardClass.Brawler, CardTrack.Core, 2, "Brawler", "Point-blank spike", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.85f), CardEffects.SetGunFloat(GunFloatStatKey.DamageAfterDistanceMultiplier, 0.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.9f)); CardInfoStat[] stats24 = new CardInfoStat[3] { CardStats.Positive("On Empty Mag", "1.5s +12% Move Speed", (SimpleAmount)0), CardStats.Positive("On Empty Mag", "1.5s +18% Reload Speed", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "reload", "tempo", "brawler", "chamber" }; array[30] = new TemplateCardDefinition(new CardSpec("Spent Case", "Run the magazine dry and your body knows it is time to move and reload now, not later.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats24, CardClass.Brawler, CardTrack.Core, 2, "Brawler", "Burnout bridge", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), AbilityCardEffects.EmptyMagRush(1.5f, 1.12f, 0.82f)); CardInfoStat[] stats25 = new CardInfoStat[7] { CardStats.Positive("On Kill", "2.4m field for 2.4s", (SimpleAmount)0), CardStats.Positive("Field", "Enemy -12% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +14% Reload Time", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +10% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Damage", "+6%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+6%", (SimpleAmount)3), CardStats.Negative("HP", "-4%", (SimpleAmount)1) }; draftTags = new string[7] { "zone", "hazard", "brawler", "kill", "control", "pressure", "close" }; array[31] = new TemplateCardDefinition(new CardSpec("Ring Tax", "A brawler kill should make the floor worse for everyone else. Drop someone up close and leave a bad little ring behind before the next push starts.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats25, CardClass.Brawler, CardTrack.Payoff, 3, "Brawler", "Kill zone bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.06f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.06f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.96f), AbilityCardEffects.KillZone(2.4f, 2.4f, 0.45f, 0, 0.88f, 1.14f, 1.1f)); CardInfoStat[] stats26 = new CardInfoStat[8] { CardStats.Positive("Corner Post", "Every 2.2s drop a 5.0m post", (SimpleAmount)0), CardStats.Positive("Corner Post", "Enemy +20% Damage Taken", (SimpleAmount)0), CardStats.Positive("Corner Post", "Enemy -10% Move Speed", (SimpleAmount)0), CardStats.Positive("Corner Post", "Enemy +12% Reload Time", (SimpleAmount)0), CardStats.Positive("Corner Post", "Enemy +10% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Damage", "+8%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+6%", (SimpleAmount)3), CardStats.Negative("HP", "-4%", (SimpleAmount)1) }; draftTags = new string[7] { "beacon", "summon", "exposure", "brawler", "pressure", "control", "close" }; array[32] = new TemplateCardDefinition(new CardSpec("Red Corner", "A real brawler post card. Stay in the pocket and you keep planting corner markers behind the fight, so anybody who steps back in takes more damage and worse tempo immediately.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats26, CardClass.Brawler, CardTrack.Signature, 4, "Brawler", "Corner post signature", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.08f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.06f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.96f), AbilityCardEffects.BeaconDrop(2.2f, 4.2f, 5f, 0.55f, 0, 1f, 1.2f, 1f, 0.9f, 1.12f, 1.1f)); CardInfoStat[] stats27 = new CardInfoStat[7] { CardStats.Positive("Wake Trail", "Every 1.5s drop a 2.3m field", (SimpleAmount)0), CardStats.Positive("Field", "Enemy -12% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +14% Reload Time", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +10% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Damage", "+10%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+8%", (SimpleAmount)3), CardStats.Negative("HP", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "hazard", "zone", "brawler", "pressure", "control", "close" }; array[33] = new TemplateCardDefinition(new CardSpec("Boil Over", "A real brawler room card. The longer you stay in the scrap, the more often you leave hot ground behind and force everyone else to respect where the fistfight is happening.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats27, CardClass.Brawler, CardTrack.Signature, 4, "Brawler", "Hot ground signature", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.08f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.TrailZone(1.5f, 2f, 2.3f, 0.4f, 0, 0.88f, 1.14f, 1.1f)); array[34] = new TemplateCardDefinition(new CardSpec("Countercharge", "A clean block loads your next shot with real weight instead of just forgiveness.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("After Block", "Next shot +75% Damage", (SimpleAmount)0), CardStats.Positive("After Block", "Next shot +15% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+12%", (SimpleAmount)3) }, CardClass.Bulwark, CardTrack.Signature, 4, "Bulwark", "Punish window", false, null, null), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.12f), AbilityCardEffects.CounterCharge(1.75f, 1.15f)); CardInfoStat[] stats28 = new CardInfoStat[6] { CardStats.Positive("After Block", "Next shot unblockable", (SimpleAmount)0), CardStats.Positive("After Block", "+35% Damage", (SimpleAmount)0), CardStats.Positive("After Block", "+18% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Block", "+45% Projectile Size", (SimpleAmount)0), CardStats.Positive("Additional Blocks", "+1", (SimpleAmount)0), CardStats.Negative("Movement Speed", "-8%", (SimpleAmount)2) }; draftTags = new string[3] { "block", "projectile", "precision" }; array[35] = new TemplateCardDefinition(new CardSpec("Mirror Verdict", "A real parry card: block once, then return a huge ghost round that does not care about the other player's shield.", (Rarity)2, (CardThemeColorType)1, (CardInfoStat[])(object)stats28, CardClass.Bulwark, CardTrack.Capstone, 5, "Bulwark", "Ghost parry", allowMultiple: false, null, null, draftTags), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f), AbilityCardEffects.BlockGhost(1.35f, 1.18f, 1.45f, unblockable: true)); array[36] = new TemplateCardDefinition(new CardSpec("Final Notice", "The last round in the magazine gets the respect the rest never earned.", (Rarity)1, (CardThemeColorType)0, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("Last Bullet", "+100% Damage", (SimpleAmount)0), CardStats.Positive("Last Bullet", "+20% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+10%", (SimpleAmount)3) }, CardClass.Marksman, CardTrack.Payoff, 3, "Marksman", "Closer", false, null, null), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), AbilityCardEffects.LastBulletBonus(2f, 1.2f)); array[37] = new TemplateCardDefinition(new CardSpec("Opening Statement", "Reloads should have a point. Make the first shot announce itself.", (Rarity)1, (CardThemeColorType)2, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("First Bullet", "+60% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+20% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }, CardClass.Neutral, CardTrack.Core, 2, "Neutral", "Reload cadence", false, null, null), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), AbilityCardEffects.FirstShotBonus(1.6f, 1.2f)); CardInfoStat[] stats29 = new CardInfoStat[4] { CardStats.Positive("After Full Reload", "+35% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "+12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Full Reload", "+18% Projectile Size", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "reload", "chamber", "precision", "projectile" }; array[38] = new TemplateCardDefinition(new CardSpec("Clean Break", "A real reload should do more than refill the gun. Let it load one round that comes out noticeably sharper.", (Rarity)1, (CardThemeColorType)2, (CardInfoStat[])(object)stats29, CardClass.Marksman, CardTrack.Payoff, 3, "Marksman", "Reload charge", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), AbilityCardEffects.ReloadCharge(1.35f, 1.12f, 1.18f, unblockable: false)); array[39] = new TemplateCardDefinition(new CardSpec("Skybreaker", "The moment you leave the floor, your weapon stops pretending it belongs to ground fights.", (Rarity)2, (CardThemeColorType)2, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Airborne", "+45% Damage", (SimpleAmount)0), CardStats.Positive("Airborne", "+20% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Jump", "+12%", (SimpleAmount)3), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }, CardClass.Skirmisher, CardTrack.Payoff, 3, "Skirmisher", "Aerial punish", false, null, null), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.Jump, 1.12f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.AirborneBonus(1.45f, 1.2f)); array[40] = new TemplateCardDefinition(new CardSpec("Catch and Rack", "A clean block buys back a round and keeps the weapon from falling behind the pace of the room.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("After Block", "+1 Ammo", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("Block Cooldown", "+10%", (SimpleAmount)3) }, CardClass.Neutral, CardTrack.Core, 2, "Neutral", "Ammo rescue", false, null, null), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.1f), AbilityCardEffects.BlockAmmo(1)); CardInfoStat[] stats30 = new CardInfoStat[4] { CardStats.Positive("After Full Reload", "Next shot ignores walls", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +18% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +16% Projectile Size", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[5] { "reload", "weapon mode", "wall", "projectile", "neutral" }; array[41] = new TemplateCardDefinition(new CardSpec("Paper Breach", "A simple weapon-mode bridge. A real reload should change the next shot pattern, not just its damage line.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats30, CardClass.Neutral, CardTrack.Core, 2, "Neutral", "Breach bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.ReloadMode(1, 1.18f, 1.02f, 1.16f, ignoreWalls: true, 0, 0f)); CardInfoStat[] stats31 = new CardInfoStat[5] { CardStats.Positive("After Full Reload", "Next shot +1 Projectile", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +15% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +12% Bullet Speed", (SimpleAmount)0), CardStats.Negative("After Full Reload", "Next shot +12% Spread", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[5] { "reload", "weapon mode", "projectile", "burst", "neutral" }; array[42] = new TemplateCardDefinition(new CardSpec("Split Decision", "A lower-end reload mode that changes shape instead of just damage. Full reloads kick one wider, nastier double-shell back into the chamber.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats31, CardClass.Neutral, CardTrack.Core, 2, "Neutral", "Split shell bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.ReloadMode(1, 1.15f, 1.12f, 1.05f, ignoreWalls: false, 1, 0.12f)); array[43] = new TemplateCardDefinition(new CardSpec("Loaded Curtain", "A gambler's version of a real weapon mode: only a full reload arms it, then the next pair of shots stop respecting cover or symmetry.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[7] { CardStats.Positive("After Full Reload", "Next 2 shots ignore walls", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next 2 shots +45% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next 2 shots +12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next 2 shots +24% Projectile Size", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next 2 shots +1 Projectile", (SimpleAmount)0), CardStats.Negative("Spread", "+10%", (SimpleAmount)3), CardStats.Negative("Movement Speed", "-8%", (SimpleAmount)2) }, CardClass.Gambler, CardTrack.Signature, 4, "Gambler", "Volatile opener", false, null, null), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f), AbilityCardEffects.ReloadMode(2, 1.45f, 1.12f, 1.24f, ignoreWalls: true, 1, 0.1f)); CardInfoStat[] stats32 = new CardInfoStat[3] { CardStats.Positive("On Hit", "Next shot +22% Damage", (SimpleAmount)0), CardStats.Positive("On Hit", "Next shot +8% Bullet Speed", (SimpleAmount)0), CardStats.Negative("HP", "-6%", (SimpleAmount)2) }; draftTags = new string[4] { "hit", "tempo", "gambler", "burst" }; array[44] = new TemplateCardDefinition(new CardSpec("Bad Debt", "Every clean hit puts a little pressure into the next shot. Not enough to decide the round, enough to tilt the next exchange.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats32, CardClass.Gambler, CardTrack.Core, 2, "Gambler", "Light debt", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.HitCharge(1.22f, 1.08f)); CardInfoStat[] stats33 = new CardInfoStat[5] { CardStats.Positive("On Hit", "Enemy 1.6s -14% Move Speed", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 1.6s +18% Reload Time", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 1.6s +12% Damage Taken", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "disruption", "hit", "tempo", "gambler", "control", "exposure" }; array[45] = new TemplateCardDefinition(new CardSpec("Quiet Lien", "A low-tier disruption card. The hit is not just a setup for you; it leaves the other player softer for the next exchange too, not just slower.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats33, CardClass.Gambler, CardTrack.Core, 2, "Gambler", "Disruption bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.HitDisruption(1.6f, 0.86f, 1.18f, 1.08f), AbilityCardEffects.HitExposure(1.6f, 1.12f)); CardInfoStat[] stats34 = new CardInfoStat[6] { CardStats.Positive("Orbit Wards", "1 ward, 5.4m scan", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "Apply Mark", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "Enemy 0.9s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "Enemy +8% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Reload Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "orbit", "summon", "mark", "gambler", "disruption", "control" }; array[46] = new TemplateCardDefinition(new CardSpec("Float Account", "A gambler helper bridge. Let a little collector circle the fight, tag people for you, and keep the room feeling alive even before the loud capstones show up.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats34, CardClass.Gambler, CardTrack.Core, 2, "Gambler", "Collector bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.94f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.OrbitWard(1, 5.4f, 2.5f, 1, 0.9f, 1f, 1.1f, 1.08f)); CardInfoStat[] stats35 = new CardInfoStat[6] { CardStats.Positive("Wake Trail", "Every 2.0s drop a 2.1m field", (SimpleAmount)0), CardStats.Positive("Field", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Field", "Enemy +12% Reload Time", (SimpleAmount)0), CardStats.Positive("Reload Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[6] { "hazard", "zone", "mark", "gambler", "control", "economy" }; array[47] = new TemplateCardDefinition(new CardSpec("Debt Wake", "A gambler control card that changes the floor instead of the chamber. Your path through the round keeps leaving little marked debt pools behind for the next exchange.", (Rarity)1, (CardThemeColorType)3, (CardInfoStat[])(object)stats35, CardClass.Gambler, CardTrack.Payoff, 3, "Gambler", "Debt field", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.92f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.TrailZone(2f, 2f, 2.1f, 0.45f, 1, 1f, 1.12f, 1.06f)); CardInfoStat[] stats36 = new CardInfoStat[7] { CardStats.Positive("Collector Post", "Every 2.5s drop a 4.8m post", (SimpleAmount)0), CardStats.Positive("Collector Post", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Collector Post", "Enemy +18% Damage Taken", (SimpleAmount)0), CardStats.Positive("Collector Post", "Enemy +12% Reload Time", (SimpleAmount)0), CardStats.Positive("Reload Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[7] { "beacon", "summon", "mark", "exposure", "gambler", "disruption", "control" }; array[48] = new TemplateCardDefinition(new CardSpec("Witness Dividend", "A gambler room card that keeps paying forward. Your route leaves collector posts behind, they mark people, and the next hit into that debt actually hurts more.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats36, CardClass.Gambler, CardTrack.Signature, 4, "Gambler", "Collector post", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.92f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.BeaconDrop(2.5f, 4.4f, 4.8f, 0.65f, 1, 1f, 1.18f, 0.9f, 1f, 1.12f, 1.06f)); array[49] = new TemplateCardDefinition(new CardSpec("Hard Reset", "A perfect block forces the whole weapon system back into motion for a short burst.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("After Block", "+18% Move Speed for 2s", (SimpleAmount)0), CardStats.Positive("After Block", "+25% Reload Speed for 2s", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+10%", (SimpleAmount)3) }, CardClass.Wildcard, CardTrack.Wildcard, 3, "Wildcard", "Parry tempo", false, null, null), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.1f), AbilityCardEffects.BlockTempo(2f, 1.18f, 0.75f)); CardInfoStat[] stats37 = new CardInfoStat[4] { CardStats.Positive("After Miss", "+40% Damage", (SimpleAmount)0), CardStats.Positive("After Miss", "+14% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Miss", "+20% Projectile Size", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "miss", "precision", "tempo", "projectile" }; array[50] = new TemplateCardDefinition(new CardSpec("Mercy Window", "A miss is not dead time if the follow-up arrives faster, bigger, and with enough force to matter.", (Rarity)1, (CardThemeColorType)2, (CardInfoStat[])(object)stats37, CardClass.Skirmisher, CardTrack.Payoff, 3, "Skirmisher", "Whiff punish", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.MissCharge(1.4f, 1.14f, 1.2f, unblockable: false)); array[51] = new TemplateCardDefinition(new CardSpec("Held Breath", "Patience turns into punch when the next trigger pull is worth waiting for.", (Rarity)1, (CardThemeColorType)0, (CardInfoStat[])(object)new CardInfoStat[3] { CardStats.Positive("If Idle 1.25s", "Next shot +85% Damage", (SimpleAmount)0), CardStats.Positive("If Idle 1.25s", "Next shot +15% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }, CardClass.Neutral, CardTrack.Payoff, 3, "Neutral", "Patience spike", false, null, null), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.PatientShot(1.25f, 1.85f, 1.15f)); array[52] = new TemplateCardDefinition(new CardSpec("Run the Rack", "Keep firing on rhythm and the weapon starts climbing into a much uglier gear.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Chain Shots", "+12% Damage per stack", (SimpleAmount)0), CardStats.Positive("Chain Shots", "+10% Bullet Speed per stack", (SimpleAmount)0), CardStats.Positive("Chain Shots", "Up to 3 stacks", (SimpleAmount)0), CardStats.Negative("Spread", "+12%", (SimpleAmount)3) }, CardClass.Wildcard, CardTrack.Wildcard, 4, "Wildcard", "Pressure escalator", false, null, null), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.12f), AbilityCardEffects.ChainShot(0.65f, 1.12f, 1.1f, 3)); array[53] = new TemplateCardDefinition(new CardSpec("Bloodsport", "Every clean hit pays back a little life so the brawl can stay ugly longer.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Damage", "+15%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+10%", (SimpleAmount)3), CardStats.Positive("Heal on Hit", "10% Damage Returned", (SimpleAmount)0), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }, CardClass.Brawler, CardTrack.Signature, 4, "Brawler", "Sustain bruiser", false, null, null), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.15f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.1f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.Lifesteal(0.1f)); array[54] = new TemplateCardDefinition(new CardSpec("Ghost Payload", "The round slips through defenses, drags the victim out of line, and leaves their recovery worse for a moment after the hit.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[8] { CardStats.Positive("Bullets", "Unblockable", (SimpleAmount)0), CardStats.Positive("Knockback", "-1.6x", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 2s -8% Move Speed", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy 2s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("On Hit", "Enemy +8% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+12%", (SimpleAmount)3), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }, CardClass.Trickster, CardTrack.Capstone, 5, "Trickster", "Defense bypass", false, null, null), CardEffects.SetGunBool(GunBoolStatKey.Unblockable, value: true), CardEffects.ReverseKnockback(1.6f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.12f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.HitDisruption(2f, 0.92f, 1.1f, 1.08f)); array[55] = new TemplateCardDefinition(new CardSpec("Nova Chamber", "Your shot grows into a spell-body of its own, and full reloads arm the next one into a heavier chamber instead of just refilling the gun.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[7] { CardStats.Positive("Projectile Size", "+35%", (SimpleAmount)4), CardStats.Positive("Bullet Gravity", "-18%", (SimpleAmount)3), CardStats.Positive("Bullet Life", "+1.5s", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +12% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +10% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next shot +20% Projectile Size", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }, CardClass.Arcanist, CardTrack.Signature, 4, "Arcanist", "Spell projectile", false, null, null), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.35f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Gravity, 0.82f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 5f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 1.5f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), AbilityCardEffects.ReloadCharge(1.12f, 1.1f, 1.2f, unblockable: false)); array[56] = new TemplateCardDefinition(new CardSpec("Mouth of the Pit", "Demolitionist capstone: bigger shells, more shell, and reloads that arm the next burst into a denser, meaner barrage.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)new CardInfoStat[7] { CardStats.Positive("Projectiles", "+1", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+30%", (SimpleAmount)4), CardStats.Positive("Damage", "+12%", (SimpleAmount)3), CardStats.Positive("After Full Reload", "Next 2 shots +18% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Next 2 shots +14% Projectile Size", (SimpleAmount)0), CardStats.Negative("Movement Speed", "-8%", (SimpleAmount)1), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }, CardClass.Demolitionist, CardTrack.Capstone, 5, "Demolitionist", "Siege spread", false, null, null), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.3f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.12f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.ReloadMode(2, 1.18f, 1.02f, 1.14f, ignoreWalls: false, 0, 0.08f)); array[57] = new TemplateCardDefinition(new CardSpec("Black Receipt", "A landed shot writes a debt note into the next one, and the interest is violence.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)new CardInfoStat[4] { CardStats.Positive("Damage", "+14%", (SimpleAmount)3), CardStats.Positive("Movement Speed", "+12%", (SimpleAmount)3), CardStats.Positive("On Hit", "Next shot charged", (SimpleAmount)0), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }, CardClass.Gambler, CardTrack.Signature, 4, "Gambler", "Momentum debt", false, null, null), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.14f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.12f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.HitCharge(1.3f, 1.15f)); CardInfoStat[] stats38 = new CardInfoStat[6] { CardStats.Positive("Damage Falloff", "Removed", (SimpleAmount)0), CardStats.Positive("If Idle 1s", "Next shot +70% Damage", (SimpleAmount)0), CardStats.Positive("If Idle 1s", "Next shot +15% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+12%", (SimpleAmount)3), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Reload Speed", "-8%", (SimpleAmount)2) }; draftTags = new string[4] { "precision", "range", "patient", "projectile" }; array[58] = new TemplateCardDefinition(new CardSpec("Far Witness", "A real lane-control capstone: the room stops eating your damage, and patience turns the next shot into an execution.", (Rarity)2, (CardThemeColorType)0, (CardInfoStat[])(object)stats38, CardClass.Marksman, CardTrack.Capstone, 5, "Marksman", "Lane execution", allowMultiple: false, null, null, draftTags), CardEffects.SetGunFloat(GunFloatStatKey.DamageAfterDistanceMultiplier, 1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.12f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.PatientShot(1f, 1.7f, 1.15f)); CardInfoStat[] stats39 = new CardInfoStat[6] { CardStats.Positive("Reflects", "+2", (SimpleAmount)0), CardStats.Positive("Random Bounces", "+1", (SimpleAmount)0), CardStats.Positive("Smart Bounce", "+1", (SimpleAmount)0), CardStats.Positive("Per Bounce", "+18% Damage", (SimpleAmount)0), CardStats.Positive("Bullet Life", "+2s", (SimpleAmount)0), CardStats.Negative("Damage", "-8%", (SimpleAmount)2) }; draftTags = new string[4] { "bounce", "wall", "control", "projectile" }; array[59] = new TemplateCardDefinition(new CardSpec("Jury of Angles", "A wall card that actually scales: each bounce makes the case worse, and smart ricochets keep trying to find someone to blame.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats39, CardClass.Trickster, CardTrack.Capstone, 5, "Trickster", "Angle execution", allowMultiple: false, null, null, draftTags), VanillaCardEffects.AddRoomBounce("precision"), VanillaCardEffects.AddRetargetBounce("precision", "weapon_mode"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 2), CardEffects.AddGunInt(GunIntStatKey.RandomBounces, 1), CardEffects.AddGunInt(GunIntStatKey.SmartBounces, 1), CardEffects.SetGunFloat(GunFloatStatKey.BounceSpeedMultiplier, 1.15f), CardEffects.SetGunFloat(GunFloatStatKey.BounceDamageMultiplier, 1.18f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 2f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f)); CardInfoStat[] stats40 = new CardInfoStat[9] { CardStats.Positive("Projectiles", "+1", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+25%", (SimpleAmount)4), CardStats.Positive("Near Enemy", "Explodes for 16 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "2.1m", (SimpleAmount)0), CardStats.Positive("Bullets", "Leave a static remnant on contact", (SimpleAmount)0), CardStats.Positive("On Kill", "2m field for 2s", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +10% Reload Time", (SimpleAmount)0), CardStats.Negative("Bullet Speed", "-6%", (SimpleAmount)1), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[6] { "explosion", "projectile", "aoe", "demolitionist", "remnant", "hazard" }; array[60] = new TemplateCardDefinition(new CardSpec("Wake Mine", "A demolitionist capstone for people who want every near miss to still be a threat, every clean hit to leave a remnant behind, and every kill to leave a little denial behind.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats40, CardClass.Demolitionist, CardTrack.Capstone, 5, "Demolitionist", "Proximity artillery", allowMultiple: false, null, null, draftTags), CardEffects.AddGunInt(GunIntStatKey.NumberOfProjectiles, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.25f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 16f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 2.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.94f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), VanillaCardEffects.AddStaticRemnantShot("hazard", "weapon_mode"), AbilityCardEffects.KillZone(2f, 2f, 0.6f, 0, 0.96f, 1.1f, 1.04f)); CardInfoStat[] stats41 = new CardInfoStat[9] { CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+30%", (SimpleAmount)4), CardStats.Positive("Bullet Gravity", "-18%", (SimpleAmount)3), CardStats.Positive("Bullet Life", "+2s", (SimpleAmount)0), CardStats.Positive("After Miss", "Next shot +12% Damage", (SimpleAmount)0), CardStats.Positive("After Miss", "Next shot +8% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Miss", "Next shot +15% Projectile Size", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "wall", "projectile", "precision", "arcanist" }; array[61] = new TemplateCardDefinition(new CardSpec("Cathedral Thread", "The spell-bolt version of cheating: big rounds, slow nerves, no respect for cover, and misses that come back loaded instead of wasted.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats41, CardClass.Arcanist, CardTrack.Capstone, 5, "Arcanist", "Cover breach", allowMultiple: false, null, null, draftTags), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.3f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Gravity, 0.82f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 2f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), AbilityCardEffects.MissCharge(1.12f, 1.08f, 1.15f, unblockable: false)); CardInfoStat[] stats42 = new CardInfoStat[5] { CardStats.Positive("First Bullet", "+30% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+10% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+15% Projectile Size", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "reload", "chamber", "precision", "tempo" }; array[62] = new TemplateCardDefinition(new CardSpec("Cold Ledger", "Reload with intent. The first round out of a full mag comes out cleaner, faster, and heavy enough to matter.", (Rarity)1, (CardThemeColorType)0, (CardInfoStat[])(object)stats42, CardClass.Marksman, CardTrack.Payoff, 3, "Marksman", "Chamber bridge", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), AbilityCardEffects.ChamberedRound(1.3f, 1.1f, 1.15f, unblockable: false)); CardInfoStat[] stats43 = new CardInfoStat[3] { CardStats.Positive("Next 2 Round Drafts", "+1 Pick", (SimpleAmount)0), CardStats.Positive("Reload Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[4] { "draft", "economy", "tempo", "future pick" }; array[63] = new TemplateCardDefinition(new CardSpec("Layaway", "Cash this card in over time: your next two round drafts each let you take one extra card.", (Rarity)1, (CardThemeColorType)2, (CardInfoStat[])(object)stats43, CardClass.Neutral, CardTrack.Payoff, 3, "Neutral", "Draft layaway", allowMultiple: false, null, null, draftTags), DraftCardEffects.FutureExtraPicks(2, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.9f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f)); CardInfoStat[] stats44 = new CardInfoStat[5] { CardStats.Positive("Near Enemy", "Explodes for 10 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.5m", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+18%", (SimpleAmount)4), CardStats.Negative("Damage", "-8%", (SimpleAmount)1), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "explosion", "projectile", "wildcard", "control" }; array[64] = new TemplateCardDefinition(new CardSpec("Proxy Lease", "Near misses still make rent. Shots bloom when they get close enough to start scaring people.", (Rarity)1, (CardThemeColorType)3, (CardInfoStat[])(object)stats44, CardClass.Wildcard, CardTrack.Wildcard, 3, "Wildcard", "Hazard bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.18f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 10f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.5f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f)); CardInfoStat[] stats45 = new CardInfoStat[5] { CardStats.Positive("First Bullet", "+55% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+18% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+25% Projectile Size", (SimpleAmount)0), CardStats.Positive("First Bullet", "Unblockable", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "reload", "chamber", "precision", "projectile" }; array[65] = new TemplateCardDefinition(new CardSpec("Sealed Verdict", "Full reloads become a ceremony: the next shot comes out bigger, faster, and impossible to ignore.", (Rarity)2, (CardThemeColorType)0, (CardInfoStat[])(object)stats45, CardClass.Marksman, CardTrack.Signature, 4, "Marksman", "Chamber finisher", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), AbilityCardEffects.ChamberedRound(1.55f, 1.18f, 1.25f, unblockable: true)); CardInfoStat[] stats46 = new CardInfoStat[8] { CardStats.Positive("After Full Reload", "+65% Damage", (SimpleAmount)0), CardStats.Positive("After Full Reload", "+20% Projectile Size", (SimpleAmount)0), CardStats.Positive("After Full Reload", "Unblockable", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +45% Damage", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +15% Bullet Speed", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +15% Projectile Size", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Movement Speed", "-6%", (SimpleAmount)1) }; draftTags = new string[5] { "reload", "kill", "gambler", "projectile", "tempo" }; array[66] = new TemplateCardDefinition(new CardSpec("Blackout Clause", "A real capstone chamber card: finish a reload to arm the opener, then turn every kill into another loaded threat.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats46, CardClass.Gambler, CardTrack.Capstone, 5, "Gambler", "Reload-kill chain", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.94f), AbilityCardEffects.ReloadCharge(1.65f, 1.1f, 1.2f, unblockable: true), AbilityCardEffects.KillCharge(1.45f, 1.15f, 1.15f, unblockable: false)); CardInfoStat[] stats47 = new CardInfoStat[4] { CardStats.Positive("On Empty Mag", "2s +18% Move Speed", (SimpleAmount)0), CardStats.Positive("On Empty Mag", "2s +28% Reload Speed", (SimpleAmount)0), CardStats.Positive("Damage", "+8%", (SimpleAmount)3), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "reload", "chamber", "tempo", "brawler" }; array[67] = new TemplateCardDefinition(new CardSpec("Dead Chamber", "Spend the last bullet and your whole body shifts gears long enough to get the next reload under control.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats47, CardClass.Brawler, CardTrack.Payoff, 3, "Brawler", "Empty-mag tempo", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.08f), AbilityCardEffects.EmptyMagRush(2f, 1.18f, 0.72f)); CardInfoStat[] stats48 = new CardInfoStat[4] { CardStats.Positive("Every 3rd Shot", "+45% Damage", (SimpleAmount)0), CardStats.Positive("Every 3rd Shot", "+15% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Every 3rd Shot", "+30% Projectile Size", (SimpleAmount)0), CardStats.Negative("Spread", "+10%", (SimpleAmount)3) }; draftTags = new string[4] { "cadence", "chamber", "projectile", "burst" }; array[68] = new TemplateCardDefinition(new CardSpec("Third Rail", "Every third shot stops being a bullet and starts being a point you are making.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats48, CardClass.Arcanist, CardTrack.Signature, 4, "Arcanist", "Cadence shell", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.1f), AbilityCardEffects.CadenceRound(3, 1.45f, 1.15f, 1.3f, unblockable: false)); CardInfoStat[] stats49 = new CardInfoStat[3] { CardStats.Positive("Every 4th Shot", "+25% Damage", (SimpleAmount)0), CardStats.Positive("Every 4th Shot", "+15% Projectile Size", (SimpleAmount)0), CardStats.Negative("Spread", "+6%", (SimpleAmount)3) }; draftTags = new string[4] { "cadence", "projectile", "tempo", "arcanist" }; array[69] = new TemplateCardDefinition(new CardSpec("Trip Script", "A small cadence card. Every fourth shot gets just enough extra shape and force to make pacing matter.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats49, CardClass.Arcanist, CardTrack.Core, 2, "Arcanist", "Light cadence", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.06f), AbilityCardEffects.CadenceRound(4, 1.25f, 1.02f, 1.15f, unblockable: false)); CardInfoStat[] stats50 = new CardInfoStat[5] { CardStats.Positive("Orbit Ward", "1 ward, 4.8m scan", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 1.1s +10% Reload Time", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+8%", (SimpleAmount)3), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[5] { "summon", "orbit", "mark", "arcanist", "control" }; array[70] = new TemplateCardDefinition(new CardSpec("Chamber Wisp", "A light summon bridge. Let a small orbiting witness bother nearby players so your spell cards stop relying on raw projectile math alone.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats50, CardClass.Arcanist, CardTrack.Core, 2, "Arcanist", "Summon bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.08f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), AbilityCardEffects.OrbitWard(1, 4.8f, 2.2f, 1, 1.1f, 1f, 1.1f, 1.04f)); CardInfoStat[] stats51 = new CardInfoStat[6] { CardStats.Positive("After Miss", "+60% Damage", (SimpleAmount)0), CardStats.Positive("After Miss", "+18% Bullet Speed", (SimpleAmount)0), CardStats.Positive("After Miss", "+35% Projectile Size", (SimpleAmount)0), CardStats.Positive("After Miss", "Unblockable", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Spread", "+8%", (SimpleAmount)3) }; draftTags = new string[4] { "miss", "projectile", "precision", "burst" }; array[71] = new TemplateCardDefinition(new CardSpec("Cold Read", "A whiff is just the setup. The follow-up arrives bigger, faster, and too clean to block on reaction.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats51, CardClass.Arcanist, CardTrack.Signature, 4, "Arcanist", "Miss-reader", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.08f), AbilityCardEffects.MissCharge(1.6f, 1.18f, 1.35f, unblockable: true)); CardInfoStat[] stats52 = new CardInfoStat[4] { CardStats.Positive("First Hit", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +35% Damage", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +10% Bullet Speed", (SimpleAmount)0), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[4] { "mark", "control", "projectile", "tempo" }; array[72] = new TemplateCardDefinition(new CardSpec("Marked Prey", "The first clean hit brands them. The second cashes it in and loads your next shot with the receipt.", (Rarity)1, (CardThemeColorType)3, (CardInfoStat[])(object)stats52, CardClass.Trickster, CardTrack.Payoff, 3, "Trickster", "Tag and cash", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.MarkedPrey(1, 1, 1.35f, 1.1f, 1.1f, 0f)); CardInfoStat[] stats53 = new CardInfoStat[9] { CardStats.Positive("First Hit", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +55% Damage", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +18% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Heal 10% Damage", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "x2, 6m scan", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "Apply Mark", (SimpleAmount)0), CardStats.Positive("Orbit Wards", "Enemy 1.2s +16% Reload Time", (SimpleAmount)0), CardStats.Positive("Bullets", "Spawn a collector on hit", (SimpleAmount)0), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }; draftTags = new string[8] { "mark", "gambler", "survival", "precision", "summon", "disruption", "minion", "helper" }; array[73] = new TemplateCardDefinition(new CardSpec("Collection Agency", "Leave marks on people, let orbiting collectors keep the pressure on, and have your rounds kick out a little helper when the debt gets confirmed.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats53, CardClass.Gambler, CardTrack.Capstone, 5, "Gambler", "Debt collector", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.MarkedPrey(1, 2, 1.55f, 1.18f, 1.22f, 0.1f), AbilityCardEffects.OrbitWard(2, 6f, 2.2f, 1, 1.2f, 0.92f, 1.16f, 1.08f), VanillaCardEffects.AddMinionShot("summon", "disruption")); CardInfoStat[] stats54 = new CardInfoStat[4] { CardStats.Positive("On Kill", "Next shot +45% Damage", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +20% Projectile Size", (SimpleAmount)0), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[4] { "kill", "tempo", "projectile", "brawler" }; array[74] = new TemplateCardDefinition(new CardSpec("Collection Notice", "Kills should pay instantly. Drop someone and the next chamber comes back heavier than the last.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats54, CardClass.Brawler, CardTrack.Payoff, 3, "Brawler", "Kill payout", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.KillCharge(1.45f, 1.12f, 1.2f, unblockable: false)); CardInfoStat[] stats55 = new CardInfoStat[6] { CardStats.Positive("Near Enemy", "Explodes for 10 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.6m", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +55% Damage", (SimpleAmount)0), CardStats.Positive("On Kill", "Next shot +25% Projectile Size", (SimpleAmount)0), CardStats.Positive("On Kill", "Unblockable", (SimpleAmount)0), CardStats.Negative("Bullet Speed", "-8%", (SimpleAmount)1) }; draftTags = new string[4] { "kill", "explosion", "projectile", "demolitionist" }; array[75] = new TemplateCardDefinition(new CardSpec("Red Ledger", "Every confirmed kill loads a wider, meaner follow-up, and your bullets already threaten a small blast zone on the way in.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats55, CardClass.Demolitionist, CardTrack.Signature, 4, "Demolitionist", "Execution shell", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 10f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.6f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.92f), AbilityCardEffects.KillCharge(1.55f, 1.05f, 1.25f, unblockable: true)); CardInfoStat[] stats56 = new CardInfoStat[9] { CardStats.Positive("First Hit", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +40% Damage", (SimpleAmount)0), CardStats.Positive("Marked Hit", "Next shot +12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+35% Damage", (SimpleAmount)0), CardStats.Positive("First Bullet", "+14% Bullet Speed", (SimpleAmount)0), CardStats.Positive("First Bullet", "+18% Projectile Size", (SimpleAmount)0), CardStats.Positive("First Bullet", "Unblockable", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Reload Speed", "+12%", (SimpleAmount)3) }; draftTags = new string[5] { "mark", "reload", "chamber", "gambler", "precision" }; array[76] = new TemplateCardDefinition(new CardSpec("Black Binder", "Brand them, reload, then serve the one shot that actually closes the account.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats56, CardClass.Gambler, CardTrack.Capstone, 5, "Gambler", "Loaded collection capstone", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.12f), AbilityCardEffects.MarkedPrey(1, 1, 1.4f, 1.12f, 1.1f, 0f), AbilityCardEffects.ChamberedRound(1.35f, 1.14f, 1.18f, unblockable: true)); CardInfoStat[] stats57 = new CardInfoStat[5] { CardStats.Positive("Every 4th Shot", "+65% Damage", (SimpleAmount)0), CardStats.Positive("Every 4th Shot", "+40% Projectile Size", (SimpleAmount)0), CardStats.Positive("Every 4th Shot", "Unblockable", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Movement Speed", "-6%", (SimpleAmount)1) }; draftTags = new string[4] { "cadence", "projectile", "demolitionist", "chamber" }; array[77] = new TemplateCardDefinition(new CardSpec("Grand Cylinder", "Every fourth trigger pull loads a shell big enough to feel like a separate weapon mode.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats57, CardClass.Demolitionist, CardTrack.Capstone, 5, "Demolitionist", "Heavy cylinder", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.94f), AbilityCardEffects.CadenceRound(4, 1.65f, 1.1f, 1.4f, unblockable: true)); CardInfoStat[] stats58 = new CardInfoStat[6] { CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Near Enemy", "Explodes for 12 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.7m", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+20%", (SimpleAmount)4), CardStats.Negative("Bullet Speed", "-8%", (SimpleAmount)1), CardStats.Negative("Ammo", "-1", (SimpleAmount)2) }; draftTags = new string[4] { "wall", "explosion", "wildcard", "projectile" }; array[78] = new TemplateCardDefinition(new CardSpec("Open Warrant", "This round ignores cover and still blooms when it gets close. Ducking behind a wall stops being a plan.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats58, CardClass.Wildcard, CardTrack.Wildcard, 4, "Wildcard", "Cover breach explosive", allowMultiple: false, null, null, draftTags), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 12f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.7f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.2f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.92f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1)); CardInfoStat[] stats59 = new CardInfoStat[6] { CardStats.Positive("After Block", "+1 Ammo", (SimpleAmount)0), CardStats.Positive("After Block", "1.75s +12% Move Speed", (SimpleAmount)0), CardStats.Positive("After Block", "1.75s +20% Reload Speed", (SimpleAmount)0), CardStats.Positive("After Block", "Next shot +25% Damage", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+12%", (SimpleAmount)3), CardStats.Negative("Damage", "-5%", (SimpleAmount)1) }; draftTags = new string[4] { "block", "tempo", "reload", "precision" }; array[79] = new TemplateCardDefinition(new CardSpec("Dividend Shield", "A clean block pays three ways: it buys ammo, quickens the reload, and loads a more serious answer.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)stats59, CardClass.Bulwark, CardTrack.Payoff, 3, "Bulwark", "Parry dividend", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.95f), AbilityCardEffects.BlockAmmo(1), AbilityCardEffects.BlockTempo(1.75f, 1.12f, 0.8f), AbilityCardEffects.CounterCharge(1.25f, 1.08f)); CardInfoStat[] stats60 = new CardInfoStat[13] { CardStats.Positive("Perfect Block", "Next shot unblockable", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +60% Damage", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +20% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +45% Projectile Size", (SimpleAmount)0), CardStats.Positive("Bullets", "Carry a shield-charge hit", (SimpleAmount)0), CardStats.Positive("After Block", "2.2s +18% Move Speed", (SimpleAmount)0), CardStats.Positive("After Block", "2.2s +28% Reload Speed", (SimpleAmount)0), CardStats.Positive("After Block", "2.4m field for 2.4s", (SimpleAmount)0), CardStats.Positive("Field", "Enemies -10% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +18% Reload Time", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +12% Block Cooldown", (SimpleAmount)0), CardStats.Positive("Additional Blocks", "+1", (SimpleAmount)0), CardStats.Negative("Movement Speed", "-6%", (SimpleAmount)1) }; draftTags = new string[9] { "block", "parry", "shield", "tempo", "projectile", "precision", "survival", "zone", "hazard" }; array[80] = new TemplateCardDefinition(new CardSpec("Faultless Mirror", "If you are going to block perfectly, the return shot should punish them, carry the shield with it, and leave the space around you hostile for a second after.", (Rarity)2, (CardThemeColorType)1, (CardInfoStat[])(object)stats60, CardClass.Bulwark, CardTrack.Capstone, 5, "Bulwark", "Perfect parry capstone", allowMultiple: false, null, null, draftTags), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.94f), AbilityCardEffects.PerfectParry(0.22f, 1.6f, 1.2f, 1.45f, unblockable: true), AbilityCardEffects.BlockTempo(2.2f, 1.18f, 0.72f), AbilityCardEffects.BlockZone(2.4f, 2.4f, 0.45f, 1, 0.9f, 1.18f, 1.12f), VanillaCardEffects.AddShieldChargeShot("block", "summon", "weapon_mode")); CardInfoStat[] stats61 = new CardInfoStat[5] { CardStats.Positive("Last Bullet", "+55% Damage", (SimpleAmount)0), CardStats.Positive("Last Bullet", "+12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Last Bullet", "+35% Projectile Size", (SimpleAmount)0), CardStats.Positive("Last Bullet", "Unblockable", (SimpleAmount)0), CardStats.Negative("Reload Speed", "+12%", (SimpleAmount)3) }; draftTags = new string[4] { "reload", "chamber", "burst", "precision" }; array[81] = new TemplateCardDefinition(new CardSpec("Break Action", "Treat the last shell like a separate firing mode: huge, loud, and worth the whole magazine setup.", (Rarity)1, (CardThemeColorType)0, (CardInfoStat[])(object)stats61, CardClass.Marksman, CardTrack.Payoff, 3, "Marksman", "Last-shell mode", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.12f), AbilityCardEffects.LastShell(1.55f, 1.12f, 1.35f, unblockable: true)); CardInfoStat[] stats62 = new CardInfoStat[6] { CardStats.Positive("Last Bullet", "+45% Damage", (SimpleAmount)0), CardStats.Positive("Last Bullet", "+25% Projectile Size", (SimpleAmount)0), CardStats.Positive("On Empty Mag", "2s +20% Move Speed", (SimpleAmount)0), CardStats.Positive("On Empty Mag", "2s +30% Reload Speed", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Spread", "+8%", (SimpleAmount)3) }; draftTags = new string[5] { "reload", "chamber", "tempo", "burst", "wildcard" }; array[82] = new TemplateCardDefinition(new CardSpec("Afterburn Magazine", "Spend the last shell like a weapon mode, then use the empty gun as permission to sprint into the reload.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats62, CardClass.Wildcard, CardTrack.Wildcard, 4, "Wildcard", "Burnout mode", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.AddGunFloat(GunFloatStatKey.Spread, 0.08f), AbilityCardEffects.LastShell(1.45f, 1.05f, 1.25f, unblockable: false), AbilityCardEffects.EmptyMagRush(2f, 1.2f, 0.7f)); CardInfoStat[] stats63 = new CardInfoStat[11] { CardStats.Positive("Reflects", "+1", (SimpleAmount)0), CardStats.Positive("Random Bounce", "+1", (SimpleAmount)0), CardStats.Positive("Near Enemy", "Explodes for 12 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.8m", (SimpleAmount)0), CardStats.Positive("On Damage", "Spawn a follow-up object", (SimpleAmount)0), CardStats.Positive("On Kill", "2.8m field for 3.2s", (SimpleAmount)0), CardStats.Positive("Field", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Field", "Enemies -10% Move Speed", (SimpleAmount)0), CardStats.Positive("Field", "Enemies +16% Reload Time", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+18%", (SimpleAmount)4), CardStats.Negative("Damage", "-8%", (SimpleAmount)1) }; draftTags = new string[8] { "explosion", "bounce", "projectile", "control", "wildcard", "zone", "hazard", "spawn" }; array[83] = new TemplateCardDefinition(new CardSpec("Lease Minefield", "Every near miss rents space. Once the shots start bouncing, dealing damage throws real spawned junk into the room and kills turn the whole stage into a bad lease.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats63, CardClass.Wildcard, CardTrack.Wildcard, 4, "Wildcard", "Hazard bounce", allowMultiple: false, null, null, draftTags), VanillaCardEffects.AddRoomBounce("precision"), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.AddGunInt(GunIntStatKey.RandomBounces, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.18f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 12f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.8f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 6f), VanillaCardEffects.AddDamageSpawnShot("hazard", "summon", "disruption"), AbilityCardEffects.KillZone(3.2f, 2.8f, 0.45f, 1, 0.9f, 1.16f, 1.08f)); CardInfoStat[] stats64 = new CardInfoStat[7] { CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Near Enemy", "Explodes for 14 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.9m", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+24%", (SimpleAmount)4), CardStats.Positive("Bullet Life", "+1.5s", (SimpleAmount)0), CardStats.Negative("Ammo", "-1", (SimpleAmount)2), CardStats.Negative("Bullet Speed", "-8%", (SimpleAmount)1) }; draftTags = new string[5] { "explosion", "wall", "projectile", "control", "demolitionist" }; array[84] = new TemplateCardDefinition(new CardSpec("Closed Circuit", "The shell ignores walls, blooms when it gets close, and makes a bad position stay bad after the first dodge.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats64, CardClass.Demolitionist, CardTrack.Capstone, 5, "Demolitionist", "Hazard breach capstone", allowMultiple: false, null, null, draftTags), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 14f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.9f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.24f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 1.5f), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, -1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 0.92f)); CardInfoStat[] stats65 = new CardInfoStat[5] { CardStats.Positive("Below 40% HP", "+25% Damage", (SimpleAmount)0), CardStats.Positive("Below 40% HP", "+15% Move Speed", (SimpleAmount)0), CardStats.Positive("Below 40% HP", "+22% Reload Speed", (SimpleAmount)0), CardStats.Positive("Below 40% HP", "Heal 5% Damage", (SimpleAmount)0), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }; draftTags = new string[4] { "survival", "tempo", "brawler", "health" }; array[85] = new TemplateCardDefinition(new CardSpec("Last Stand", "When your health gets ugly, the gun stops being polite and starts trying to drag you back into the round.", (Rarity)1, (CardThemeColorType)4, (CardInfoStat[])(object)stats65, CardClass.Brawler, CardTrack.Payoff, 3, "Brawler", "Low-health stance", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), AbilityCardEffects.LowHealthStance(0.4f, 1.25f, 1.15f, 0.78f, 0.05f)); CardInfoStat[] stats66 = new CardInfoStat[4] { CardStats.Positive("After Dying", "Next round 6s +35% Damage", (SimpleAmount)0), CardStats.Positive("After Dying", "Next round 6s +15% Move Speed", (SimpleAmount)0), CardStats.Positive("After Dying", "Next round 6s +28% Reload Speed", (SimpleAmount)0), CardStats.Negative("HP", "-6%", (SimpleAmount)1) }; draftTags = new string[4] { "survival", "comeback", "tempo", "gambler" }; array[86] = new TemplateCardDefinition(new CardSpec("Burial Clause", "If a round ends with you face down, the next one starts with a clause in your favor.", (Rarity)2, (CardThemeColorType)2, (CardInfoStat[])(object)stats66, CardClass.Gambler, CardTrack.Signature, 4, "Gambler", "Comeback clause", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.DeathVow(6f, 1.35f, 1.15f, 0.72f, 2)); CardInfoStat[] stats67 = new CardInfoStat[7] { CardStats.Positive("Below 45% HP", "+22% Damage", (SimpleAmount)0), CardStats.Positive("Below 45% HP", "+12% Move Speed", (SimpleAmount)0), CardStats.Positive("Below 45% HP", "+18% Reload Speed", (SimpleAmount)0), CardStats.Positive("After Dying", "Next round 6s +25% Damage", (SimpleAmount)0), CardStats.Positive("After Dying", "Next round 6s +12% Move Speed", (SimpleAmount)0), CardStats.Positive("After Dying", "Next round 6s +20% Reload Speed", (SimpleAmount)0), CardStats.Negative("HP", "-10%", (SimpleAmount)2) }; draftTags = new string[5] { "survival", "comeback", "tempo", "gambler", "health" }; array[87] = new TemplateCardDefinition(new CardSpec("Wake Clause", "The build that refuses to stay dead: go low and get mean, die and come back even meaner.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats67, CardClass.Gambler, CardTrack.Capstone, 5, "Gambler", "Wake-up capstone", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.9f), AbilityCardEffects.LowHealthStance(0.45f, 1.22f, 1.12f, 0.82f, 0.04f), AbilityCardEffects.DeathVow(6f, 1.25f, 1.12f, 0.8f, 2)); CardInfoStat[] stats68 = new CardInfoStat[4] { CardStats.Positive("Perfect Block", "Next shot +30% Damage", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +10% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Perfect Block", "Next shot +18% Projectile Size", (SimpleAmount)0), CardStats.Negative("Block Cooldown", "+10%", (SimpleAmount)3) }; draftTags = new string[5] { "block", "parry", "tempo", "precision", "bridge" }; array[88] = new TemplateCardDefinition(new CardSpec("Blue Window", "A small real parry card. Catch the block right as it comes back and the next shot gains enough shape to matter.", (Rarity)0, (CardThemeColorType)1, (CardInfoStat[])(object)stats68, CardClass.Bulwark, CardTrack.Core, 2, "Bulwark", "Parry bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyBlockFloat(BlockFloatStatKey.CooldownMultiplier, 1.1f), AbilityCardEffects.PerfectParry(0.22f, 1.3f, 1.1f, 1.18f, unblockable: false)); CardInfoStat[] stats69 = new CardInfoStat[4] { CardStats.Positive("Grounded", "+22% Damage", (SimpleAmount)0), CardStats.Positive("Grounded", "+18% Reload Speed", (SimpleAmount)0), CardStats.Positive("HP", "+10%", (SimpleAmount)3), CardStats.Negative("Movement Speed", "-6%", (SimpleAmount)1) }; draftTags = new string[5] { "ground", "stance", "reload", "bulwark", "tempo" }; array[89] = new TemplateCardDefinition(new CardSpec("Set Anchor", "Stop drifting. Standing your ground should actually load power into the gun instead of only into the stat sheet.", (Rarity)1, (CardThemeColorType)1, (CardInfoStat[])(object)stats69, CardClass.Bulwark, CardTrack.Payoff, 3, "Bulwark", "Grounded stance", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.1f), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.94f), AbilityCardEffects.GroundedStance(1.22f, 1f, 0.82f)); CardInfoStat[] stats70 = new CardInfoStat[4] { CardStats.Positive("Rapid Shots", "+8% Damage per stack", (SimpleAmount)0), CardStats.Positive("Rapid Shots", "+4% Bullet Speed per stack", (SimpleAmount)0), CardStats.Negative("Overheat", "1.4s +28% Reload Time", (SimpleAmount)0), CardStats.Negative("Overheat", "+10% Spread", (SimpleAmount)0) }; draftTags = new string[5] { "heat", "overheat", "tempo", "precision", "weapon mode" }; array[90] = new TemplateCardDefinition(new CardSpec("Flash Coil", "Shoot fast enough and the barrel starts paying you back. Stay greedy too long and it pays you back differently.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)stats70, CardClass.Marksman, CardTrack.Core, 2, "Marksman", "Heat bridge", allowMultiple: false, null, null, draftTags), AbilityCardEffects.HeatMode(4, 0.95f, 1.08f, 1.04f, 1.4f, 1.28f, 0.1f)); CardInfoStat[] stats71 = new CardInfoStat[4] { CardStats.Positive("Near Wall", "+16% Damage", (SimpleAmount)0), CardStats.Positive("Near Wall", "+12% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Movement Speed", "+6%", (SimpleAmount)3), CardStats.Negative("HP", "-6%", (SimpleAmount)1) }; draftTags = new string[5] { "wall", "stance", "tempo", "skirmisher", "positioning" }; array[91] = new TemplateCardDefinition(new CardSpec("Wall Script", "Hugging cover should do more than save you. Make the wall itself part of the firing plan.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats71, CardClass.Skirmisher, CardTrack.Core, 2, "Skirmisher", "Wall bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 1.06f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.WallStance(0.8f, 1.16f, 1.12f, 1f)); CardInfoStat[] stats72 = new CardInfoStat[4] { CardStats.Positive("Near Enemy", "Explodes for 6 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.1m", (SimpleAmount)0), CardStats.Positive("Bullet Life", "+0.7s", (SimpleAmount)0), CardStats.Negative("Damage", "-5%", (SimpleAmount)1) }; draftTags = new string[5] { "explosion", "hazard", "projectile", "control", "demolitionist" }; array[92] = new TemplateCardDefinition(new CardSpec("Dust Charge", "A small hazard bridge. Near misses should still pressure space even before the giant demolition cards show up.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats72, CardClass.Demolitionist, CardTrack.Core, 2, "Demolitionist", "Hazard bridge", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 6f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.1f), CardEffects.AddGunFloat(GunFloatStatKey.DestroyBulletAfter, 0.7f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.95f)); CardInfoStat[] stats73 = new CardInfoStat[5] { CardStats.Positive("Near Wall", "+22% Damage", (SimpleAmount)0), CardStats.Positive("Near Wall", "+16% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Near Wall", "+18% Reload Speed", (SimpleAmount)0), CardStats.Positive("Reflects", "+1", (SimpleAmount)0), CardStats.Negative("Damage", "-6%", (SimpleAmount)1) }; draftTags = new string[5] { "wall", "stance", "bounce", "tempo", "trickster" }; array[93] = new TemplateCardDefinition(new CardSpec("Corner Doctrine", "Once the wall is close, the whole weapon changes posture. It stops being a fallback and starts being a battery.", (Rarity)1, (CardThemeColorType)3, (CardInfoStat[])(object)stats73, CardClass.Trickster, CardTrack.Payoff, 3, "Trickster", "Wall payoff", allowMultiple: false, null, null, draftTags), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), AbilityCardEffects.WallStance(0.95f, 1.22f, 1.16f, 0.82f)); CardInfoStat[] stats74 = new CardInfoStat[5] { CardStats.Positive("Grounded", "+34% Damage", (SimpleAmount)0), CardStats.Positive("Grounded", "+26% Reload Speed", (SimpleAmount)0), CardStats.Positive("HP", "+12%", (SimpleAmount)3), CardStats.Positive("Additional Blocks", "+1", (SimpleAmount)0), CardStats.Negative("Movement Speed", "-8%", (SimpleAmount)1) }; draftTags = new string[6] { "ground", "stance", "block", "reload", "bulwark", "capstone" }; array[94] = new TemplateCardDefinition(new CardSpec("Stone Sermon", "The true grounded capstone: plant yourself, load the room into the chamber, and make the next clean angle count harder.", (Rarity)2, (CardThemeColorType)1, (CardInfoStat[])(object)stats74, CardClass.Bulwark, CardTrack.Signature, 4, "Bulwark", "Grounded capstone bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 1.12f), CardEffects.AddBlockInt(BlockIntStatKey.AdditionalBlocks, 1), CardEffects.MultiplyCharacterModifierFloat(CharacterModifierFloatStatKey.MovementSpeed, 0.92f), AbilityCardEffects.GroundedStance(1.34f, 1f, 0.74f)); CardInfoStat[] stats75 = new CardInfoStat[6] { CardStats.Positive("Near Wall", "+35% Damage", (SimpleAmount)0), CardStats.Positive("Near Wall", "+22% Bullet Speed", (SimpleAmount)0), CardStats.Positive("Near Wall", "+25% Reload Speed", (SimpleAmount)0), CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+18%", (SimpleAmount)4), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }; draftTags = new string[6] { "wall", "stance", "projectile", "arcanist", "precision", "capstone" }; array[95] = new TemplateCardDefinition(new CardSpec("Backwall Gospel", "A real wall capstone. If you fight from cover, the gun becomes faster, meaner, and rude enough to start ignoring the cover entirely.", (Rarity)2, (CardThemeColorType)3, (CardInfoStat[])(object)stats75, CardClass.Arcanist, CardTrack.Capstone, 5, "Arcanist", "Wall sermon capstone", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.18f), AbilityCardEffects.WallStance(1.05f, 1.35f, 1.22f, 0.75f)); CardInfoStat[] stats76 = new CardInfoStat[6] { CardStats.Positive("Rapid Shots", "+12% Damage per stack", (SimpleAmount)0), CardStats.Positive("Rapid Shots", "+6% Bullet Speed per stack", (SimpleAmount)0), CardStats.Positive("Ammo", "+1", (SimpleAmount)0), CardStats.Positive("Damage", "+10%", (SimpleAmount)3), CardStats.Negative("Overheat", "1.75s +40% Reload Time", (SimpleAmount)0), CardStats.Negative("Overheat", "+16% Spread", (SimpleAmount)0) }; draftTags = new string[6] { "heat", "overheat", "tempo", "brawler", "weapon mode", "capstone" }; array[96] = new TemplateCardDefinition(new CardSpec("Boiler Room", "The heat capstone. Keep pouring shots through it and the barrel turns into a real weapon mode until it finally punishes you for the greed.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats76, CardClass.Brawler, CardTrack.Capstone, 5, "Brawler", "Heat capstone", allowMultiple: false, null, null, draftTags), CardEffects.AdjustGunAmmoInt(GunAmmoIntStatKey.MaxAmmo, 1, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), AbilityCardEffects.HeatMode(5, 1.05f, 1.12f, 1.06f, 1.75f, 1.4f, 0.16f)); CardInfoStat[] stats77 = new CardInfoStat[4] { CardStats.Positive("Bullets", "Blink toward target lane", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+10%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[5] { "teleport", "trickster", "precision", "weapon mode", "projectile" }; array[97] = new TemplateCardDefinition(new CardSpec("Skip Trace", "Borrow a real blink-shot rule early. The round cuts into the target lane once, which gives trickster starts a visible mechanic before the draft reaches the loud payoff cards.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats77, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Blink bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), VanillaCardEffects.AddTeleportShot("weapon_mode", "precision")); CardInfoStat[] stats78 = new CardInfoStat[5] { CardStats.Positive("Orbit Ward", "1 ward, 4.8m scan", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 0.9s -8% Move Speed", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("HP", "-5%", (SimpleAmount)1) }; draftTags = new string[6] { "summon", "orbit", "mark", "arcanist", "control", "wall" }; array[98] = new TemplateCardDefinition(new CardSpec("Lintel Wisp", "A small arcanist familiar that helps own space instead of waiting for the build to hit giant signatures. It tags intruders and bends the room a little in your favor.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats78, CardClass.Arcanist, CardTrack.Core, 2, "Arcanist", "Summon bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.95f), AbilityCardEffects.OrbitWard(1, 4.8f, 2.5f, 1, 0.9f, 0.92f, 1f, 1f)); CardInfoStat[] stats79 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Blink toward target lane", (SimpleAmount)0), CardStats.Positive("Bullets", "Ignore Walls", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+12%", (SimpleAmount)4), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("HP", "-6%", (SimpleAmount)2) }; draftTags = new string[5] { "teleport", "wall", "arcanist", "precision", "weapon mode" }; array[99] = new TemplateCardDefinition(new CardSpec("Lintel Key", "Turn wall play into a real mid-tier rule. The shot blinks through the room once, ignores cover, and lands big enough that the angle matters.", (Rarity)1, (CardThemeColorType)3, (CardInfoStat[])(object)stats79, CardClass.Arcanist, CardTrack.Payoff, 3, "Arcanist", "Warp payoff", allowMultiple: false, null, null, draftTags), CardEffects.SetGunBool(GunBoolStatKey.IgnoreWalls, value: true), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.12f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), VanillaCardEffects.AddTeleportShot("weapon_mode", "stance", "precision")); CardInfoStat[] stats80 = new CardInfoStat[4] { CardStats.Positive("Bullets", "Carry an orbiting echo", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+10%", (SimpleAmount)4), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2) }; draftTags = new string[5] { "orbit", "summon", "projectile", "arcanist", "weapon mode" }; array[100] = new TemplateCardDefinition(new CardSpec("Halo Writ", "A low-tier orbit port. The projectile carries an orbiting echo, which makes even a small arcanist start feel like it owns a little extra space.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats80, CardClass.Arcanist, CardTrack.Core, 2, "Arcanist", "Orbit bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.1f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), VanillaCardEffects.AddOrbitShot("summon", "weapon_mode")); CardInfoStat[] stats81 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Spawn a small helper on hit", (SimpleAmount)0), CardStats.Positive("Projectile Size", "+6%", (SimpleAmount)3), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-10%", (SimpleAmount)2), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[6] { "minion", "helper", "summon", "arcanist", "projectile", "control" }; array[101] = new TemplateCardDefinition(new CardSpec("Clerk Imp", "A low-tier helper port. The shot spawns a little minion rule, which makes early arcanist starts feel inhabited before the big signatures show up.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats81, CardClass.Arcanist, CardTrack.Core, 2, "Arcanist", "Minion bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), VanillaCardEffects.AddMinionShot("summon", "weapon_mode")); CardInfoStat[] stats82 = new CardInfoStat[4] { CardStats.Positive("Bullets", "Reflect on first hit", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-6%", (SimpleAmount)2), CardStats.Negative("Spread", "+6%", (SimpleAmount)2) }; draftTags = new string[5] { "reflect", "demolitionist", "projectile", "precision", "hazard" }; array[102] = new TemplateCardDefinition(new CardSpec("Mirror Fuse", "Steal a real reflect behavior at the low end instead of faking it with more math. The shell checks back after first contact and changes the whole line.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats82, CardClass.Demolitionist, CardTrack.Core, 2, "Demolitionist", "Reflect bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.94f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Spread, 1.06f), VanillaCardEffects.AddReflectShot("precision", "weapon_mode")); CardInfoStat[] stats83 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Leave a static remnant on contact", (SimpleAmount)0), CardStats.Positive("Near Enemy", "Explodes for 8 damage", (SimpleAmount)0), CardStats.Positive("Explosion Radius", "1.35m", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2) }; draftTags = new string[5] { "remnant", "hazard", "spawn", "demolitionist", "projectile" }; array[103] = new TemplateCardDefinition(new CardSpec("Ash Deed", "A low-tier remnant port. Even the first clean hit can leave a bad little object behind, which is exactly what demolitionist bridges should do.", (Rarity)0, (CardThemeColorType)4, (CardInfoStat[])(object)stats83, CardClass.Demolitionist, CardTrack.Core, 2, "Demolitionist", "Remnant bridge", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 8f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.35f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), VanillaCardEffects.AddStaticRemnantShot("hazard", "weapon_mode")); CardInfoStat[] stats84 = new CardInfoStat[4] { CardStats.Positive("Bullets", "Trigger a reflect event on hit", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Projectile Size", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-10%", (SimpleAmount)2) }; draftTags = new string[5] { "reflect", "trickster", "precision", "weapon mode", "projectile" }; array[104] = new TemplateCardDefinition(new CardSpec("Return Filing", "A lower-end mirror port. The round throws a reflection event on contact, which gives trickster runs another real way to turn bad lines into live ones.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats84, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Mirror event bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.9f), VanillaCardEffects.AddReflectEventShot("precision", "weapon_mode")); CardInfoStat[] stats85 = new CardInfoStat[6] { CardStats.Positive("Bullets", "Reflect on first hit", (SimpleAmount)0), CardStats.Positive("Bullets", "Leave a static remnant on contact", (SimpleAmount)0), CardStats.Positive("Explosion Damage", "+25%", (SimpleAmount)4), CardStats.Positive("Explosion Range", "+18%", (SimpleAmount)3), CardStats.Positive("Damage", "+10%", (SimpleAmount)3), CardStats.Negative("Reload Speed", "+8%", (SimpleAmount)3) }; draftTags = new string[7] { "reflect", "demolitionist", "explosion", "hazard", "signature", "remnant", "spawn" }; array[105] = new TemplateCardDefinition(new CardSpec("Shatter Ledger", "A real demolitionist signature. The round reflects on first contact, leaves bad debris behind, and turns that crooked path into a live explosion threat instead of only a numbers upgrade.", (Rarity)2, (CardThemeColorType)4, (CardInfoStat[])(object)stats85, CardClass.Demolitionist, CardTrack.Signature, 4, "Demolitionist", "Reflect demolition signature", allowMultiple: false, null, null, draftTags), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionDamage, 12f), CardEffects.AddGunFloat(GunFloatStatKey.ProximityExplosionRange, 1.7f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 1.1f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.08f), VanillaCardEffects.AddReflectShot("precision", "hazard", "weapon_mode"), VanillaCardEffects.AddStaticRemnantShot("hazard", "weapon_mode")); CardInfoStat[] stats86 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Drill through first hit", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Positive("Damage Falloff", "-10%", (SimpleAmount)1), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("Reload Speed", "+6%", (SimpleAmount)3) }; draftTags = new string[5] { "drill", "precision", "range", "marksman", "weapon mode" }; array[106] = new TemplateCardDefinition(new CardSpec("Bore Sight", "A low-tier lane tool. The round drills once instead of dying cleanly, which gives marksman starts a real geometry rule before the run reaches full signatures.", (Rarity)0, (CardThemeColorType)0, (CardInfoStat[])(object)stats86, CardClass.Marksman, CardTrack.Core, 2, "Marksman", "Drill bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.DamageAfterDistanceMultiplier, 0.9f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 1.06f), VanillaCardEffects.AddDrillShot("precision", "weapon_mode")); CardInfoStat[] stats87 = new CardInfoStat[4] { CardStats.Positive("Reflects", "+1", (SimpleAmount)0), CardStats.Positive("Bounce", "Retargets after first contact", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+8%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2) }; draftTags = new string[5] { "bounce", "reflect", "trickster", "precision", "weapon mode" }; array[107] = new TemplateCardDefinition(new CardSpec("Appeal Route", "A real trickster bridge: let the first bad bounce ask the room for another angle instead of accepting the miss.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats87, CardClass.Trickster, CardTrack.Core, 2, "Trickster", "Retarget bridge", allowMultiple: false, null, null, draftTags), CardEffects.AddGunInt(GunIntStatKey.Reflects, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.08f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.EnsureMinimumGunFloat(GunFloatStatKey.DestroyBulletAfter, 5.5f), VanillaCardEffects.AddRetargetBounce("precision", "weapon_mode")); CardInfoStat[] stats88 = new CardInfoStat[4] { CardStats.Positive("Orbit Ward", "1 ward, 5m scan", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Applies Mark", (SimpleAmount)0), CardStats.Positive("Orbit Ward", "Enemy 1s +10% Reload Time", (SimpleAmount)0), CardStats.Negative("HP", "-6%", (SimpleAmount)2) }; draftTags = new string[5] { "summon", "orbit", "mark", "wildcard", "disruption" }; array[108] = new TemplateCardDefinition(new CardSpec("House Sprite", "A small summoned-helper bridge. Let a familiar do a little quiet work nearby instead of forcing every mechanic through your trigger finger.", (Rarity)0, (CardThemeColorType)3, (CardInfoStat[])(object)stats88, CardClass.Wildcard, CardTrack.Wildcard, 2, "Wildcard", "Summon bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.94f), AbilityCardEffects.OrbitWard(1, 5f, 2.8f, 1, 1f, 1f, 1.1f, 1.04f)); CardInfoStat[] stats89 = new CardInfoStat[5] { CardStats.Positive("Bullets", "Spawn a follow-up object on damage", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+6%", (SimpleAmount)3), CardStats.Positive("Projectile Size", "+6%", (SimpleAmount)3), CardStats.Negative("Damage", "-8%", (SimpleAmount)2), CardStats.Negative("HP", "-5%", (SimpleAmount)1) }; draftTags = new string[6] { "spawn", "hazard", "summon", "gambler", "projectile", "disruption" }; array[109] = new TemplateCardDefinition(new CardSpec("Claims Runner", "A low-tier spawned-object port. Confirm damage and the shot throws one more little problem into the room instead of cashing out as plain numbers.", (Rarity)0, (CardThemeColorType)2, (CardInfoStat[])(object)stats89, CardClass.Gambler, CardTrack.Core, 2, "Gambler", "Spawn bridge", allowMultiple: false, null, null, draftTags), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSize, 1.06f), CardEffects.MultiplyGunFloat(GunFloatStatKey.Damage, 0.92f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.95f), VanillaCardEffects.AddDamageSpawnShot("hazard", "summon", "disruption")); CardInfoStat[] stats90 = new CardInfoStat[4] { CardStats.Positive("Next 2 Round Drafts", "+1 Pick", (SimpleAmount)0), CardStats.Positive("Bullet Speed", "+10%", (SimpleAmount)3), CardStats.Positive("Reload Speed", "+10%", (SimpleAmount)3), CardStats.Negative("HP", "-8%", (SimpleAmount)2) }; draftTags = new string[4] { "draft", "economy", "wildcard", "tempo" }; array[110] = new TemplateCardDefinition(new CardSpec("Second Salary", "The next two round drafts both pay out an extra pick, and your day-to-day gun gets just a little cleaner meanwhile.", (Rarity)2, (CardThemeColorType)2, (CardInfoStat[])(object)stats90, CardClass.Wildcard, CardTrack.Wildcard, 4, "Wildcard", "Draft economy spike", allowMultiple: false, null, null, draftTags), DraftCardEffects.FutureExtraPicks(2, 1), CardEffects.MultiplyGunFloat(GunFloatStatKey.ProjectileSpeed, 1.1f), CardEffects.MultiplyGunAmmoFloat(GunAmmoFloatStatKey.ReloadTimeMultiplier, 0.9f), CardEffects.MultiplyCharacterDataFloat(CharacterDataFloatStatKey.MaxHealth, 0.92f)); Definitions = array; StandardTracks = new CardTrack[10] { CardTrack.Starter, CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Payoff, CardTrack.Payoff, CardTrack.Signature, CardTrack.Signature, CardTrack.Capstone }; StandardTiers = new int[10] { 1, 1, 2, 2, 2, 3, 3, 4, 4, 5 }; FoundationTracks = new CardTrack[10] { CardTrack.Starter, CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core }; FoundationTiers = new int[10] { 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 }; Rarity[] array2 = new Rarity[10]; RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); FoundationRarities = (Rarity[])(object)array2; Rarity[] array3 = new Rarity[10]; RuntimeHelpers.InitializeArray(array3, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); StandardRarities = (Rarity[])(object)array3; SevenTracks = new CardTrack[7] { CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Payoff, CardTrack.Payoff, CardTrack.Signature, CardTrack.Capstone }; SevenTiers = new int[7] { 1, 2, 2, 3, 3, 4, 5 }; FoundationSevenTracks = new CardTrack[7] { CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core }; FoundationSevenTiers = new int[7] { 1, 2, 2, 2, 2, 2, 2 }; Rarity[] array4 = new Rarity[7]; RuntimeHelpers.InitializeArray(array4, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); FoundationSevenRarities = (Rarity[])(object)array4; Rarity[] array5 = new Rarity[7]; RuntimeHelpers.InitializeArray(array5, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); SevenRarities = (Rarity[])(object)array5; EightTracks = new CardTrack[8] { CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Payoff, CardTrack.Payoff, CardTrack.Signature, CardTrack.Capstone }; EightTiers = new int[8] { 1, 2, 2, 2, 3, 3, 4, 5 }; FoundationEightTracks = new CardTrack[8] { CardTrack.Starter, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core, CardTrack.Core }; FoundationEightTiers = new int[8] { 1, 2, 2, 2, 2, 2, 2, 2 }; Rarity[] array6 = new Rarity[8]; RuntimeHelpers.InitializeArray(array6, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); FoundationEightRarities = (Rarity[])(object)array6; Rarity[] array7 = new Rarity[8]; RuntimeHelpers.InitializeArray(array7, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); EightRarities = (Rarity[])(object)array7; } } } namespace RoundsPartyPack.Art { internal static class CardArtFactory { private static readonly Type Vector2Type = typeof(GameObject).Assembly.GetType("UnityEngine.Vector2"); private static readonly ConstructorInfo Vector2Constructor = Vector2Type?.GetConstructor(new Type[2] { typeof(float), typeof(float) }); private static readonly MethodInfo AddComponentMethod = typeof(GameObject).GetMethod("AddComponent", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(Type) }, null); private static readonly PropertyInfo FontSizeProperty = typeof(TextMeshProUGUI).GetProperty("fontSize", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo AlignmentProperty = typeof(TextMeshProUGUI).GetProperty("alignment", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo OverflowModeProperty = typeof(TextMeshProUGUI).GetProperty("overflowMode", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo EnableWordWrappingProperty = typeof(TextMeshProUGUI).GetProperty("enableWordWrapping", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo RectTransformProperty = typeof(TextMeshProUGUI).GetProperty("rectTransform", BindingFlags.Instance | BindingFlags.Public); internal static GameObject Create(CardSpec spec) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (spec == null) { return null; } GameObject val = new GameObject { name = "BetterRoundsArt" }; object? obj = AddComponentMethod?.Invoke(val, new object[1] { typeof(TextMeshProUGUI) }); TextMeshProUGUI val2 = (TextMeshProUGUI)((obj is TextMeshProUGUI) ? obj : null); if (val2 == null) { return null; } val2.text = spec.ShortLabel; val2.color = ResolveAccent(spec.Theme); SetPropertyIfPresent(val2, FontSizeProperty, 90f); SetEnumPropertyIfPresent(val2, AlignmentProperty, 514); SetEnumPropertyIfPresent(val2, OverflowModeProperty, 0); SetPropertyIfPresent(val2, EnableWordWrappingProperty, false); ConfigureRectTransform(val2); return val; } private static void ConfigureRectTransform(TextMeshProUGUI text) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) object obj = RectTransformProperty?.GetValue(text, null); if (obj != null) { object value = CreateVector2(0.5f, 0.5f); object value2 = CreateVector2(360f, 180f); SetObjectPropertyIfPresent(obj, "anchorMin", value); SetObjectPropertyIfPresent(obj, "anchorMax", value); SetObjectPropertyIfPresent(obj, "pivot", value); SetObjectPropertyIfPresent(obj, "sizeDelta", value2); object obj2 = obj.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public)?.GetValue(obj, null); if (obj2 != null) { SetObjectPropertyIfPresent(obj2, "localScale", Vector3.one); SetObjectPropertyIfPresent(obj2, "localPosition", (object)new Vector3(0f, 35f, 0f)); SetObjectPropertyIfPresent(obj2, "localEulerAngles", Vector3.zero); } } } private static void SetEnumPropertyIfPresent(object target, PropertyInfo property, int rawValue) { if (target != null && !(property == null) && property.CanWrite) { object value = Enum.ToObject(property.PropertyType, rawValue); property.SetValue(target, value, null); } } private static void SetPropertyIfPresent(object target, PropertyInfo property, object value) { if (target != null && !(property == null) && property.CanWrite) { property.SetValue(target, value, null); } } private static void SetObjectPropertyIfPresent(object target, string propertyName, object value) { if (target != null) { PropertyInfo property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public); if (!(property == null) && property.CanWrite) { property.SetValue(target, value, null); } } } private static object CreateVector2(float x, float y) { if (Vector2Constructor == null) { return null; } return Vector2Constructor.Invoke(new object[2] { x, y }); } private static Color ResolveAccent(CardThemeColorType theme) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) return (Color)((int)theme switch { 0 => new Color(0.94f, 0.77f, 0.21f, 1f), 1 => new Color(0.28f, 0.57f, 0.89f, 1f), 4 => new Color(0.89f, 0.34f, 0.28f, 1f), 3 => new Color(0.86f, 0.42f, 0.78f, 1f), _ => new Color(0.76f, 0.82f, 0.88f, 1f), }); } } } namespace RoundsPartyPack.Abilities { internal static class AbilityGameModeHooks { private static bool _initialized; internal static void Initialize() { if (!_initialized) { _initialized = true; GameModeManager.AddHook("PointEnd", (Func)NotifyPointEnded, 0); GameModeManager.AddHook("RoundStart", (Func)NotifyRoundStarted, 800); GameModeManager.AddHook("GameStart", (Func)ResetSessionState, 800); GameModeManager.AddHook("GameEnd", (Func)ResetSessionState, 800); } } private static IEnumerator NotifyPointEnded(IGameModeHandler gameModeHandler) { foreach (Player item in PlayerManager.instance?.players?.Where((Player player) => player != null) ?? Enumerable.Empty()) { PlayerAbilityController.GetOrAdd(item)?.NotifyPointEnded(); } yield break; } private static IEnumerator NotifyRoundStarted(IGameModeHandler gameModeHandler) { foreach (Player item in PlayerManager.instance?.players?.Where((Player player) => player != null) ?? Enumerable.Empty()) { PlayerAbilityController.GetOrAdd(item)?.NotifyRoundStarted(); } yield break; } private static IEnumerator ResetSessionState(IGameModeHandler gameModeHandler) { foreach (Player item in PlayerManager.instance?.players?.Where((Player player) => player != null) ?? Enumerable.Empty()) { PlayerAbilityController.GetOrAdd(item)?.ResetSessionState(); } yield break; } } internal sealed class BeaconNodeController : MonoBehaviour { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private Player _owner; private float _expiresAt; private float _scanRange; private float _pulseInterval; private float _nextPulseAt; private int _marksApplied; private float _exposureDuration; private float _exposureDamageMultiplier = 1f; private float _disruptionDuration; private float _movementMultiplier = 1f; private float _reloadMultiplier = 1f; private float _blockCooldownMultiplier = 1f; private float _pulseAccentUntil; private float _lastCueAt; private Color _baseColor = new Color(1f, 0.84f, 0.28f, 0.84f); private Vector3 _originPosition; internal static void Spawn(Player owner, Vector3 position, float duration, float scanRange, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (owner != null && !(duration <= 0.05f) && !(scanRange <= 0.25f)) { GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); GameObject val2 = (GameObject)((val != null) ? ((object)Object.Instantiate(val, position, default(Quaternion), (Transform)null)) : ((object)new GameObject())); if (val2 != null) { ((Object)val2).name = "BetterRoundsBeaconNode"; val2.transform.position = position; ExtensionMethods.GetOrAddComponent(val2, false).Initialize(owner, duration, scanRange, pulseInterval, marksApplied, exposureDuration, exposureDamageMultiplier, disruptionDuration, movementMultiplier, reloadMultiplier, blockCooldownMultiplier, color); } } } private void Initialize(Player owner, float duration, float scanRange, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) _owner = owner; _expiresAt = Time.unscaledTime + duration; _scanRange = Mathf.Max(0.6f, scanRange); _pulseInterval = Mathf.Max(0.25f, pulseInterval); _nextPulseAt = Time.unscaledTime + 0.05f; _marksApplied = Math.Max(0, marksApplied); _exposureDuration = Mathf.Max(0f, exposureDuration); _exposureDamageMultiplier = Mathf.Clamp(exposureDamageMultiplier, 1f, 1.6f); _disruptionDuration = Mathf.Max(0f, disruptionDuration); _movementMultiplier = Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _reloadMultiplier = Mathf.Max(1f, reloadMultiplier); _blockCooldownMultiplier = Mathf.Max(1f, blockCooldownMultiplier); _baseColor = new Color(color.r, color.g, color.b, Mathf.Clamp((color.a <= 0.01f) ? 0.84f : color.a, 0.32f, 0.94f)); _originPosition = ((Component)this).transform.position; ((Component)this).transform.localScale = new Vector3(0.18f, 0.18f, 0.18f); ApplyTint(((Component)this).gameObject, _baseColor); FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Beacon, 0.07f, 1.03f); } private void Update() { float unscaledTime = Time.unscaledTime; if (unscaledTime >= _expiresAt) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } if (unscaledTime >= _nextPulseAt) { Pulse(); _nextPulseAt = unscaledTime + _pulseInterval; } UpdateVisuals(unscaledTime); } private void Pulse() { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) Player val = FindNearestEnemy(); if (val != null) { if (_marksApplied > 0) { PlayerMarkStatusController.GetOrAdd(val)?.AddMarks(_owner, _marksApplied, Math.Max(1, _marksApplied * 2)); } if (_exposureDuration > 0.05f && _exposureDamageMultiplier > 1.001f) { PlayerExposureStatusController.GetOrAdd(val)?.ApplyExposure(_owner, _exposureDuration, _exposureDamageMultiplier); } if (_disruptionDuration > 0.05f) { PlayerDisruptionStatusController.GetOrAdd(val)?.ApplyDisruption(_owner, _disruptionDuration, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); } _pulseAccentUntil = Time.unscaledTime + 0.22f; if (Time.unscaledTime >= _lastCueAt + 0.45f) { _lastCueAt = Time.unscaledTime; FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Beacon, 0.07f, 1.08f); } } } private Player FindNearestEnemy() { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: 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_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (_owner == null || _scanRange <= 0.05f) { return null; } float num = _scanRange * _scanRange; Player result = null; foreach (Player item in PlayerManager.instance?.players?.Where((Player player) => player != null) ?? Enumerable.Empty()) { if (item != _owner && item.data != null && !GetMemberValue(item.data, "dead", fallback: false)) { Vector3 val = ((Component)item).transform.position - ((Component)this).transform.position; float num2 = val.x * val.x + val.y * val.y + val.z * val.z; if (num2 <= num) { num = num2; result = item; } } } return result; } private void UpdateVisuals(float now) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) float num = Clamp01(_expiresAt - now); float num2 = 0.92f + 0.08f * (float)Math.Sin(now * 4.6f); float num3 = ((now <= _pulseAccentUntil) ? 1.28f : (1f + 0.06f * (float)Math.Sin(now * 5.2f))); float num4 = 0.17f * num2 * num3; ((Component)this).transform.localScale = new Vector3(num4, num4, num4); ((Component)this).transform.position = new Vector3(_originPosition.x, _originPosition.y + 0.025f * (float)Math.Sin(now * 3.2f), 0f); float num5 = ((now <= _pulseAccentUntil) ? 0.94f : (0.7f + 0.08f * (float)Math.Sin(now * 4.1f))); Color color = default(Color); ((Color)(ref color))..ctor(_baseColor.r, _baseColor.g, _baseColor.b, Mathf.Clamp(num5 * Mathf.Max(0.55f, num), 0.3f, 0.96f)); ApplyTint(((Component)this).gameObject, color); } private static void ApplyTint(GameObject target, Color color) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target != null) { MethodInfo method = ((object)target).GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.SpriteRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.SpriteRenderer, UnityEngine") }), color); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.MeshRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.MeshRenderer, UnityEngine") }), color); for (int i = 0; i < target.transform.childCount; i++) { ApplyTint(((Component)target.transform.GetChild(i)).gameObject, color); } } } private static void ApplyTintToComponent(object component, Color color) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (component == null) { return; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } PropertyInfo property2 = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((property2 == null) ? null : property2.GetValue(component, null)); if (obj != null) { PropertyInfo property3 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.CanWrite && property3.PropertyType == typeof(Color)) { property3.SetValue(obj, color, null); } } } private static T GetMemberValue(object target, string memberName, T fallback) { if (target == null) { return fallback; } FieldInfo field = target.GetType().GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && TryConvertValue(field.GetValue(target), out var value)) { return value; } PropertyInfo property = target.GetType().GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && TryConvertValue(property.GetValue(target, null), out var value2)) { return value2; } return fallback; } private static bool TryConvertValue(object rawValue, out T value) { if (rawValue == null) { value = default(T); return false; } if (rawValue is T val) { value = val; return true; } try { value = (T)Convert.ChangeType(rawValue, typeof(T)); return true; } catch { value = default(T); return false; } } private static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } } internal sealed class HazardZoneController : MonoBehaviour { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private Player _owner; private float _expiresAt; private float _radius; private float _pulseInterval; private float _nextPulseAt; private int _marksApplied; private float _movementMultiplier = 1f; private float _reloadMultiplier = 1f; private float _blockCooldownMultiplier = 1f; private float _pulseAccentUntil; private float _lastCueAt; private Color _baseColor = new Color(1f, 0.52f, 0.22f, 0.72f); internal static void Spawn(Player owner, Vector3 position, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (owner != null && !(duration <= 0.05f) && !(radius <= 0.1f)) { GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); GameObject val2 = (GameObject)((val != null) ? ((object)Object.Instantiate(val, position, default(Quaternion), (Transform)null)) : ((object)new GameObject())); if (val2 != null) { ((Object)val2).name = "BetterRoundsHazardZone"; val2.transform.position = position; ExtensionMethods.GetOrAddComponent(val2, false).Initialize(owner, duration, radius, pulseInterval, marksApplied, movementMultiplier, reloadMultiplier, blockCooldownMultiplier, color); } } } private void Initialize(Player owner, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) _owner = owner; _expiresAt = Time.unscaledTime + duration; _radius = radius; _pulseInterval = Mathf.Max(0.25f, pulseInterval); _nextPulseAt = Time.unscaledTime; _marksApplied = Math.Max(0, marksApplied); _movementMultiplier = Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _reloadMultiplier = Mathf.Max(1f, reloadMultiplier); _blockCooldownMultiplier = Mathf.Max(1f, blockCooldownMultiplier); _baseColor = new Color(color.r, color.g, color.b, Mathf.Clamp((color.a <= 0.01f) ? 0.72f : color.a, 0.28f, 0.85f)); ((Component)this).transform.localScale = new Vector3(_radius * 2f, _radius * 2f, 0.16f); ApplyTint(((Component)this).gameObject, _baseColor); } private void Update() { float unscaledTime = Time.unscaledTime; if (unscaledTime >= _expiresAt) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } if (unscaledTime >= _nextPulseAt) { Pulse(); _nextPulseAt = unscaledTime + _pulseInterval; } UpdateVisuals(unscaledTime); } private void Pulse() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) if (_owner == null) { return; } bool flag = false; foreach (Player item in PlayerManager.instance?.players?.Where((Player player) => player != null) ?? Enumerable.Empty()) { if (item == _owner || item.data == null) { continue; } Vector3 val = ((Component)item).transform.position - ((Component)this).transform.position; if (!(val.x * val.x + val.y * val.y + val.z * val.z > _radius * _radius)) { if (_marksApplied > 0) { PlayerMarkStatusController.GetOrAdd(item)?.AddMarks(_owner, _marksApplied, Math.Max(1, _marksApplied * 2)); } PlayerDisruptionStatusController.GetOrAdd(item)?.ApplyDisruption(_owner, _pulseInterval * 1.35f, _movementMultiplier, _reloadMultiplier, _blockCooldownMultiplier); _pulseAccentUntil = Time.unscaledTime + 0.18f; flag = true; } } if (flag && Time.unscaledTime >= _lastCueAt + 0.4f) { _lastCueAt = Time.unscaledTime; FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Hazard, 0.08f, 0.98f); } } private void UpdateVisuals(float now) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) float num = Clamp01(_expiresAt - now); float num2 = ((now <= _pulseAccentUntil) ? 1.12f : (1f + 0.05f * (float)Math.Sin(now * 3.6f))); ((Component)this).transform.localScale = new Vector3(_radius * 2f * num2, _radius * 2f * num2, 0.16f); ((Component)this).transform.position = new Vector3(((Component)this).transform.position.x, ((Component)this).transform.position.y, num * 0.02f); float num3 = ((now <= _pulseAccentUntil) ? 0.82f : (0.56f + 0.1f * (float)Math.Sin(now * 4.2f))); ApplyTint(((Component)this).gameObject, new Color(_baseColor.r, _baseColor.g, _baseColor.b, Mathf.Clamp(num3, 0.3f, 0.9f))); } private static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } private static void ApplyTint(GameObject target, Color color) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target != null) { MethodInfo method = ((object)target).GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.SpriteRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.SpriteRenderer, UnityEngine") }), color); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.MeshRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.MeshRenderer, UnityEngine") }), color); for (int i = 0; i < target.transform.childCount; i++) { ApplyTint(((Component)target.transform.GetChild(i)).gameObject, color); } } } private static void ApplyTintToComponent(object component, Color color) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (component == null) { return; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } PropertyInfo property2 = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((property2 == null) ? null : property2.GetValue(component, null)); if (obj != null) { PropertyInfo property3 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.CanWrite && property3.PropertyType == typeof(Color)) { property3.SetValue(obj, color, null); } } } } internal sealed class PlayerAbilityController : MonoBehaviour { internal struct AbilityVisualTelemetry { internal bool ChamberReady { get; } internal bool LastShellReady { get; } internal bool ReloadChargeReady { get; } internal bool KillChargeReady { get; } internal bool MissChargeReady { get; } internal bool ReloadModeReady { get; } internal bool EmptyMagRushActive { get; } internal bool GroundedStanceActive { get; } internal bool WallStanceActive { get; } internal bool LowHealthStanceActive { get; } internal bool ComebackRushActive { get; } internal bool MarkChargeReady { get; } internal bool HasMarkMechanic { get; } internal bool PerfectParryReady { get; } internal bool BlockGhostReady { get; } internal bool CounterChargeReady { get; } internal bool OrbitWardActive { get; } internal int OrbitWardCount { get; } internal bool HazardZoneArmed { get; } internal bool HitDisruptionArmed { get; } internal bool OverheatActive { get; } internal int HeatThreshold { get; } internal int HeatStacks { get; } internal int CadenceInterval { get; } internal int CadenceProgress { get; } internal bool CadenceReady { get; } internal AbilityVisualTelemetry(bool chamberReady, bool lastShellReady, bool reloadChargeReady, bool killChargeReady, bool missChargeReady, bool reloadModeReady, bool emptyMagRushActive, bool groundedStanceActive, bool wallStanceActive, bool lowHealthStanceActive, bool comebackRushActive, bool markChargeReady, bool hasMarkMechanic, bool perfectParryReady, bool blockGhostReady, bool counterChargeReady, bool orbitWardActive, int orbitWardCount, bool hazardZoneArmed, bool hitDisruptionArmed, bool overheatActive, int heatThreshold, int heatStacks, int cadenceInterval, int cadenceProgress, bool cadenceReady) { ChamberReady = chamberReady; LastShellReady = lastShellReady; ReloadChargeReady = reloadChargeReady; KillChargeReady = killChargeReady; MissChargeReady = missChargeReady; ReloadModeReady = reloadModeReady; EmptyMagRushActive = emptyMagRushActive; GroundedStanceActive = groundedStanceActive; WallStanceActive = wallStanceActive; LowHealthStanceActive = lowHealthStanceActive; ComebackRushActive = comebackRushActive; MarkChargeReady = markChargeReady; HasMarkMechanic = hasMarkMechanic; PerfectParryReady = perfectParryReady; BlockGhostReady = blockGhostReady; CounterChargeReady = counterChargeReady; OrbitWardActive = orbitWardActive; OrbitWardCount = orbitWardCount; HazardZoneArmed = hazardZoneArmed; HitDisruptionArmed = hitDisruptionArmed; OverheatActive = overheatActive; HeatThreshold = heatThreshold; HeatStacks = heatStacks; CadenceInterval = cadenceInterval; CadenceProgress = cadenceProgress; CadenceReady = cadenceReady; } } private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Type Collider2DType = Type.GetType("UnityEngine.Collider2D, UnityEngine.Physics2DModule") ?? Type.GetType("UnityEngine.Collider2D, UnityEngine"); private static readonly MethodInfo FindObjectsOfTypeMethod = typeof(GameObject).GetMethod("FindObjectsOfType", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Type) }, null); private static readonly MethodInfo BlockCanBlockMethod = typeof(Block).GetMethod("CanBlock", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); private Player _player; private Gun _gun; private GunAmmo _gunAmmo; private CharacterData _data; private CharacterStatModifiers _characterStats; private int _counterChargeShots; private float _counterChargeDamageMultiplier = 1f; private float _counterChargeSpeedMultiplier = 1f; private float _firstShotDamageMultiplier = 1f; private float _firstShotSpeedMultiplier = 1f; private float _lastBulletDamageMultiplier = 1f; private float _lastBulletSpeedMultiplier = 1f; private float _lastShellDamageMultiplier = 1f; private float _lastShellSpeedMultiplier = 1f; private float _lastShellProjectileSizeMultiplier = 1f; private int _lastShellUnblockableCount; private int _reloadChargeShots; private float _reloadChargeDamageMultiplier = 1f; private float _reloadChargeSpeedMultiplier = 1f; private float _reloadChargeProjectileSizeMultiplier = 1f; private int _reloadChargeUnblockableCount; private int _killChargeShots; private float _killChargeDamageMultiplier = 1f; private float _killChargeSpeedMultiplier = 1f; private float _killChargeProjectileSizeMultiplier = 1f; private int _killChargeUnblockableCount; private int _missChargeShots; private float _missChargeDamageMultiplier = 1f; private float _missChargeSpeedMultiplier = 1f; private float _missChargeProjectileSizeMultiplier = 1f; private int _missChargeUnblockableCount; private float _airborneDamageMultiplier = 1f; private float _airborneSpeedMultiplier = 1f; private bool _airborneBuffActive; private int _blockAmmoGain; private float _blockTempoDuration; private float _blockTempoMoveMultiplier = 1f; private float _blockTempoReloadMultiplier = 1f; private bool _blockTempoBuffActive; private float _blockTempoExpiresAt; private int _perfectParryShots; private float _perfectParryWindowSeconds; private float _perfectParryDamageMultiplier = 1f; private float _perfectParrySpeedMultiplier = 1f; private float _perfectParryProjectileSizeMultiplier = 1f; private int _perfectParryUnblockableCount; private bool _observedCanBlock; private bool _lastObservedCanBlock; private float _blockReadyAt = float.NegativeInfinity; private int _blockGhostShots; private int _blockGhostUnblockableCount; private float _blockGhostDamageMultiplier = 1f; private float _blockGhostSpeedMultiplier = 1f; private float _blockGhostProjectileSizeMultiplier = 1f; private float _chamberedDamageMultiplier = 1f; private float _chamberedSpeedMultiplier = 1f; private float _chamberedProjectileSizeMultiplier = 1f; private int _chamberedUnblockableCount; private float _emptyMagRushDuration; private float _emptyMagRushMoveMultiplier = 1f; private float _emptyMagRushReloadMultiplier = 1f; private bool _emptyMagRushBuffActive; private float _emptyMagRushExpiresAt; private float _groundedDamageMultiplier = 1f; private float _groundedMoveMultiplier = 1f; private float _groundedReloadMultiplier = 1f; private bool _groundedBuffActive; private float _wallDistanceThreshold; private float _wallDamageMultiplier = 1f; private float _wallSpeedMultiplier = 1f; private float _wallReloadMultiplier = 1f; private bool _wallBuffActive; private float _lowHealthThreshold; private float _lowHealthDamageMultiplier = 1f; private float _lowHealthMoveMultiplier = 1f; private float _lowHealthReloadMultiplier = 1f; private float _lowHealthLifestealFraction; private bool _lowHealthBuffActive; private float _painRushDuration; private float _painRushMoveMultiplier = 1f; private float _painRushReloadMultiplier = 1f; private bool _painRushBuffActive; private float _painRushExpiresAt; private int _deathVowPendingCharges; private int _deathVowMaxPendingCharges; private float _deathVowDuration; private float _deathVowDamageMultiplier = 1f; private float _deathVowMoveMultiplier = 1f; private float _deathVowReloadMultiplier = 1f; private bool _deathVowBuffActive; private float _deathVowExpiresAt; private float _patientShotDelay; private float _patientShotDamageMultiplier = 1f; private float _patientShotSpeedMultiplier = 1f; private float _chainShotWindow; private float _chainShotDamageStep = 1f; private float _chainShotSpeedStep = 1f; private int _chainShotMaxStacks; private int _chainShotStacks; private int _hitChargeShots; private float _hitChargeDamageMultiplier = 1f; private float _hitChargeSpeedMultiplier = 1f; private int _cadenceEveryShots; private float _cadenceDamageMultiplier = 1f; private float _cadenceSpeedMultiplier = 1f; private float _cadenceProjectileSizeMultiplier = 1f; private int _cadenceUnblockableCount; private int _successfulAttacks; private int _heatShotsToOverheat; private float _heatWindowSeconds; private float _heatPerStackDamageMultiplier = 1f; private float _heatPerStackSpeedMultiplier = 1f; private float _heatOverheatDuration; private float _heatOverheatReloadMultiplier = 1f; private float _heatOverheatSpreadPenalty; private int _heatStacks; private float _lastHeatShotAt = float.NegativeInfinity; private bool _heatOverheatActive; private float _heatOverheatExpiresAt; private int _markApplyCount; private int _markMaxStacks; private int _markChargeShots; private float _markChargeDamageMultiplier = 1f; private float _markChargeSpeedMultiplier = 1f; private float _markChargeProjectileSizeMultiplier = 1f; private float _markChargeHealFraction; private float _hitDisruptionDuration; private float _hitDisruptMovementMultiplier = 1f; private float _hitDisruptReloadMultiplier = 1f; private float _hitDisruptBlockCooldownMultiplier = 1f; private float _hitExposureDuration; private float _hitExposureDamageMultiplier = 1f; private float _blockZoneDuration; private float _blockZoneRadius; private float _blockZonePulseInterval; private int _blockZoneMarksApplied; private float _blockZoneMovementMultiplier = 1f; private float _blockZoneReloadMultiplier = 1f; private float _blockZoneBlockCooldownMultiplier = 1f; private float _killZoneDuration; private float _killZoneRadius; private float _killZonePulseInterval; private int _killZoneMarksApplied; private float _killZoneMovementMultiplier = 1f; private float _killZoneReloadMultiplier = 1f; private float _killZoneBlockCooldownMultiplier = 1f; private float _trailZoneSpawnInterval; private float _trailZoneDuration; private float _trailZoneRadius; private float _trailZonePulseInterval; private int _trailZoneMarksApplied; private float _trailZoneMovementMultiplier = 1f; private float _trailZoneReloadMultiplier = 1f; private float _trailZoneBlockCooldownMultiplier = 1f; private float _nextTrailZoneAt; private float _beaconDropSpawnInterval; private float _beaconDropDuration; private float _beaconDropRange; private float _beaconDropPulseInterval; private int _beaconDropMarksApplied; private float _beaconDropExposureDuration; private float _beaconDropExposureDamageMultiplier = 1f; private float _beaconDropDisruptionDuration; private float _beaconDropMovementMultiplier = 1f; private float _beaconDropReloadMultiplier = 1f; private float _beaconDropBlockCooldownMultiplier = 1f; private float _nextBeaconDropAt; private int _reloadModeShotsPerReload; private int _reloadModePreparedShots; private float _reloadModeDamageMultiplier = 1f; private float _reloadModeSpeedMultiplier = 1f; private float _reloadModeProjectileSizeMultiplier = 1f; private int _reloadModeIgnoreWallsCount; private int _reloadModeExtraProjectiles; private float _reloadModeSpreadPenalty; private int _orbitWardCount; private float _orbitWardRange; private float _orbitWardPulseInterval; private int _orbitWardMarksApplied; private float _orbitWardDisruptionDuration; private float _orbitWardMovementMultiplier = 1f; private float _orbitWardReloadMultiplier = 1f; private float _orbitWardBlockCooldownMultiplier = 1f; private float _nextOrbitWardPulseAt; private float _lifestealFraction; private float _lastSuccessfulAttackAt = float.NegativeInfinity; private bool _observedAmmoInitialized; private bool _observedSpentAmmoSinceFull; private int _lastObservedAmmo; private bool _pendingMissCheck; private float _pendingMissExpiresAt; private bool _attackBuffApplied; private bool _pendingConsumeCounterCharge; private bool _pendingConsumeHitCharge; private bool _pendingConsumePerfectParry; private bool _pendingConsumeBlockGhost; private bool _pendingConsumeMarkCharge; private bool _pendingConsumeReloadCharge; private bool _pendingConsumeKillCharge; private bool _pendingConsumeMissCharge; private bool _pendingConsumeReloadMode; private float _attackDamageBefore; private float _attackSpeedBefore; private bool _attackHadAmmoValue; private int _attackAmmoBefore; private bool _attackExtraProjectilesApplied; private int _attackProjectilesBefore; private bool _attackSpreadApplied; private float _attackSpreadBefore; private bool _attackProjectileSizeApplied; private float _attackProjectileSizeBefore; private bool _attackUnblockableApplied; private bool _attackUnblockableBefore; private bool _attackIgnoreWallsApplied; private bool _attackIgnoreWallsBefore; private const float MissChargeWindowSeconds = 0.9f; private static float CurrentTime => Time.unscaledTime; internal void AddHitDisruption(float duration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _hitDisruptionDuration = Mathf.Max(_hitDisruptionDuration, Mathf.Max(0.15f, duration)); _hitDisruptMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _hitDisruptReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _hitDisruptBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); } internal void AddHitExposure(float duration, float damageMultiplier) { _hitExposureDuration = Mathf.Max(_hitExposureDuration, Mathf.Max(0.2f, duration)); _hitExposureDamageMultiplier *= Mathf.Clamp(damageMultiplier, 1f, 1.6f); } internal void RemoveHitExposure(float duration, float damageMultiplier) { _hitExposureDuration = Mathf.Max(0f, _hitExposureDuration - Mathf.Max(0.2f, duration)); _hitExposureDamageMultiplier = SafeDivide(_hitExposureDamageMultiplier, Mathf.Clamp(damageMultiplier, 1f, 1.6f)); } internal void RemoveHitDisruption(float duration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _hitDisruptionDuration = Mathf.Max(0f, _hitDisruptionDuration - Mathf.Max(0.15f, duration)); _hitDisruptMovementMultiplier = SafeDivide(_hitDisruptMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _hitDisruptReloadMultiplier = SafeDivide(_hitDisruptReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _hitDisruptBlockCooldownMultiplier = SafeDivide(_hitDisruptBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); } internal void AddBlockZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _blockZoneDuration = Mathf.Max(_blockZoneDuration, Mathf.Max(0.2f, duration)); _blockZoneRadius = Mathf.Max(_blockZoneRadius, Mathf.Max(0.35f, radius)); _blockZonePulseInterval = Mathf.Max(0.2f, Math.Min((_blockZonePulseInterval <= 0f) ? float.MaxValue : _blockZonePulseInterval, Mathf.Max(0.2f, pulseInterval))); _blockZoneMarksApplied += Math.Max(0, marksApplied); _blockZoneMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _blockZoneReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _blockZoneBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); } internal void RemoveBlockZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _blockZoneDuration = Mathf.Max(0f, _blockZoneDuration - Mathf.Max(0.2f, duration)); _blockZoneRadius = Mathf.Max(0f, _blockZoneRadius - Mathf.Max(0.35f, radius)); _blockZoneMarksApplied = Math.Max(0, _blockZoneMarksApplied - Math.Max(0, marksApplied)); _blockZoneMovementMultiplier = SafeDivide(_blockZoneMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _blockZoneReloadMultiplier = SafeDivide(_blockZoneReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _blockZoneBlockCooldownMultiplier = SafeDivide(_blockZoneBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); if (_blockZoneDuration <= 0.001f || _blockZoneRadius <= 0.001f) { _blockZonePulseInterval = 0f; _blockZoneMarksApplied = 0; } } internal void AddKillZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _killZoneDuration = Mathf.Max(_killZoneDuration, Mathf.Max(0.2f, duration)); _killZoneRadius = Mathf.Max(_killZoneRadius, Mathf.Max(0.35f, radius)); _killZonePulseInterval = Mathf.Max(0.2f, Math.Min((_killZonePulseInterval <= 0f) ? float.MaxValue : _killZonePulseInterval, Mathf.Max(0.2f, pulseInterval))); _killZoneMarksApplied += Math.Max(0, marksApplied); _killZoneMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _killZoneReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _killZoneBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); } internal void RemoveKillZone(float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _killZoneDuration = Mathf.Max(0f, _killZoneDuration - Mathf.Max(0.2f, duration)); _killZoneRadius = Mathf.Max(0f, _killZoneRadius - Mathf.Max(0.35f, radius)); _killZoneMarksApplied = Math.Max(0, _killZoneMarksApplied - Math.Max(0, marksApplied)); _killZoneMovementMultiplier = SafeDivide(_killZoneMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _killZoneReloadMultiplier = SafeDivide(_killZoneReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _killZoneBlockCooldownMultiplier = SafeDivide(_killZoneBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); if (_killZoneDuration <= 0.001f || _killZoneRadius <= 0.001f) { _killZonePulseInterval = 0f; _killZoneMarksApplied = 0; } } internal void AddTrailZone(float spawnInterval, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _trailZoneSpawnInterval = Mathf.Max(0.35f, Math.Min((_trailZoneSpawnInterval <= 0f) ? float.MaxValue : _trailZoneSpawnInterval, Mathf.Max(0.35f, spawnInterval))); _trailZoneDuration = Mathf.Max(_trailZoneDuration, Mathf.Max(0.2f, duration)); _trailZoneRadius = Mathf.Max(_trailZoneRadius, Mathf.Max(0.35f, radius)); _trailZonePulseInterval = Mathf.Max(0.2f, Math.Min((_trailZonePulseInterval <= 0f) ? float.MaxValue : _trailZonePulseInterval, Mathf.Max(0.2f, pulseInterval))); _trailZoneMarksApplied += Math.Max(0, marksApplied); _trailZoneMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _trailZoneReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _trailZoneBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); if (_nextTrailZoneAt <= 0f) { _nextTrailZoneAt = CurrentTime + Mathf.Max(0.2f, _trailZoneSpawnInterval * 0.5f); } } internal void RemoveTrailZone(float spawnInterval, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _trailZoneDuration = Mathf.Max(0f, _trailZoneDuration - Mathf.Max(0.2f, duration)); _trailZoneRadius = Mathf.Max(0f, _trailZoneRadius - Mathf.Max(0.35f, radius)); _trailZoneMarksApplied = Math.Max(0, _trailZoneMarksApplied - Math.Max(0, marksApplied)); _trailZoneMovementMultiplier = SafeDivide(_trailZoneMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _trailZoneReloadMultiplier = SafeDivide(_trailZoneReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _trailZoneBlockCooldownMultiplier = SafeDivide(_trailZoneBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); if (_trailZoneDuration <= 0.001f || _trailZoneRadius <= 0.001f) { _trailZoneSpawnInterval = 0f; _trailZonePulseInterval = 0f; _trailZoneMarksApplied = 0; _nextTrailZoneAt = 0f; } } internal void AddBeaconDrop(float spawnInterval, float duration, float range, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _beaconDropSpawnInterval = Mathf.Max(0.45f, Math.Min((_beaconDropSpawnInterval <= 0f) ? float.MaxValue : _beaconDropSpawnInterval, Mathf.Max(0.45f, spawnInterval))); _beaconDropDuration = Mathf.Max(_beaconDropDuration, Mathf.Max(0.5f, duration)); _beaconDropRange = Mathf.Max(_beaconDropRange, Mathf.Max(1.2f, range)); _beaconDropPulseInterval = Mathf.Max(0.25f, Math.Min((_beaconDropPulseInterval <= 0f) ? float.MaxValue : _beaconDropPulseInterval, Mathf.Max(0.25f, pulseInterval))); _beaconDropMarksApplied += Math.Max(0, marksApplied); _beaconDropExposureDuration = Mathf.Max(_beaconDropExposureDuration, Mathf.Max(0f, exposureDuration)); _beaconDropExposureDamageMultiplier *= Mathf.Clamp(exposureDamageMultiplier, 1f, 1.6f); _beaconDropDisruptionDuration = Mathf.Max(_beaconDropDisruptionDuration, Mathf.Max(0f, disruptionDuration)); _beaconDropMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _beaconDropReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _beaconDropBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); if (_nextBeaconDropAt <= 0f) { _nextBeaconDropAt = CurrentTime + Mathf.Max(0.2f, _beaconDropSpawnInterval * 0.5f); } } internal void RemoveBeaconDrop(float spawnInterval, float duration, float range, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _beaconDropDuration = Mathf.Max(0f, _beaconDropDuration - Mathf.Max(0.5f, duration)); _beaconDropRange = Mathf.Max(0f, _beaconDropRange - Mathf.Max(1.2f, range)); _beaconDropMarksApplied = Math.Max(0, _beaconDropMarksApplied - Math.Max(0, marksApplied)); _beaconDropExposureDuration = Mathf.Max(0f, _beaconDropExposureDuration - Mathf.Max(0f, exposureDuration)); _beaconDropExposureDamageMultiplier = SafeDivide(_beaconDropExposureDamageMultiplier, Mathf.Clamp(exposureDamageMultiplier, 1f, 1.6f)); _beaconDropDisruptionDuration = Mathf.Max(0f, _beaconDropDisruptionDuration - Mathf.Max(0f, disruptionDuration)); _beaconDropMovementMultiplier = SafeDivide(_beaconDropMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _beaconDropReloadMultiplier = SafeDivide(_beaconDropReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _beaconDropBlockCooldownMultiplier = SafeDivide(_beaconDropBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); if (_beaconDropDuration <= 0.001f || _beaconDropRange <= 0.001f) { _beaconDropSpawnInterval = 0f; _beaconDropPulseInterval = 0f; _beaconDropMarksApplied = 0; _beaconDropExposureDuration = 0f; _beaconDropExposureDamageMultiplier = 1f; _beaconDropDisruptionDuration = 0f; _nextBeaconDropAt = 0f; } } internal void AddReloadMode(int shotsPerReload, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool ignoreWalls, int extraProjectiles, float spreadPenalty) { _reloadModeShotsPerReload += Math.Max(1, shotsPerReload); _reloadModeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _reloadModeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _reloadModeProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); _reloadModeExtraProjectiles += Math.Max(0, extraProjectiles); _reloadModeSpreadPenalty += Mathf.Max(0f, spreadPenalty); if (ignoreWalls) { _reloadModeIgnoreWallsCount++; } } internal void RemoveReloadMode(int shotsPerReload, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool ignoreWalls, int extraProjectiles, float spreadPenalty) { _reloadModeShotsPerReload = Math.Max(0, _reloadModeShotsPerReload - Math.Max(1, shotsPerReload)); _reloadModeDamageMultiplier = SafeDivide(_reloadModeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _reloadModeSpeedMultiplier = SafeDivide(_reloadModeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _reloadModeProjectileSizeMultiplier = SafeDivide(_reloadModeProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); _reloadModeExtraProjectiles = Math.Max(0, _reloadModeExtraProjectiles - Math.Max(0, extraProjectiles)); _reloadModeSpreadPenalty = Mathf.Max(0f, _reloadModeSpreadPenalty - Mathf.Max(0f, spreadPenalty)); if (ignoreWalls) { _reloadModeIgnoreWallsCount = Math.Max(0, _reloadModeIgnoreWallsCount - 1); } if (_reloadModeShotsPerReload <= 0) { _reloadModePreparedShots = 0; } } internal void AddOrbitWard(int count, float range, float pulseInterval, int marksApplied, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _orbitWardCount += Math.Max(1, count); _orbitWardRange = Mathf.Max(_orbitWardRange, Mathf.Max(1f, range)); _orbitWardPulseInterval = Mathf.Max(0.2f, Math.Min((_orbitWardPulseInterval <= 0f) ? float.MaxValue : _orbitWardPulseInterval, Mathf.Max(0.2f, pulseInterval))); _orbitWardMarksApplied += Math.Max(0, marksApplied); _orbitWardDisruptionDuration = Mathf.Max(_orbitWardDisruptionDuration, Mathf.Max(0.2f, disruptionDuration)); _orbitWardMovementMultiplier *= Mathf.Clamp(movementMultiplier, 0.35f, 1.25f); _orbitWardReloadMultiplier *= Mathf.Max(1f, reloadMultiplier); _orbitWardBlockCooldownMultiplier *= Mathf.Max(1f, blockCooldownMultiplier); if (_nextOrbitWardPulseAt <= 0f) { _nextOrbitWardPulseAt = CurrentTime + Mathf.Max(0.15f, _orbitWardPulseInterval); } } internal void RemoveOrbitWard(int count, float range, float pulseInterval, int marksApplied, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { _orbitWardCount = Math.Max(0, _orbitWardCount - Math.Max(1, count)); _orbitWardMarksApplied = Math.Max(0, _orbitWardMarksApplied - Math.Max(0, marksApplied)); _orbitWardDisruptionDuration = Mathf.Max(0f, _orbitWardDisruptionDuration - Mathf.Max(0.2f, disruptionDuration)); _orbitWardMovementMultiplier = SafeDivide(_orbitWardMovementMultiplier, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f)); _orbitWardReloadMultiplier = SafeDivide(_orbitWardReloadMultiplier, Mathf.Max(1f, reloadMultiplier)); _orbitWardBlockCooldownMultiplier = SafeDivide(_orbitWardBlockCooldownMultiplier, Mathf.Max(1f, blockCooldownMultiplier)); if (_orbitWardCount <= 0) { _orbitWardRange = 0f; _orbitWardPulseInterval = 0f; _nextOrbitWardPulseAt = 0f; } } private void PulseOrbitWard() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) Player val = FindNearestEnemy(_orbitWardRange); if (val != null) { if (_orbitWardMarksApplied > 0) { PlayerMarkStatusController.GetOrAdd(val)?.AddMarks(_player, _orbitWardMarksApplied, Math.Max(1, _orbitWardMarksApplied * 2)); } if (_orbitWardDisruptionDuration > 0.05f) { PlayerDisruptionStatusController.GetOrAdd(val)?.ApplyDisruption(_player, _orbitWardDisruptionDuration, _orbitWardMovementMultiplier, _orbitWardReloadMultiplier, _orbitWardBlockCooldownMultiplier); } FeedbackCueUtility.PlayCue(((Component)val).transform.position, FeedbackCueKind.Summon, 0.07f, 1.02f); } } private void UpdateAreaControl() { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) if (_orbitWardCount > 0 && _orbitWardRange > 0.1f && _orbitWardPulseInterval > 0.1f && CurrentTime >= _nextOrbitWardPulseAt) { PulseOrbitWard(); float num = Mathf.Max(0.22f, _orbitWardPulseInterval / (float)Mathf.Clamp(_orbitWardCount, 1, 3)); _nextOrbitWardPulseAt = CurrentTime + num; } if (!GetMemberValue(_data, "dead", fallback: false) && _trailZoneSpawnInterval > 0.1f && _trailZoneDuration > 0.05f && _trailZoneRadius > 0.1f && CurrentTime >= _nextTrailZoneAt) { SpawnHazardZone(((Component)this).transform.position, _trailZoneDuration, _trailZoneRadius, _trailZonePulseInterval, _trailZoneMarksApplied, _trailZoneMovementMultiplier, _trailZoneReloadMultiplier, _trailZoneBlockCooldownMultiplier, (_trailZoneMarksApplied > 0) ? new Color(0.98f, 0.36f, 0.68f, 0.72f) : new Color(0.95f, 0.48f, 0.22f, 0.7f)); FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Hazard, 0.05f, (_trailZoneMarksApplied > 0) ? 1.08f : 0.96f); _nextTrailZoneAt = CurrentTime + Mathf.Max(0.4f, _trailZoneSpawnInterval); } if (!GetMemberValue(_data, "dead", fallback: false) && _beaconDropSpawnInterval > 0.1f && _beaconDropDuration > 0.05f && _beaconDropRange > 0.2f && CurrentTime >= _nextBeaconDropAt) { SpawnBeaconNode(((Component)this).transform.position, _beaconDropDuration, _beaconDropRange, _beaconDropPulseInterval, _beaconDropMarksApplied, _beaconDropExposureDuration, _beaconDropExposureDamageMultiplier, _beaconDropDisruptionDuration, _beaconDropMovementMultiplier, _beaconDropReloadMultiplier, _beaconDropBlockCooldownMultiplier, (_beaconDropMarksApplied > 0) ? new Color(0.98f, 0.82f, 0.28f, 0.84f) : new Color(0.96f, 0.56f, 0.24f, 0.8f)); _nextBeaconDropAt = CurrentTime + Mathf.Max(0.45f, _beaconDropSpawnInterval); } } private void SpawnHazardZone(Vector3 position, float duration, float radius, float pulseInterval, int marksApplied, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) HazardZoneController.Spawn(_player, position, duration, radius, (pulseInterval <= 0.05f) ? 0.45f : pulseInterval, marksApplied, movementMultiplier, reloadMultiplier, blockCooldownMultiplier, color); } private void SpawnBeaconNode(Vector3 position, float duration, float range, float pulseInterval, int marksApplied, float exposureDuration, float exposureDamageMultiplier, float disruptionDuration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier, Color color) { //IL_0006: 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) BeaconNodeController.Spawn(_player, position, duration, range, (pulseInterval <= 0.05f) ? 0.6f : pulseInterval, marksApplied, exposureDuration, exposureDamageMultiplier, disruptionDuration, movementMultiplier, reloadMultiplier, blockCooldownMultiplier, color); } internal void NotifyDamageTaken(float damageAmount) { if (damageAmount <= 0.001f) { return; } ResolveReferences(); if (!(_painRushDuration <= 0f) && (!(_painRushMoveMultiplier <= 1.001f) || !(_painRushReloadMultiplier >= 0.999f))) { if (!_painRushBuffActive) { ActivatePainRushBuff(); } _painRushExpiresAt = Mathf.Max(_painRushExpiresAt, CurrentTime + _painRushDuration); } } internal void NotifyDamageDealt(float damageAmount, Player damagedPlayer) { if (damageAmount <= 0.001f) { return; } _pendingMissCheck = false; _pendingMissExpiresAt = float.NegativeInfinity; float num = _lifestealFraction + (_lowHealthBuffActive ? _lowHealthLifestealFraction : 0f); if (num > 0.001f) { TryHeal(damageAmount * num); } if (_hitChargeDamageMultiplier > 1.001f || _hitChargeSpeedMultiplier > 1.001f) { _hitChargeShots++; } if ((_markApplyCount > 0 || _markChargeDamageMultiplier > 1.001f || _markChargeSpeedMultiplier > 1.001f) && damagedPlayer != null) { PlayerMarkStatusController orAdd = PlayerMarkStatusController.GetOrAdd(damagedPlayer); if (orAdd != null && orAdd.TryConsumeMark(_player, 1)) { _markChargeShots++; if (_markChargeHealFraction > 0.001f) { TryHeal(damageAmount * _markChargeHealFraction); } } else if (_markApplyCount > 0) { orAdd?.AddMarks(_player, _markApplyCount, Math.Max(1, _markMaxStacks)); } } if (damagedPlayer != null && _hitExposureDuration > 0.05f && _hitExposureDamageMultiplier > 1.001f) { PlayerExposureStatusController.GetOrAdd(damagedPlayer)?.ApplyExposure(_player, _hitExposureDuration, _hitExposureDamageMultiplier); } if (damagedPlayer != null && _hitDisruptionDuration > 0.05f) { PlayerDisruptionStatusController.GetOrAdd(damagedPlayer)?.ApplyDisruption(_player, _hitDisruptionDuration, _hitDisruptMovementMultiplier, _hitDisruptReloadMultiplier, _hitDisruptBlockCooldownMultiplier); } } internal void NotifyKillConfirmed(Player killedPlayer) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) if (killedPlayer != null && killedPlayer != _player) { if (_killChargeDamageMultiplier > 1.001f || _killChargeSpeedMultiplier > 1.001f || _killChargeProjectileSizeMultiplier > 1.001f || _killChargeUnblockableCount > 0) { _killChargeShots++; } if (_killZoneDuration > 0.05f && _killZoneRadius > 0.1f) { SpawnHazardZone(((Component)killedPlayer).transform.position, _killZoneDuration, _killZoneRadius, _killZonePulseInterval, _killZoneMarksApplied, _killZoneMovementMultiplier, _killZoneReloadMultiplier, _killZoneBlockCooldownMultiplier, new Color(1f, 0.44f, 0.24f, 0.4f)); } } } internal static PlayerAbilityController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void AddCounterCharge(float damageMultiplier, float speedMultiplier) { _counterChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _counterChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemoveCounterCharge(float damageMultiplier, float speedMultiplier) { _counterChargeDamageMultiplier = SafeDivide(_counterChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _counterChargeSpeedMultiplier = SafeDivide(_counterChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); if (_counterChargeDamageMultiplier <= 1.001f && _counterChargeSpeedMultiplier <= 1.001f) { _counterChargeShots = 0; } } internal void AddFirstShotBonus(float damageMultiplier, float speedMultiplier) { _firstShotDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _firstShotSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemoveFirstShotBonus(float damageMultiplier, float speedMultiplier) { _firstShotDamageMultiplier = SafeDivide(_firstShotDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _firstShotSpeedMultiplier = SafeDivide(_firstShotSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); } internal void AddLastBulletBonus(float damageMultiplier, float speedMultiplier) { _lastBulletDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _lastBulletSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemoveLastBulletBonus(float damageMultiplier, float speedMultiplier) { _lastBulletDamageMultiplier = SafeDivide(_lastBulletDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _lastBulletSpeedMultiplier = SafeDivide(_lastBulletSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); } internal void AddLastShell(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _lastShellDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _lastShellSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _lastShellProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _lastShellUnblockableCount++; } } internal void RemoveLastShell(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _lastShellDamageMultiplier = SafeDivide(_lastShellDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _lastShellSpeedMultiplier = SafeDivide(_lastShellSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _lastShellProjectileSizeMultiplier = SafeDivide(_lastShellProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _lastShellUnblockableCount = Math.Max(0, _lastShellUnblockableCount - 1); } } internal void AddReloadCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _reloadChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _reloadChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _reloadChargeProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _reloadChargeUnblockableCount++; } } internal void RemoveReloadCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _reloadChargeDamageMultiplier = SafeDivide(_reloadChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _reloadChargeSpeedMultiplier = SafeDivide(_reloadChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _reloadChargeProjectileSizeMultiplier = SafeDivide(_reloadChargeProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _reloadChargeUnblockableCount = Math.Max(0, _reloadChargeUnblockableCount - 1); } if (_reloadChargeDamageMultiplier <= 1.001f && _reloadChargeSpeedMultiplier <= 1.001f && _reloadChargeProjectileSizeMultiplier <= 1.001f && _reloadChargeUnblockableCount <= 0) { _reloadChargeShots = 0; } } internal void AddKillCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _killChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _killChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _killChargeProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _killChargeUnblockableCount++; } } internal void RemoveKillCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _killChargeDamageMultiplier = SafeDivide(_killChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _killChargeSpeedMultiplier = SafeDivide(_killChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _killChargeProjectileSizeMultiplier = SafeDivide(_killChargeProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _killChargeUnblockableCount = Math.Max(0, _killChargeUnblockableCount - 1); } if (_killChargeDamageMultiplier <= 1.001f && _killChargeSpeedMultiplier <= 1.001f && _killChargeProjectileSizeMultiplier <= 1.001f && _killChargeUnblockableCount <= 0) { _killChargeShots = 0; } } internal void AddMissCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _missChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _missChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _missChargeProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _missChargeUnblockableCount++; } } internal void RemoveMissCharge(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _missChargeDamageMultiplier = SafeDivide(_missChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _missChargeSpeedMultiplier = SafeDivide(_missChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _missChargeProjectileSizeMultiplier = SafeDivide(_missChargeProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _missChargeUnblockableCount = Math.Max(0, _missChargeUnblockableCount - 1); } if (_missChargeDamageMultiplier <= 1.001f && _missChargeSpeedMultiplier <= 1.001f && _missChargeProjectileSizeMultiplier <= 1.001f && _missChargeUnblockableCount <= 0) { _missChargeShots = 0; } } internal void AddAirborneBonus(float damageMultiplier, float speedMultiplier) { _airborneDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _airborneSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemoveAirborneBonus(float damageMultiplier, float speedMultiplier) { if (_airborneBuffActive) { DeactivateAirborneBuff(); } _airborneDamageMultiplier = SafeDivide(_airborneDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _airborneSpeedMultiplier = SafeDivide(_airborneSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); } internal void AddBlockAmmo(int ammoGain) { _blockAmmoGain += Math.Max(0, ammoGain); } internal void RemoveBlockAmmo(int ammoGain) { _blockAmmoGain = Math.Max(0, _blockAmmoGain - Math.Max(0, ammoGain)); } internal void AddBlockTempo(float duration, float movementMultiplier, float reloadMultiplier) { _blockTempoDuration = Mathf.Max(_blockTempoDuration, Mathf.Max(0f, duration)); _blockTempoMoveMultiplier *= Mathf.Max(1f, movementMultiplier); _blockTempoReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 1f); } internal void RemoveBlockTempo(float movementMultiplier, float reloadMultiplier) { if (_blockTempoBuffActive) { DeactivateBlockTempoBuff(); } _blockTempoMoveMultiplier = SafeDivide(_blockTempoMoveMultiplier, Mathf.Max(1f, movementMultiplier)); _blockTempoReloadMultiplier = SafeDivide(_blockTempoReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 1f)); } internal void AddPerfectParry(float readyWindowSeconds, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _perfectParryWindowSeconds += Mathf.Max(0.01f, readyWindowSeconds); _perfectParryDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _perfectParrySpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _perfectParryProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _perfectParryUnblockableCount++; } } internal void RemovePerfectParry(float readyWindowSeconds, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _perfectParryWindowSeconds = Mathf.Max(0f, _perfectParryWindowSeconds - Mathf.Max(0.01f, readyWindowSeconds)); _perfectParryDamageMultiplier = SafeDivide(_perfectParryDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _perfectParrySpeedMultiplier = SafeDivide(_perfectParrySpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _perfectParryProjectileSizeMultiplier = SafeDivide(_perfectParryProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _perfectParryUnblockableCount = Math.Max(0, _perfectParryUnblockableCount - 1); } if (_perfectParryWindowSeconds <= 0.001f && _perfectParryDamageMultiplier <= 1.001f && _perfectParrySpeedMultiplier <= 1.001f && _perfectParryProjectileSizeMultiplier <= 1.001f && _perfectParryUnblockableCount <= 0) { _perfectParryShots = 0; } } internal void AddBlockGhost(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _blockGhostDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _blockGhostSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _blockGhostProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _blockGhostUnblockableCount++; } } internal void RemoveBlockGhost(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _blockGhostDamageMultiplier = SafeDivide(_blockGhostDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _blockGhostSpeedMultiplier = SafeDivide(_blockGhostSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _blockGhostProjectileSizeMultiplier = SafeDivide(_blockGhostProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _blockGhostUnblockableCount = Math.Max(0, _blockGhostUnblockableCount - 1); } if (_blockGhostDamageMultiplier <= 1.001f && _blockGhostSpeedMultiplier <= 1.001f && _blockGhostProjectileSizeMultiplier <= 1.001f && _blockGhostUnblockableCount <= 0) { _blockGhostShots = 0; } } internal void AddChamberedRound(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _chamberedDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _chamberedSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _chamberedProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _chamberedUnblockableCount++; } } internal void RemoveChamberedRound(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _chamberedDamageMultiplier = SafeDivide(_chamberedDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _chamberedSpeedMultiplier = SafeDivide(_chamberedSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _chamberedProjectileSizeMultiplier = SafeDivide(_chamberedProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _chamberedUnblockableCount = Math.Max(0, _chamberedUnblockableCount - 1); } } internal void AddEmptyMagRush(float duration, float movementMultiplier, float reloadMultiplier) { _emptyMagRushDuration = Mathf.Max(_emptyMagRushDuration, Mathf.Max(0f, duration)); _emptyMagRushMoveMultiplier *= Mathf.Max(1f, movementMultiplier); _emptyMagRushReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 1f); } internal void RemoveEmptyMagRush(float movementMultiplier, float reloadMultiplier) { if (_emptyMagRushBuffActive) { DeactivateEmptyMagRushBuff(); } _emptyMagRushMoveMultiplier = SafeDivide(_emptyMagRushMoveMultiplier, Mathf.Max(1f, movementMultiplier)); _emptyMagRushReloadMultiplier = SafeDivide(_emptyMagRushReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 1f)); } internal void AddGroundedStance(float damageMultiplier, float movementMultiplier, float reloadMultiplier) { _groundedDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _groundedMoveMultiplier *= Mathf.Max(0.05f, movementMultiplier); _groundedReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 4f); } internal void RemoveGroundedStance(float damageMultiplier, float movementMultiplier, float reloadMultiplier) { if (_groundedBuffActive) { DeactivateGroundedBuff(); } _groundedDamageMultiplier = SafeDivide(_groundedDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _groundedMoveMultiplier = SafeDivide(_groundedMoveMultiplier, Mathf.Max(0.05f, movementMultiplier)); _groundedReloadMultiplier = SafeDivide(_groundedReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 4f)); } internal void AddWallStance(float wallDistance, float damageMultiplier, float speedMultiplier, float reloadMultiplier) { _wallDistanceThreshold += Mathf.Max(0.1f, wallDistance); _wallDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _wallSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _wallReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 4f); } internal void RemoveWallStance(float wallDistance, float damageMultiplier, float speedMultiplier, float reloadMultiplier) { if (_wallBuffActive) { DeactivateWallBuff(); } _wallDistanceThreshold = Mathf.Max(0f, _wallDistanceThreshold - Mathf.Max(0.1f, wallDistance)); _wallDamageMultiplier = SafeDivide(_wallDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _wallSpeedMultiplier = SafeDivide(_wallSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _wallReloadMultiplier = SafeDivide(_wallReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 4f)); } internal void AddLowHealthStance(float healthFractionThreshold, float damageMultiplier, float movementMultiplier, float reloadMultiplier, float lifestealFraction) { _lowHealthThreshold = Mathf.Max(_lowHealthThreshold, Mathf.Clamp(healthFractionThreshold, 0.05f, 0.95f)); _lowHealthDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _lowHealthMoveMultiplier *= Mathf.Max(1f, movementMultiplier); _lowHealthReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 1f); _lowHealthLifestealFraction += Math.Max(0f, lifestealFraction); } internal void RemoveLowHealthStance(float damageMultiplier, float movementMultiplier, float reloadMultiplier, float lifestealFraction) { if (_lowHealthBuffActive) { DeactivateLowHealthBuff(); } _lowHealthDamageMultiplier = SafeDivide(_lowHealthDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _lowHealthMoveMultiplier = SafeDivide(_lowHealthMoveMultiplier, Mathf.Max(1f, movementMultiplier)); _lowHealthReloadMultiplier = SafeDivide(_lowHealthReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 1f)); _lowHealthLifestealFraction = Math.Max(0f, _lowHealthLifestealFraction - Math.Max(0f, lifestealFraction)); if (_lowHealthDamageMultiplier <= 1.001f && _lowHealthMoveMultiplier <= 1.001f && _lowHealthReloadMultiplier >= 0.999f && _lowHealthLifestealFraction <= 0.001f) { _lowHealthThreshold = 0f; } } internal void AddPatientShot(float readyDelay, float damageMultiplier, float speedMultiplier) { float num = Mathf.Max(0.05f, readyDelay); _patientShotDelay = ((_patientShotDelay <= 0f) ? num : Math.Min(_patientShotDelay, num)); _patientShotDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _patientShotSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemovePatientShot(float damageMultiplier, float speedMultiplier) { _patientShotDamageMultiplier = SafeDivide(_patientShotDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _patientShotSpeedMultiplier = SafeDivide(_patientShotSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); if (_patientShotDamageMultiplier <= 1.001f && _patientShotSpeedMultiplier <= 1.001f) { _patientShotDelay = 0f; } } internal void AddChainShot(float window, float damageStep, float speedStep, int maxStacks) { _chainShotWindow = Mathf.Max(_chainShotWindow, Mathf.Max(0.1f, window)); _chainShotDamageStep *= Mathf.Max(1f, damageStep); _chainShotSpeedStep *= Mathf.Max(1f, speedStep); _chainShotMaxStacks = Math.Max(_chainShotMaxStacks, Math.Max(1, maxStacks)); } internal void RemoveChainShot(float damageStep, float speedStep) { _chainShotDamageStep = SafeDivide(_chainShotDamageStep, Mathf.Max(1f, damageStep)); _chainShotSpeedStep = SafeDivide(_chainShotSpeedStep, Mathf.Max(1f, speedStep)); if (_chainShotDamageStep <= 1.001f && _chainShotSpeedStep <= 1.001f) { _chainShotWindow = 0f; _chainShotMaxStacks = 0; _chainShotStacks = 0; } } internal void AddLifesteal(float fraction) { _lifestealFraction += Math.Max(0f, fraction); } internal void RemoveLifesteal(float fraction) { _lifestealFraction = Math.Max(0f, _lifestealFraction - Math.Max(0f, fraction)); } internal void AddPainRush(float duration, float movementMultiplier, float reloadMultiplier) { _painRushDuration = Mathf.Max(_painRushDuration, Mathf.Max(0f, duration)); _painRushMoveMultiplier *= Mathf.Max(1f, movementMultiplier); _painRushReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 1f); } internal void RemovePainRush(float movementMultiplier, float reloadMultiplier) { if (_painRushBuffActive) { DeactivatePainRushBuff(); } _painRushMoveMultiplier = SafeDivide(_painRushMoveMultiplier, Mathf.Max(1f, movementMultiplier)); _painRushReloadMultiplier = SafeDivide(_painRushReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 1f)); } internal void AddDeathVow(float duration, float damageMultiplier, float movementMultiplier, float reloadMultiplier, int maxStoredCharges) { _deathVowDuration = Mathf.Max(_deathVowDuration, Mathf.Max(0.25f, duration)); _deathVowDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _deathVowMoveMultiplier *= Mathf.Max(1f, movementMultiplier); _deathVowReloadMultiplier *= Mathf.Clamp(reloadMultiplier, 0.05f, 1f); _deathVowMaxPendingCharges = Math.Max(_deathVowMaxPendingCharges, Math.Max(1, maxStoredCharges)); } internal void RemoveDeathVow(float damageMultiplier, float movementMultiplier, float reloadMultiplier) { if (_deathVowBuffActive) { DeactivateDeathVowBuff(); } _deathVowDamageMultiplier = SafeDivide(_deathVowDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _deathVowMoveMultiplier = SafeDivide(_deathVowMoveMultiplier, Mathf.Max(1f, movementMultiplier)); _deathVowReloadMultiplier = SafeDivide(_deathVowReloadMultiplier, Mathf.Clamp(reloadMultiplier, 0.05f, 1f)); if (_deathVowDamageMultiplier <= 1.001f && _deathVowMoveMultiplier <= 1.001f && _deathVowReloadMultiplier >= 0.999f) { _deathVowDuration = 0f; _deathVowPendingCharges = 0; _deathVowMaxPendingCharges = 0; } } internal void AddHitCharge(float damageMultiplier, float speedMultiplier) { _hitChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _hitChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); } internal void RemoveHitCharge(float damageMultiplier, float speedMultiplier) { _hitChargeDamageMultiplier = SafeDivide(_hitChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _hitChargeSpeedMultiplier = SafeDivide(_hitChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); if (_hitChargeDamageMultiplier <= 1.001f && _hitChargeSpeedMultiplier <= 1.001f) { _hitChargeShots = 0; } } internal void AddCadenceRound(int everyShots, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { int num = Math.Max(2, everyShots); _cadenceEveryShots = ((_cadenceEveryShots <= 0) ? num : Math.Min(_cadenceEveryShots, num)); _cadenceDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _cadenceSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _cadenceProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); if (unblockable) { _cadenceUnblockableCount++; } } internal void RemoveCadenceRound(float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, bool unblockable) { _cadenceDamageMultiplier = SafeDivide(_cadenceDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _cadenceSpeedMultiplier = SafeDivide(_cadenceSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _cadenceProjectileSizeMultiplier = SafeDivide(_cadenceProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); if (unblockable) { _cadenceUnblockableCount = Math.Max(0, _cadenceUnblockableCount - 1); } if (_cadenceDamageMultiplier <= 1.001f && _cadenceSpeedMultiplier <= 1.001f && _cadenceProjectileSizeMultiplier <= 1.001f && _cadenceUnblockableCount <= 0) { _cadenceEveryShots = 0; } } internal void AddHeatMode(int shotsToOverheat, float heatWindow, float perStackDamageMultiplier, float perStackSpeedMultiplier, float overheatDuration, float overheatReloadMultiplier, float overheatSpreadPenalty) { _heatShotsToOverheat += Math.Max(1, shotsToOverheat); _heatWindowSeconds += Mathf.Max(0.05f, heatWindow); _heatPerStackDamageMultiplier *= Mathf.Max(1f, perStackDamageMultiplier); _heatPerStackSpeedMultiplier *= Mathf.Max(1f, perStackSpeedMultiplier); _heatOverheatDuration += Mathf.Max(0.1f, overheatDuration); _heatOverheatReloadMultiplier *= Mathf.Clamp(overheatReloadMultiplier, 1f, 10f); _heatOverheatSpreadPenalty += Mathf.Max(0f, overheatSpreadPenalty); } internal void RemoveHeatMode(int shotsToOverheat, float heatWindow, float perStackDamageMultiplier, float perStackSpeedMultiplier, float overheatDuration, float overheatReloadMultiplier, float overheatSpreadPenalty) { if (_heatOverheatActive) { DeactivateHeatOverheat(); } _heatShotsToOverheat = Math.Max(0, _heatShotsToOverheat - Math.Max(1, shotsToOverheat)); _heatWindowSeconds = Mathf.Max(0f, _heatWindowSeconds - Mathf.Max(0.05f, heatWindow)); _heatPerStackDamageMultiplier = SafeDivide(_heatPerStackDamageMultiplier, Mathf.Max(1f, perStackDamageMultiplier)); _heatPerStackSpeedMultiplier = SafeDivide(_heatPerStackSpeedMultiplier, Mathf.Max(1f, perStackSpeedMultiplier)); _heatOverheatDuration = Mathf.Max(0f, _heatOverheatDuration - Mathf.Max(0.1f, overheatDuration)); _heatOverheatReloadMultiplier = SafeDivide(_heatOverheatReloadMultiplier, Mathf.Clamp(overheatReloadMultiplier, 1f, 10f)); _heatOverheatSpreadPenalty = Mathf.Max(0f, _heatOverheatSpreadPenalty - Mathf.Max(0f, overheatSpreadPenalty)); if (_heatShotsToOverheat <= 0) { _heatStacks = 0; } } internal void AddMarkedPrey(int marksApplied, int maxStacks, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, float healFraction) { _markApplyCount += Math.Max(0, marksApplied); _markMaxStacks = Math.Max(_markMaxStacks, Math.Max(1, maxStacks)); _markChargeDamageMultiplier *= Mathf.Max(1f, damageMultiplier); _markChargeSpeedMultiplier *= Mathf.Max(1f, speedMultiplier); _markChargeProjectileSizeMultiplier *= Mathf.Max(1f, projectileSizeMultiplier); _markChargeHealFraction += Math.Max(0f, healFraction); } internal void RemoveMarkedPrey(int marksApplied, float damageMultiplier, float speedMultiplier, float projectileSizeMultiplier, float healFraction) { _markApplyCount = Math.Max(0, _markApplyCount - Math.Max(0, marksApplied)); _markChargeDamageMultiplier = SafeDivide(_markChargeDamageMultiplier, Mathf.Max(1f, damageMultiplier)); _markChargeSpeedMultiplier = SafeDivide(_markChargeSpeedMultiplier, Mathf.Max(1f, speedMultiplier)); _markChargeProjectileSizeMultiplier = SafeDivide(_markChargeProjectileSizeMultiplier, Mathf.Max(1f, projectileSizeMultiplier)); _markChargeHealFraction = Math.Max(0f, _markChargeHealFraction - Math.Max(0f, healFraction)); if (_markApplyCount <= 0 && _markChargeDamageMultiplier <= 1.001f && _markChargeSpeedMultiplier <= 1.001f && _markChargeProjectileSizeMultiplier <= 1.001f && _markChargeHealFraction <= 0.001f) { _markMaxStacks = 0; _markChargeShots = 0; } } internal void ObserveCanBlock(bool canBlock) { if (canBlock && (!_observedCanBlock || !_lastObservedCanBlock)) { _blockReadyAt = CurrentTime; } _observedCanBlock = true; _lastObservedCanBlock = canBlock; } internal void NotifySuccessfulBlock() { //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) ResolveReferences(); if (_counterChargeDamageMultiplier > 1.001f || _counterChargeSpeedMultiplier > 1.001f) { _counterChargeShots++; } if (_blockAmmoGain > 0 && _gun != null && _gunAmmo != null && TryGetMemberValue(_gun, "ammo", out var value)) { SetMemberValue(_gun, "ammo", Math.Min(_gunAmmo.maxAmmo, value + _blockAmmoGain)); } if (_blockTempoDuration > 0f && (_blockTempoMoveMultiplier > 1.001f || _blockTempoReloadMultiplier < 0.999f)) { if (!_blockTempoBuffActive) { ActivateBlockTempoBuff(); } _blockTempoExpiresAt = Mathf.Max(_blockTempoExpiresAt, CurrentTime + _blockTempoDuration); } if (_blockGhostDamageMultiplier > 1.001f || _blockGhostSpeedMultiplier > 1.001f || _blockGhostProjectileSizeMultiplier > 1.001f || _blockGhostUnblockableCount > 0) { _blockGhostShots++; } if (_perfectParryWindowSeconds > 0.001f && (_perfectParryDamageMultiplier > 1.001f || _perfectParrySpeedMultiplier > 1.001f || _perfectParryProjectileSizeMultiplier > 1.001f || _perfectParryUnblockableCount > 0) && (BlockCanBlockMethod == null || (_observedCanBlock && CurrentTime - _blockReadyAt <= _perfectParryWindowSeconds))) { _perfectParryShots++; } if (_blockZoneDuration > 0.05f && _blockZoneRadius > 0.1f) { SpawnHazardZone(((Component)this).transform.position, _blockZoneDuration, _blockZoneRadius, _blockZonePulseInterval, _blockZoneMarksApplied, _blockZoneMovementMultiplier, _blockZoneReloadMultiplier, _blockZoneBlockCooldownMultiplier, new Color(0.3f, 0.72f, 1f, 0.38f)); } } internal bool PrepareAttack() { ResolveReferences(); if (_gun == null || _gunAmmo == null) { return false; } float num = 1f; float num2 = 1f; float num3 = 1f; bool flag = false; bool flag2 = false; int num4 = 0; float num5 = 0f; _pendingConsumeCounterCharge = _counterChargeShots > 0; if (_pendingConsumeCounterCharge) { num *= _counterChargeDamageMultiplier; num2 *= _counterChargeSpeedMultiplier; } _pendingConsumeHitCharge = _hitChargeShots > 0; if (_pendingConsumeHitCharge) { num *= _hitChargeDamageMultiplier; num2 *= _hitChargeSpeedMultiplier; } _pendingConsumePerfectParry = _perfectParryShots > 0; if (_pendingConsumePerfectParry) { num *= _perfectParryDamageMultiplier; num2 *= _perfectParrySpeedMultiplier; num3 *= _perfectParryProjectileSizeMultiplier; flag |= _perfectParryUnblockableCount > 0; } _pendingConsumeBlockGhost = _blockGhostShots > 0; if (_pendingConsumeBlockGhost) { num *= _blockGhostDamageMultiplier; num2 *= _blockGhostSpeedMultiplier; num3 *= _blockGhostProjectileSizeMultiplier; flag |= _blockGhostUnblockableCount > 0; } _pendingConsumeMarkCharge = _markChargeShots > 0; if (_pendingConsumeMarkCharge) { num *= _markChargeDamageMultiplier; num2 *= _markChargeSpeedMultiplier; num3 *= _markChargeProjectileSizeMultiplier; } _pendingConsumeReloadCharge = _reloadChargeShots > 0; if (_pendingConsumeReloadCharge) { num *= _reloadChargeDamageMultiplier; num2 *= _reloadChargeSpeedMultiplier; num3 *= _reloadChargeProjectileSizeMultiplier; flag |= _reloadChargeUnblockableCount > 0; } _pendingConsumeKillCharge = _killChargeShots > 0; if (_pendingConsumeKillCharge) { num *= _killChargeDamageMultiplier; num2 *= _killChargeSpeedMultiplier; num3 *= _killChargeProjectileSizeMultiplier; flag |= _killChargeUnblockableCount > 0; } _pendingConsumeMissCharge = _missChargeShots > 0; if (_pendingConsumeMissCharge) { num *= _missChargeDamageMultiplier; num2 *= _missChargeSpeedMultiplier; num3 *= _missChargeProjectileSizeMultiplier; flag |= _missChargeUnblockableCount > 0; } _pendingConsumeReloadMode = _reloadModePreparedShots > 0; if (_pendingConsumeReloadMode) { num *= _reloadModeDamageMultiplier; num2 *= _reloadModeSpeedMultiplier; num3 *= _reloadModeProjectileSizeMultiplier; flag2 |= _reloadModeIgnoreWallsCount > 0; num4 += _reloadModeExtraProjectiles; num5 += _reloadModeSpreadPenalty; } _attackHadAmmoValue = TryGetMemberValue(_gun, "ammo", out var value); _attackAmmoBefore = (_attackHadAmmoValue ? value : 0); if ((_chamberedDamageMultiplier > 1.001f || _chamberedSpeedMultiplier > 1.001f || _chamberedProjectileSizeMultiplier > 1.001f || _chamberedUnblockableCount > 0) && _attackHadAmmoValue && _gunAmmo.maxAmmo > 0 && value >= _gunAmmo.maxAmmo) { num *= _chamberedDamageMultiplier; num2 *= _chamberedSpeedMultiplier; num3 *= _chamberedProjectileSizeMultiplier; flag |= _chamberedUnblockableCount > 0; } if ((_cadenceDamageMultiplier > 1.001f || _cadenceSpeedMultiplier > 1.001f || _cadenceProjectileSizeMultiplier > 1.001f || _cadenceUnblockableCount > 0) && _cadenceEveryShots > 0 && (_successfulAttacks + 1) % _cadenceEveryShots == 0) { num *= _cadenceDamageMultiplier; num2 *= _cadenceSpeedMultiplier; num3 *= _cadenceProjectileSizeMultiplier; flag |= _cadenceUnblockableCount > 0; } if ((_firstShotDamageMultiplier > 1.001f || _firstShotSpeedMultiplier > 1.001f) && _attackHadAmmoValue && _gunAmmo.maxAmmo > 0 && value >= _gunAmmo.maxAmmo) { num *= _firstShotDamageMultiplier; num2 *= _firstShotSpeedMultiplier; } if ((_lastBulletDamageMultiplier > 1.001f || _lastBulletSpeedMultiplier > 1.001f) && _attackHadAmmoValue && value <= 1) { num *= _lastBulletDamageMultiplier; num2 *= _lastBulletSpeedMultiplier; } if ((_lastShellDamageMultiplier > 1.001f || _lastShellSpeedMultiplier > 1.001f || _lastShellProjectileSizeMultiplier > 1.001f || _lastShellUnblockableCount > 0) && _attackHadAmmoValue && value <= 1) { num *= _lastShellDamageMultiplier; num2 *= _lastShellSpeedMultiplier; num3 *= _lastShellProjectileSizeMultiplier; flag |= _lastShellUnblockableCount > 0; } if ((_patientShotDamageMultiplier > 1.001f || _patientShotSpeedMultiplier > 1.001f) && _patientShotDelay > 0f && CurrentTime - _lastSuccessfulAttackAt >= _patientShotDelay) { num *= _patientShotDamageMultiplier; num2 *= _patientShotSpeedMultiplier; } if ((_chainShotDamageStep > 1.001f || _chainShotSpeedStep > 1.001f) && _chainShotStacks > 0 && _chainShotWindow > 0f && CurrentTime - _lastSuccessfulAttackAt <= _chainShotWindow) { num *= Mathf.Pow(_chainShotDamageStep, (float)_chainShotStacks); num2 *= Mathf.Pow(_chainShotSpeedStep, (float)_chainShotStacks); } if (!_heatOverheatActive && _heatStacks > 0 && _heatShotsToOverheat > 0 && (_heatPerStackDamageMultiplier > 1.001f || _heatPerStackSpeedMultiplier > 1.001f)) { num *= Mathf.Pow(_heatPerStackDamageMultiplier, (float)_heatStacks); num2 *= Mathf.Pow(_heatPerStackSpeedMultiplier, (float)_heatStacks); } bool flag3 = !NearlyEqual(num3, 1f) || flag || flag2 || num4 != 0 || !NearlyEqual(num5, 0f); if (NearlyEqual(num, 1f) && NearlyEqual(num2, 1f) && !flag3) { return false; } _attackBuffApplied = true; _attackDamageBefore = _gun.damage; _attackSpeedBefore = _gun.projectileSpeed; Gun gun = _gun; gun.damage *= num; Gun gun2 = _gun; gun2.projectileSpeed *= num2; if (!NearlyEqual(num3, 1f) && TryGetMemberValue(_gun, "projectileSize", out var value2)) { _attackProjectileSizeBefore = value2; SetMemberValue(_gun, "projectileSize", value2 * num3); _attackProjectileSizeApplied = true; } if (num4 != 0) { _attackProjectilesBefore = _gun.numberOfProjectiles; Gun gun3 = _gun; gun3.numberOfProjectiles += num4; _attackExtraProjectilesApplied = true; } if (!NearlyEqual(num5, 0f)) { _attackSpreadBefore = _gun.spread; Gun gun4 = _gun; gun4.spread += num5; _attackSpreadApplied = true; } if (flag && TryGetMemberValue(_gun, "unblockable", out var value3)) { _attackUnblockableBefore = value3; SetMemberValue(_gun, "unblockable", value: true); _attackUnblockableApplied = true; } if (flag2 && TryGetMemberValue(_gun, "ignoreWalls", out var value4)) { _attackIgnoreWallsBefore = value4; SetMemberValue(_gun, "ignoreWalls", value: true); _attackIgnoreWallsApplied = true; } return true; } internal void CompleteAttack(bool attackSucceeded) { if (_attackBuffApplied && _gun != null) { _gun.damage = _attackDamageBefore; _gun.projectileSpeed = _attackSpeedBefore; } if (_attackProjectileSizeApplied && _gun != null) { SetMemberValue(_gun, "projectileSize", _attackProjectileSizeBefore); } if (_attackExtraProjectilesApplied && _gun != null) { _gun.numberOfProjectiles = _attackProjectilesBefore; } if (_attackSpreadApplied && _gun != null) { _gun.spread = _attackSpreadBefore; } if (_attackUnblockableApplied && _gun != null) { SetMemberValue(_gun, "unblockable", _attackUnblockableBefore); } if (_attackIgnoreWallsApplied && _gun != null) { SetMemberValue(_gun, "ignoreWalls", _attackIgnoreWallsBefore); } if (attackSucceeded && _pendingConsumeCounterCharge) { _counterChargeShots = Math.Max(0, _counterChargeShots - 1); } if (attackSucceeded && _pendingConsumeHitCharge) { _hitChargeShots = Math.Max(0, _hitChargeShots - 1); } if (attackSucceeded && _pendingConsumePerfectParry) { _perfectParryShots = Math.Max(0, _perfectParryShots - 1); } if (attackSucceeded && _pendingConsumeBlockGhost) { _blockGhostShots = Math.Max(0, _blockGhostShots - 1); } if (attackSucceeded && _pendingConsumeMarkCharge) { _markChargeShots = Math.Max(0, _markChargeShots - 1); } if (attackSucceeded && _pendingConsumeReloadCharge) { _reloadChargeShots = Math.Max(0, _reloadChargeShots - 1); } if (attackSucceeded && _pendingConsumeKillCharge) { _killChargeShots = Math.Max(0, _killChargeShots - 1); } if (attackSucceeded && _pendingConsumeMissCharge) { _missChargeShots = Math.Max(0, _missChargeShots - 1); } if (attackSucceeded && _pendingConsumeReloadMode) { _reloadModePreparedShots = Math.Max(0, _reloadModePreparedShots - 1); } if (attackSucceeded) { bool flag = _chainShotWindow > 0f && CurrentTime - _lastSuccessfulAttackAt <= _chainShotWindow; if (_chainShotDamageStep > 1.001f || _chainShotSpeedStep > 1.001f) { _chainShotStacks = ((!flag) ? 1 : Math.Min(_chainShotMaxStacks, _chainShotStacks + 1)); } if (_emptyMagRushDuration > 0f && (_emptyMagRushMoveMultiplier > 1.001f || _emptyMagRushReloadMultiplier < 0.999f) && _attackHadAmmoValue && _attackAmmoBefore <= 1) { if (!_emptyMagRushBuffActive) { ActivateEmptyMagRushBuff(); } _emptyMagRushExpiresAt = Mathf.Max(_emptyMagRushExpiresAt, CurrentTime + _emptyMagRushDuration); } if (_missChargeDamageMultiplier > 1.001f || _missChargeSpeedMultiplier > 1.001f || _missChargeProjectileSizeMultiplier > 1.001f || _missChargeUnblockableCount > 0) { _pendingMissCheck = true; _pendingMissExpiresAt = CurrentTime + 0.9f; } _successfulAttacks++; _lastSuccessfulAttackAt = CurrentTime; if (_heatShotsToOverheat > 0 && _heatWindowSeconds > 0f && !_heatOverheatActive && (_heatPerStackDamageMultiplier > 1.001f || _heatPerStackSpeedMultiplier > 1.001f)) { _heatStacks = ((!(CurrentTime - _lastHeatShotAt <= _heatWindowSeconds)) ? 1 : Math.Min(_heatShotsToOverheat, _heatStacks + 1)); _lastHeatShotAt = CurrentTime; if (_heatStacks >= _heatShotsToOverheat) { _heatStacks = 0; ActivateHeatOverheat(); } } } else { _pendingMissCheck = false; _pendingMissExpiresAt = float.NegativeInfinity; } _attackBuffApplied = false; _pendingConsumeCounterCharge = false; _pendingConsumeHitCharge = false; _pendingConsumePerfectParry = false; _pendingConsumeBlockGhost = false; _pendingConsumeMarkCharge = false; _pendingConsumeReloadCharge = false; _pendingConsumeKillCharge = false; _pendingConsumeMissCharge = false; _pendingConsumeReloadMode = false; _attackHadAmmoValue = false; _attackAmmoBefore = 0; _attackExtraProjectilesApplied = false; _attackProjectilesBefore = 0; _attackSpreadApplied = false; _attackSpreadBefore = 0f; _attackProjectileSizeApplied = false; _attackProjectileSizeBefore = 0f; _attackUnblockableApplied = false; _attackUnblockableBefore = false; _attackIgnoreWallsApplied = false; _attackIgnoreWallsBefore = false; } private void Update() { ResolveReferences(); if (_data == null || _gun == null) { return; } bool flag = (_airborneDamageMultiplier > 1.001f || _airborneSpeedMultiplier > 1.001f) && !GetMemberValue(_data, "isGrounded", fallback: true); if (flag && !_airborneBuffActive) { ActivateAirborneBuff(); } else if (!flag && _airborneBuffActive) { DeactivateAirborneBuff(); } bool flag2 = ShouldActivateLowHealthBuff(); if (flag2 && !_lowHealthBuffActive) { ActivateLowHealthBuff(); } else if (!flag2 && _lowHealthBuffActive) { DeactivateLowHealthBuff(); } bool flag3 = ShouldActivateGroundedBuff(); if (flag3 && !_groundedBuffActive) { ActivateGroundedBuff(); } else if (!flag3 && _groundedBuffActive) { DeactivateGroundedBuff(); } bool flag4 = ShouldActivateWallBuff(); if (flag4 && !_wallBuffActive) { ActivateWallBuff(); } else if (!flag4 && _wallBuffActive) { DeactivateWallBuff(); } if (_gunAmmo != null && TryGetMemberValue(_gun, "ammo", out var value)) { if (!_observedAmmoInitialized) { _lastObservedAmmo = value; _observedAmmoInitialized = true; _observedSpentAmmoSinceFull = value < _gunAmmo.maxAmmo; } else { if (value < _gunAmmo.maxAmmo) { _observedSpentAmmoSinceFull = true; } else if (_observedSpentAmmoSinceFull && _lastObservedAmmo < _gunAmmo.maxAmmo && value >= _gunAmmo.maxAmmo) { if (_reloadChargeDamageMultiplier > 1.001f || _reloadChargeSpeedMultiplier > 1.001f || _reloadChargeProjectileSizeMultiplier > 1.001f || _reloadChargeUnblockableCount > 0) { _reloadChargeShots++; } if (_reloadModeShotsPerReload > 0 && (_reloadModeDamageMultiplier > 1.001f || _reloadModeSpeedMultiplier > 1.001f || _reloadModeProjectileSizeMultiplier > 1.001f || _reloadModeIgnoreWallsCount > 0 || _reloadModeExtraProjectiles > 0 || _reloadModeSpreadPenalty > 0.001f)) { _reloadModePreparedShots += Math.Max(1, _reloadModeShotsPerReload); } _observedSpentAmmoSinceFull = false; } _lastObservedAmmo = value; } } else { _observedAmmoInitialized = false; _observedSpentAmmoSinceFull = false; _lastObservedAmmo = 0; } if (_pendingMissCheck && CurrentTime >= _pendingMissExpiresAt) { if (_missChargeDamageMultiplier > 1.001f || _missChargeSpeedMultiplier > 1.001f || _missChargeProjectileSizeMultiplier > 1.001f || _missChargeUnblockableCount > 0) { _missChargeShots++; } _pendingMissCheck = false; _pendingMissExpiresAt = float.NegativeInfinity; } if (_blockTempoBuffActive && CurrentTime >= _blockTempoExpiresAt) { DeactivateBlockTempoBuff(); } if (_painRushBuffActive && CurrentTime >= _painRushExpiresAt) { DeactivatePainRushBuff(); } if (_emptyMagRushBuffActive && CurrentTime >= _emptyMagRushExpiresAt) { DeactivateEmptyMagRushBuff(); } if (_deathVowBuffActive && CurrentTime >= _deathVowExpiresAt) { DeactivateDeathVowBuff(); } if (_heatOverheatActive && CurrentTime >= _heatOverheatExpiresAt) { DeactivateHeatOverheat(); } if (_chainShotStacks > 0 && _chainShotWindow > 0f && CurrentTime - _lastSuccessfulAttackAt > _chainShotWindow) { _chainShotStacks = 0; } if (_heatStacks > 0 && _heatWindowSeconds > 0f && CurrentTime - _lastHeatShotAt > _heatWindowSeconds) { _heatStacks = 0; } UpdateAreaControl(); } private void OnDisable() { if (_airborneBuffActive) { DeactivateAirborneBuff(); } if (_blockTempoBuffActive) { DeactivateBlockTempoBuff(); } if (_painRushBuffActive) { DeactivatePainRushBuff(); } if (_emptyMagRushBuffActive) { DeactivateEmptyMagRushBuff(); } if (_groundedBuffActive) { DeactivateGroundedBuff(); } if (_wallBuffActive) { DeactivateWallBuff(); } if (_lowHealthBuffActive) { DeactivateLowHealthBuff(); } if (_deathVowBuffActive) { DeactivateDeathVowBuff(); } if (_heatOverheatActive) { DeactivateHeatOverheat(); } } private Player FindNearestEnemy(float range) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (_player == null || range <= 0.05f) { return null; } float num = range * range; Player result = null; List list = PlayerManager.instance?.players; if (list == null) { return null; } foreach (Player item in list) { if (item != null && item != _player && item.data != null && !GetMemberValue(item.data, "dead", fallback: false)) { Vector3 val = ((Component)item).transform.position - ((Component)this).transform.position; float num2 = val.x * val.x + val.y * val.y + val.z * val.z; if (num2 <= num) { num = num2; result = item; } } } return result; } private void ResolveReferences() { if (_player == null) { _player = ((Component)this).gameObject.GetComponent(); } if (_player != null) { _data = _player.data ?? ((Component)_player).GetComponent(); _characterStats = ((Component)_player).GetComponent(); _gun = ((Component)_player).GetComponentInChildren(); _gunAmmo = ((Component)_player).GetComponentInChildren(); } } private void ActivateAirborneBuff() { ResolveReferences(); if (_gun != null) { Gun gun = _gun; gun.damage *= _airborneDamageMultiplier; Gun gun2 = _gun; gun2.projectileSpeed *= _airborneSpeedMultiplier; _airborneBuffActive = true; } } private void DeactivateAirborneBuff() { ResolveReferences(); if (_gun != null) { _gun.damage = SafeDivide(_gun.damage, _airborneDamageMultiplier); _gun.projectileSpeed = SafeDivide(_gun.projectileSpeed, _airborneSpeedMultiplier); } _airborneBuffActive = false; } private void ActivateBlockTempoBuff() { ResolveReferences(); if (_characterStats != null && _gunAmmo != null) { CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _blockTempoMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _blockTempoReloadMultiplier; _blockTempoBuffActive = true; } } private void DeactivateBlockTempoBuff() { ResolveReferences(); if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _blockTempoMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _blockTempoReloadMultiplier); } _blockTempoBuffActive = false; _blockTempoExpiresAt = 0f; } private void ActivatePainRushBuff() { ResolveReferences(); if (_characterStats != null && _gunAmmo != null) { CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _painRushMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _painRushReloadMultiplier; _painRushBuffActive = true; } } private void DeactivatePainRushBuff() { ResolveReferences(); if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _painRushMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _painRushReloadMultiplier); } _painRushBuffActive = false; _painRushExpiresAt = 0f; } private void ActivateEmptyMagRushBuff() { ResolveReferences(); if (_characterStats != null && _gunAmmo != null) { CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _emptyMagRushMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _emptyMagRushReloadMultiplier; _emptyMagRushBuffActive = true; } } private void DeactivateEmptyMagRushBuff() { ResolveReferences(); if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _emptyMagRushMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _emptyMagRushReloadMultiplier); } _emptyMagRushBuffActive = false; _emptyMagRushExpiresAt = 0f; } private bool ShouldActivateGroundedBuff() { if (_groundedDamageMultiplier <= 1.001f && _groundedMoveMultiplier <= 1.001f && _groundedReloadMultiplier >= 0.999f) { return false; } if (_data == null || GetMemberValue(_data, "dead", fallback: false)) { return false; } return GetMemberValue(_data, "isGrounded", fallback: true); } private void ActivateGroundedBuff() { ResolveReferences(); if (_gun != null && _characterStats != null && _gunAmmo != null) { Gun gun = _gun; gun.damage *= _groundedDamageMultiplier; CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _groundedMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _groundedReloadMultiplier; _groundedBuffActive = true; } } private void DeactivateGroundedBuff() { ResolveReferences(); if (_gun != null) { _gun.damage = SafeDivide(_gun.damage, _groundedDamageMultiplier); } if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _groundedMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _groundedReloadMultiplier); } _groundedBuffActive = false; } private bool ShouldActivateWallBuff() { if (_wallDistanceThreshold <= 0.001f || (_wallDamageMultiplier <= 1.001f && _wallSpeedMultiplier <= 1.001f && _wallReloadMultiplier >= 0.999f)) { return false; } if (_data == null || GetMemberValue(_data, "dead", fallback: false)) { return false; } return IsNearWall(_wallDistanceThreshold); } private void ActivateWallBuff() { ResolveReferences(); if (_gun != null && _gunAmmo != null) { Gun gun = _gun; gun.damage *= _wallDamageMultiplier; Gun gun2 = _gun; gun2.projectileSpeed *= _wallSpeedMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _wallReloadMultiplier; _wallBuffActive = true; } } private void DeactivateWallBuff() { ResolveReferences(); if (_gun != null) { _gun.damage = SafeDivide(_gun.damage, _wallDamageMultiplier); _gun.projectileSpeed = SafeDivide(_gun.projectileSpeed, _wallSpeedMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _wallReloadMultiplier); } _wallBuffActive = false; } private bool ShouldActivateLowHealthBuff() { if (_lowHealthThreshold <= 0f || (_lowHealthDamageMultiplier <= 1.001f && _lowHealthMoveMultiplier <= 1.001f && _lowHealthReloadMultiplier >= 0.999f && _lowHealthLifestealFraction <= 0.001f)) { return false; } if (_data == null || GetMemberValue(_data, "dead", fallback: false)) { return false; } if (TryGetHealthFraction(out var healthFraction)) { return healthFraction <= _lowHealthThreshold; } return false; } private void ActivateLowHealthBuff() { ResolveReferences(); if (_gun != null && _characterStats != null && _gunAmmo != null) { Gun gun = _gun; gun.damage *= _lowHealthDamageMultiplier; CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _lowHealthMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _lowHealthReloadMultiplier; _lowHealthBuffActive = true; } } private void DeactivateLowHealthBuff() { ResolveReferences(); if (_gun != null) { _gun.damage = SafeDivide(_gun.damage, _lowHealthDamageMultiplier); } if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _lowHealthMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _lowHealthReloadMultiplier); } _lowHealthBuffActive = false; } private void ActivateDeathVowBuff() { ResolveReferences(); if (_gun != null && _characterStats != null && _gunAmmo != null) { Gun gun = _gun; gun.damage *= _deathVowDamageMultiplier; CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= _deathVowMoveMultiplier; GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _deathVowReloadMultiplier; _deathVowBuffActive = true; } } private void DeactivateDeathVowBuff() { ResolveReferences(); if (_gun != null) { _gun.damage = SafeDivide(_gun.damage, _deathVowDamageMultiplier); } if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _deathVowMoveMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _deathVowReloadMultiplier); } _deathVowBuffActive = false; _deathVowExpiresAt = 0f; } private void ActivateHeatOverheat() { ResolveReferences(); if (_gun != null && _gunAmmo != null) { GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= _heatOverheatReloadMultiplier; Gun gun = _gun; gun.spread += _heatOverheatSpreadPenalty; _heatOverheatActive = true; _heatOverheatExpiresAt = Mathf.Max(_heatOverheatExpiresAt, CurrentTime + _heatOverheatDuration); } } private void DeactivateHeatOverheat() { ResolveReferences(); if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _heatOverheatReloadMultiplier); } if (_gun != null) { Gun gun = _gun; gun.spread -= _heatOverheatSpreadPenalty; } _heatOverheatActive = false; _heatOverheatExpiresAt = 0f; } private bool IsNearWall(float distance) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (distance <= 0.001f || Collider2DType == null || FindObjectsOfTypeMethod == null) { return false; } if (!(FindObjectsOfTypeMethod.Invoke(null, new object[1] { Collider2DType }) is Array array)) { return false; } Vector3 position = ((Component)this).transform.position; Transform rootTransform = GetRootTransform(((Component)this).transform); for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value == null || GetMemberValue(value, "isTrigger", fallback: false)) { continue; } Transform memberValue = GetMemberValue(value, "transform", null); if (memberValue == null || GetRootTransform(memberValue) == rootTransform) { continue; } Transform rootTransform2 = GetRootTransform(memberValue); GameObject val = ((rootTransform2 != null) ? ((Component)rootTransform2).gameObject : null); if (val == null || (val.GetComponent() == null && val.GetComponent() == null)) { Vector3 position2 = memberValue.position; if (Math.Abs(position2.x - position.x) <= distance && Math.Abs(position2.y - position.y) <= 1.75f) { return true; } } } return false; } private static Transform GetRootTransform(Transform current) { Transform val = current; while (val != null && val.parent != null) { val = val.parent; } return val; } private bool TryGetHealthFraction(out float healthFraction) { healthFraction = 0f; ResolveReferences(); Player player = _player; HealthHandler target = ((player != null) ? ((Component)player).GetComponent() : null) ?? GetMemberValue(_data, "healthHandler", null); float value = 0f; if (!TryGetFirstMemberValue(target, out value, "health", "currentHealth", "hp")) { return false; } float value2 = ((_data != null && _data.maxHealth > 0.001f) ? _data.maxHealth : 0f); if (value2 <= 0.001f && !TryGetFirstMemberValue(target, out value2, "maxHealth", "maxHp")) { return false; } if (value2 <= 0.001f) { return false; } float num = value / value2; if (num < 0f) { healthFraction = 0f; } else if (num > 1f) { healthFraction = 1f; } else { healthFraction = num; } return true; } internal void NotifyPointEnded() { ResolveReferences(); if (!(_deathVowDuration <= 0f) && (!(_deathVowDamageMultiplier <= 1.001f) || !(_deathVowMoveMultiplier <= 1.001f) || !(_deathVowReloadMultiplier >= 0.999f)) && _data != null && GetMemberValue(_data, "dead", fallback: false)) { _deathVowPendingCharges = Mathf.Clamp(_deathVowPendingCharges + 1, 0, Math.Max(1, _deathVowMaxPendingCharges)); } } internal void NotifyRoundStarted() { if (_deathVowPendingCharges > 0 && !(_deathVowDuration <= 0f) && (!(_deathVowDamageMultiplier <= 1.001f) || !(_deathVowMoveMultiplier <= 1.001f) || !(_deathVowReloadMultiplier >= 0.999f))) { if (_deathVowBuffActive) { DeactivateDeathVowBuff(); } ActivateDeathVowBuff(); _deathVowExpiresAt = Mathf.Max(_deathVowExpiresAt, CurrentTime + _deathVowDuration); _deathVowPendingCharges = Math.Max(0, _deathVowPendingCharges - 1); } } internal void ResetSessionState() { _deathVowPendingCharges = 0; _pendingMissCheck = false; _pendingMissExpiresAt = float.NegativeInfinity; _observedAmmoInitialized = false; _observedSpentAmmoSinceFull = false; _lastObservedAmmo = 0; _observedCanBlock = false; _lastObservedCanBlock = false; _blockReadyAt = float.NegativeInfinity; _heatStacks = 0; _lastHeatShotAt = float.NegativeInfinity; _reloadModePreparedShots = 0; _nextOrbitWardPulseAt = ((_orbitWardPulseInterval > 0.05f) ? (CurrentTime + _orbitWardPulseInterval) : 0f); _nextTrailZoneAt = ((_trailZoneSpawnInterval > 0.1f) ? (CurrentTime + _trailZoneSpawnInterval * 0.5f) : 0f); _nextBeaconDropAt = ((_beaconDropSpawnInterval > 0.1f) ? (CurrentTime + _beaconDropSpawnInterval * 0.5f) : 0f); if (_deathVowBuffActive) { DeactivateDeathVowBuff(); } if (_groundedBuffActive) { DeactivateGroundedBuff(); } if (_wallBuffActive) { DeactivateWallBuff(); } if (_lowHealthBuffActive) { DeactivateLowHealthBuff(); } if (_heatOverheatActive) { DeactivateHeatOverheat(); } } private void TryHeal(float amount) { if (!(amount <= 0.001f)) { ResolveReferences(); Player player = _player; HealthHandler val = ((player != null) ? ((Component)player).GetComponent() : null) ?? GetMemberValue(_data, "healthHandler", null); MethodInfo methodInfo = ((object)val)?.GetType().GetMethod("Heal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null); if (methodInfo != null) { methodInfo.Invoke(val, new object[1] { amount }); } } } private static float SafeDivide(float value, float divisor) { if (NearlyEqual(divisor, 0f)) { return value; } return value / divisor; } internal static Player ResolvePlayerFromGun(Gun gun) { object obj = GetMemberValue(gun, "player", null); if (obj == null) { obj = ((gun != null) ? ((Component)gun).GetComponent() : null); if (obj == null) { if (gun == null) { return null; } obj = ((Component)gun).GetComponentInChildren(); } } return (Player)obj; } internal static Player ResolvePlayerFromCharacterData(CharacterData data) { object obj = GetMemberValue(data, "player", null); if (obj == null) { obj = ((data != null) ? ((Component)data).GetComponent() : null); if (obj == null) { if (data == null) { return null; } obj = ((Component)data).GetComponentInChildren(); } } return (Player)obj; } internal static float ReadDamageMagnitude(object damage) { if (damage == null) { return 0f; } object memberObjectValue = GetMemberObjectValue(damage, "magnitude"); if (memberObjectValue != null) { try { return Convert.ToSingle(memberObjectValue); } catch { } } if (TryGetMemberValue(damage, "x", out var value) && TryGetMemberValue(damage, "y", out var value2)) { return (float)Math.Sqrt(value * value + value2 * value2); } return 0f; } internal static object ScaleDamageArgument(object damage, float multiplier) { if (damage == null || multiplier <= 0.001f || NearlyEqual(multiplier, 1f)) { return damage; } if (TryGetMemberValue(damage, "x", out var value) && TryGetMemberValue(damage, "y", out var value2)) { SetMemberValue(damage, "x", value * multiplier); SetMemberValue(damage, "y", value2 * multiplier); return damage; } if (damage is float num) { return num * multiplier; } if (damage is double num2) { return num2 * (double)multiplier; } if (damage is int num3) { return Mathf.RoundToInt((float)num3 * multiplier); } return damage; } internal static bool IsBlocking(Block block) { if (block == null) { return false; } MethodInfo method = ((object)block).GetType().GetMethod("IsBlocking", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null) { return false; } object obj = method.Invoke(block, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool TryGetMemberValue(object target, string memberName, out T value) { if (TryGetFieldValue(target, memberName, out value)) { return true; } if (TryGetPropertyValue(target, memberName, out value)) { return true; } value = default(T); return false; } private static bool TryGetFirstMemberValue(object target, out T value, params string[] memberNames) { foreach (string memberName in memberNames) { if (TryGetMemberValue(target, memberName, out value)) { return true; } } value = default(T); return false; } private static T GetMemberValue(object target, string memberName, T fallback) { if (!TryGetMemberValue(target, memberName, out var value)) { return fallback; } return value; } private static object GetMemberObjectValue(object target, string memberName) { Type type = target?.GetType(); if (type == null) { return null; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(target, null); } return null; } private static bool TryGetFieldValue(object target, string fieldName, out T value) { value = default(T); if (target == null) { return false; } FieldInfo field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return false; } return TryConvertValue(field.GetValue(target), out value); } private static bool TryGetPropertyValue(object target, string propertyName, out T value) { value = default(T); if (target == null) { return false; } PropertyInfo property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || property.GetIndexParameters().Length != 0) { return false; } return TryConvertValue(property.GetValue(target, null), out value); } private static bool SetMemberValue(object target, string memberName, T value) { if (target == null) { return false; } FieldInfo field = target.GetType().GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(target, value); return true; } PropertyInfo property = target.GetType().GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); return true; } return false; } private static bool TryConvertValue(object rawValue, out T value) { if (rawValue == null) { value = default(T); return false; } if (rawValue is T val) { value = val; return true; } try { value = (T)Convert.ChangeType(rawValue, typeof(T)); return true; } catch { value = default(T); return false; } } private static bool NearlyEqual(float left, float right) { return Math.Abs(left - right) <= 0.0001f; } internal AbilityVisualTelemetry GetVisualTelemetry() { ResolveReferences(); int value = 0; int num; int num2; if (_gun != null) { num = (TryGetMemberValue(_gun, "ammo", out value) ? 1 : 0); if (num != 0 && _gunAmmo != null && _gunAmmo.maxAmmo > 0 && value >= _gunAmmo.maxAmmo) { num2 = ((_chamberedDamageMultiplier > 1.001f || _chamberedSpeedMultiplier > 1.001f || _chamberedProjectileSizeMultiplier > 1.001f || _chamberedUnblockableCount > 0) ? 1 : 0); goto IL_0082; } } else { num = 0; } num2 = 0; goto IL_0082; IL_0082: bool chamberReady = (byte)num2 != 0; bool cadenceReady = _cadenceEveryShots > 0 && (_successfulAttacks + 1) % _cadenceEveryShots == 0 && (_cadenceDamageMultiplier > 1.001f || _cadenceSpeedMultiplier > 1.001f || _cadenceProjectileSizeMultiplier > 1.001f || _cadenceUnblockableCount > 0); bool lastShellReady = num != 0 && value <= 1 && (_lastShellDamageMultiplier > 1.001f || _lastShellSpeedMultiplier > 1.001f || _lastShellProjectileSizeMultiplier > 1.001f || _lastShellUnblockableCount > 0); bool reloadChargeReady = _reloadChargeShots > 0; bool killChargeReady = _killChargeShots > 0; bool missChargeReady = _missChargeShots > 0; bool reloadModeReady = _reloadModePreparedShots > 0; bool perfectParryReady = _perfectParryShots > 0; bool orbitWardActive = _orbitWardCount > 0; bool hazardZoneArmed = _blockZoneDuration > 0.05f || _killZoneDuration > 0.05f || _trailZoneDuration > 0.05f || _beaconDropDuration > 0.05f; bool hitDisruptionArmed = _hitDisruptionDuration > 0.05f || _hitExposureDuration > 0.05f; int heatThreshold = Math.Max(0, _heatShotsToOverheat); int heatStacks = Math.Max(0, _heatStacks); int cadenceProgress = ((_cadenceEveryShots > 0) ? (_successfulAttacks % _cadenceEveryShots) : 0); return new AbilityVisualTelemetry(chamberReady, lastShellReady, reloadChargeReady, killChargeReady, missChargeReady, reloadModeReady, _emptyMagRushBuffActive, _groundedBuffActive, _wallBuffActive, _lowHealthBuffActive, _deathVowBuffActive, _markChargeShots > 0, _markApplyCount > 0 || _markChargeShots > 0, perfectParryReady, _blockGhostShots > 0, _counterChargeShots > 0, orbitWardActive, _orbitWardCount, hazardZoneArmed, hitDisruptionArmed, _heatOverheatActive, heatThreshold, heatStacks, _cadenceEveryShots, cadenceProgress, cadenceReady); } } internal sealed class PlayerDisruptionStatusController : MonoBehaviour { private struct ActiveDisruption { internal int SourcePlayerId { get; } internal float ExpiresAt { get; } internal float MovementMultiplier { get; } internal float ReloadMultiplier { get; } internal float BlockCooldownMultiplier { get; } internal ActiveDisruption(int sourcePlayerId, float expiresAt, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { SourcePlayerId = sourcePlayerId; ExpiresAt = expiresAt; MovementMultiplier = movementMultiplier; ReloadMultiplier = reloadMultiplier; BlockCooldownMultiplier = blockCooldownMultiplier; } } private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private readonly List _activeDisruptions = new List(); private Player _player; private CharacterStatModifiers _characterStats; private GunAmmo _gunAmmo; private Block _block; private GameObject _indicator; private float _appliedMovementMultiplier = 1f; private float _appliedReloadMultiplier = 1f; private float _appliedBlockCooldownMultiplier = 1f; internal static PlayerDisruptionStatusController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void ApplyDisruption(Player sourcePlayer, float duration, float movementMultiplier, float reloadMultiplier, float blockCooldownMultiplier) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (!(duration <= 0.05f)) { bool num = _activeDisruptions.Count == 0; _activeDisruptions.Add(new ActiveDisruption(sourcePlayer?.playerID ?? (-1), Time.unscaledTime + duration, Mathf.Clamp(movementMultiplier, 0.35f, 1.25f), Mathf.Max(1f, reloadMultiplier), Mathf.Max(1f, blockCooldownMultiplier))); if (num) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Disrupt, 0.08f, 0.96f); } } } internal bool HasActiveDisruption() { return _activeDisruptions.Count > 0; } internal float GetVisualIntensity() { float num = Mathf.Max(0f, 1f - _appliedMovementMultiplier); float num2 = Mathf.Max(0f, _appliedReloadMultiplier - 1f); float num3 = Mathf.Max(0f, _appliedBlockCooldownMultiplier - 1f); return num + num2 + num3; } private void Update() { ResolveReferences(); PruneExpired(); RefreshAppliedMultipliers(); UpdateIndicator(); } private void OnDisable() { RemoveAppliedMultipliers(); _activeDisruptions.Clear(); DestroyIndicator(); } private void ResolveReferences() { if (_player == null) { _player = ((Component)this).GetComponent(); } if (_player != null) { _characterStats = ((Component)_player).GetComponent(); _gunAmmo = ((Component)_player).GetComponentInChildren(); _block = ((Component)_player).GetComponentInChildren(); } } private void PruneExpired() { float unscaledTime = Time.unscaledTime; for (int num = _activeDisruptions.Count - 1; num >= 0; num--) { if (_activeDisruptions[num].ExpiresAt <= unscaledTime) { _activeDisruptions.RemoveAt(num); } } } private void RefreshAppliedMultipliers() { float num = 1f; float num2 = 1f; float num3 = 1f; for (int i = 0; i < _activeDisruptions.Count; i++) { ActiveDisruption activeDisruption = _activeDisruptions[i]; num *= activeDisruption.MovementMultiplier; num2 *= activeDisruption.ReloadMultiplier; num3 *= activeDisruption.BlockCooldownMultiplier; } if (!NearlyEqual(num, _appliedMovementMultiplier) || !NearlyEqual(num2, _appliedReloadMultiplier) || !NearlyEqual(num3, _appliedBlockCooldownMultiplier)) { RemoveAppliedMultipliers(); if (_characterStats != null) { CharacterStatModifiers characterStats = _characterStats; characterStats.movementSpeed *= num; } if (_gunAmmo != null) { GunAmmo gunAmmo = _gunAmmo; gunAmmo.reloadTimeMultiplier *= num2; } if (_block != null) { Block block = _block; block.cdMultiplier *= num3; } _appliedMovementMultiplier = num; _appliedReloadMultiplier = num2; _appliedBlockCooldownMultiplier = num3; } } private void RemoveAppliedMultipliers() { if (_characterStats != null) { _characterStats.movementSpeed = SafeDivide(_characterStats.movementSpeed, _appliedMovementMultiplier); } if (_gunAmmo != null) { _gunAmmo.reloadTimeMultiplier = SafeDivide(_gunAmmo.reloadTimeMultiplier, _appliedReloadMultiplier); } if (_block != null) { _block.cdMultiplier = SafeDivide(_block.cdMultiplier, _appliedBlockCooldownMultiplier); } _appliedMovementMultiplier = 1f; _appliedReloadMultiplier = 1f; _appliedBlockCooldownMultiplier = 1f; } private void UpdateIndicator() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (_player == null || !HasActiveDisruption()) { DestroyIndicator(); return; } EnsureIndicator(); if (_indicator != null) { float num = Clamp01(GetVisualIntensity() * 1.6f); _indicator.transform.position = ((Component)_player).transform.position + new Vector3(0f, 1.22f, 0f); float num2 = 0.11f + num * 0.08f; _indicator.transform.localScale = new Vector3(num2, num2, num2); Color color = LerpColor(new Color(1f, 0.5f, 0.7f, 0.8f), new Color(1f, 0.18f, 0.38f, 0.96f), num); ApplyTint(_indicator, color); } } private void EnsureIndicator() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (_indicator == null) { GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); _indicator = (GameObject)((val != null) ? ((object)Object.Instantiate(val, ((Component)this).transform.position, default(Quaternion), (Transform)null)) : ((object)new GameObject())); if (_indicator != null) { ((Object)_indicator).name = "BetterRoundsDisruptionIndicator"; } } } private void DestroyIndicator() { if (_indicator != null) { Object.Destroy((Object)(object)_indicator); _indicator = null; } } private static void ApplyTint(GameObject target, Color color) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target != null) { MethodInfo method = ((object)target).GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.SpriteRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.SpriteRenderer, UnityEngine") }), color); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.MeshRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.MeshRenderer, UnityEngine") }), color); for (int i = 0; i < target.transform.childCount; i++) { ApplyTint(((Component)target.transform.GetChild(i)).gameObject, color); } } } private static void ApplyTintToComponent(object component, Color color) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (component == null) { return; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } PropertyInfo property2 = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((property2 == null) ? null : property2.GetValue(component, null)); if (obj != null) { PropertyInfo property3 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.CanWrite && property3.PropertyType == typeof(Color)) { property3.SetValue(obj, color, null); } } } private static float SafeDivide(float value, float divisor) { if (!NearlyEqual(divisor, 0f)) { return value / divisor; } return value; } private static bool NearlyEqual(float left, float right) { return Math.Abs(left - right) <= 0.0001f; } private static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } private static Color LerpColor(Color a, Color b, float t) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) float num = Clamp01(t); return new Color(a.r + (b.r - a.r) * num, a.g + (b.g - a.g) * num, a.b + (b.b - a.b) * num, a.a + (b.a - a.a) * num); } } internal sealed class PlayerExposureStatusController : MonoBehaviour { private struct ActiveExposure { internal int SourcePlayerId { get; } internal float ExpiresAt { get; } internal float DamageMultiplier { get; } internal ActiveExposure(int sourcePlayerId, float expiresAt, float damageMultiplier) { SourcePlayerId = sourcePlayerId; ExpiresAt = expiresAt; DamageMultiplier = damageMultiplier; } } private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private readonly List _activeExposures = new List(); private readonly List _indicators = new List(); private Player _player; internal static PlayerExposureStatusController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void ApplyExposure(Player sourcePlayer, float duration, float damageMultiplier) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (sourcePlayer != null && !(duration <= 0.05f) && !(damageMultiplier <= 1.001f)) { bool num = _activeExposures.Count == 0; _activeExposures.Add(new ActiveExposure(sourcePlayer.playerID, Time.unscaledTime + duration, Mathf.Clamp(damageMultiplier, 1f, 1.6f))); if (num) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Expose, 0.08f, 1.04f); } } } internal float GetDamageTakenMultiplier(Player sourcePlayer) { PruneExpired(); if (_activeExposures.Count == 0) { return 1f; } int num = sourcePlayer?.playerID ?? (-1); float num2 = 1f; for (int i = 0; i < _activeExposures.Count; i++) { ActiveExposure activeExposure = _activeExposures[i]; if (num < 0 || activeExposure.SourcePlayerId == num) { num2 *= activeExposure.DamageMultiplier; } } return Mathf.Clamp(num2, 1f, 2.25f); } private void Update() { if (_player == null) { _player = ((Component)this).GetComponent(); } PruneExpired(); SyncIndicators(GetIndicatorCount()); } private void OnDisable() { _activeExposures.Clear(); SyncIndicators(0); } private void PruneExpired() { float unscaledTime = Time.unscaledTime; for (int num = _activeExposures.Count - 1; num >= 0; num--) { if (_activeExposures[num].ExpiresAt <= unscaledTime) { _activeExposures.RemoveAt(num); } } } private int GetIndicatorCount() { float damageTakenMultiplier = GetDamageTakenMultiplier(null); if (damageTakenMultiplier <= 1.001f) { return 0; } if (damageTakenMultiplier >= 1.2f) { return 3; } if (damageTakenMultiplier >= 1.1f) { return 2; } return 1; } private void SyncIndicators(int desiredCount) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) while (_indicators.Count > desiredCount) { int index = _indicators.Count - 1; if (_indicators[index] != null) { Object.Destroy((Object)(object)_indicators[index]); } _indicators.RemoveAt(index); } while (_indicators.Count < desiredCount) { GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); GameObject val2 = (GameObject)((val != null) ? ((object)Object.Instantiate(val, ((Component)this).transform.position, default(Quaternion), (Transform)null)) : ((object)new GameObject())); ((Object)val2).name = "BetterRoundsExposureIndicator"; _indicators.Add(val2); } for (int i = 0; i < _indicators.Count; i++) { GameObject val3 = _indicators[i]; if (val3 != null && _player != null) { float num = i switch { 1 => 0f, 0 => -0.16f, _ => 0.16f, }; float num2 = ((i == 1) ? 1.56f : 1.46f); val3.transform.position = ((Component)_player).transform.position + new Vector3(num, num2, 0f); float num3 = 0.09f + 0.02f * (float)i; val3.transform.localScale = new Vector3(num3, num3, num3); ApplyTint(val3, (Color)(i switch { 1 => new Color(1f, 0.46f, 0.2f, 0.96f), 0 => new Color(1f, 0.86f, 0.28f, 0.92f), _ => new Color(0.98f, 0.16f, 0.24f, 0.98f), })); } } } private static void ApplyTint(GameObject target, Color color) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target != null) { MethodInfo method = ((object)target).GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.SpriteRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.SpriteRenderer, UnityEngine") }), color); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.MeshRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.MeshRenderer, UnityEngine") }), color); for (int i = 0; i < target.transform.childCount; i++) { ApplyTint(((Component)target.transform.GetChild(i)).gameObject, color); } } } private static void ApplyTintToComponent(object component, Color color) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (component == null) { return; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } PropertyInfo property2 = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((property2 == null) ? null : property2.GetValue(component, null)); if (obj != null) { PropertyInfo property3 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.CanWrite && property3.PropertyType == typeof(Color)) { property3.SetValue(obj, color, null); } } } } internal sealed class PlayerMarkStatusController : MonoBehaviour { private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private readonly Dictionary _marksBySourcePlayerId = new Dictionary(); private Player _player; private readonly List _indicators = new List(); internal static PlayerMarkStatusController GetOrAdd(Player player) { if (player != null) { return ExtensionMethods.GetOrAddComponent(((Component)player).gameObject, false); } return null; } internal void AddMarks(Player sourcePlayer, int amount, int maximumStacks) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (_player == null) { _player = ((Component)this).GetComponent(); } if (sourcePlayer != null && amount > 0 && maximumStacks > 0) { int playerID = sourcePlayer.playerID; int value; int num = (_marksBySourcePlayerId.TryGetValue(playerID, out value) ? value : 0); _marksBySourcePlayerId[playerID] = Mathf.Clamp(num + amount, 0, maximumStacks); if (num <= 0) { FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Disrupt, 0.06f, 1.08f); } } } internal bool TryConsumeMark(Player sourcePlayer, int amount) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (sourcePlayer == null || amount <= 0) { return false; } int playerID = sourcePlayer.playerID; if (!_marksBySourcePlayerId.TryGetValue(playerID, out var value) || value < amount) { return false; } int num = value - amount; if (num <= 0) { _marksBySourcePlayerId.Remove(playerID); } else { _marksBySourcePlayerId[playerID] = num; } FeedbackCueUtility.PlayCue(((Component)this).transform.position, FeedbackCueKind.Consume, 0.09f, 1.04f); return true; } internal int GetTotalMarks() { int num = 0; foreach (KeyValuePair item in _marksBySourcePlayerId) { num += item.Value; } return num; } private void Update() { if (_player == null) { _player = ((Component)this).GetComponent(); } int desiredCount = Mathf.Clamp(GetTotalMarks(), 0, 3); SyncIndicators(desiredCount); } private void OnDisable() { SyncIndicators(0); } private void SyncIndicators(int desiredCount) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) while (_indicators.Count > desiredCount) { int index = _indicators.Count - 1; if (_indicators[index] != null) { Object.Destroy((Object)(object)_indicators[index]); } _indicators.RemoveAt(index); } while (_indicators.Count < desiredCount) { GameObject val = ((CardChoiceVisuals.instance != null) ? CardChoiceVisuals.instance.shieldGem : null); GameObject val2 = (GameObject)((val != null) ? ((object)Object.Instantiate(val, ((Component)this).transform.position, default(Quaternion), (Transform)null)) : ((object)new GameObject())); ((Object)val2).name = "BetterRoundsMarkIndicator"; _indicators.Add(val2); } for (int i = 0; i < _indicators.Count; i++) { GameObject val3 = _indicators[i]; if (val3 != null && _player != null) { float num = i switch { 1 => 0f, 0 => -0.18f, _ => 0.18f, }; float num2 = ((i == 1) ? 1.42f : 1.34f); val3.transform.position = ((Component)_player).transform.position + new Vector3(num, num2, 0f); float num3 = 0.09f + 0.015f * (float)i; val3.transform.localScale = new Vector3(num3, num3, num3); ApplyTint(val3, (Color)(i switch { 1 => new Color(1f, 0.84f, 0.34f, 0.92f), 0 => new Color(1f, 0.3f, 0.7f, 0.88f), _ => new Color(1f, 0.46f, 0.22f, 0.95f), })); } } } private static void ApplyTint(GameObject target, Color color) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if (target != null) { MethodInfo method = ((object)target).GetType().GetMethod("GetComponent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Type) }, null); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.SpriteRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.SpriteRenderer, UnityEngine") }), color); ApplyTintToComponent(method?.Invoke(target, new object[1] { Type.GetType("UnityEngine.MeshRenderer, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.MeshRenderer, UnityEngine") }), color); for (int i = 0; i < target.transform.childCount; i++) { ApplyTint(((Component)target.transform.GetChild(i)).gameObject, color); } } } private static void ApplyTintToComponent(object component, Color color) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (component == null) { return; } PropertyInfo property = component.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(Color)) { property.SetValue(component, color, null); } PropertyInfo property2 = component.GetType().GetProperty("material", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((property2 == null) ? null : property2.GetValue(component, null)); if (obj != null) { PropertyInfo property3 = obj.GetType().GetProperty("color", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.CanWrite && property3.PropertyType == typeof(Color)) { property3.SetValue(obj, color, null); } } } } }