Decompiled source of ADAPPrototype v0.0.1

plugins/ADAPPrototype/ADAPPrototype.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using EntityStates;
using EntityStates.Commando.CommandoWeapon;
using EntityStates.Commando.StickyGrenade;
using EntityStates.GlobalSkills.LunarNeedle;
using IL.EntityStates.Commando.CommandoWeapon;
using IL.EntityStates.GlobalSkills.LunarDetonator;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using MonoMod.RuntimeDetour.HookGen;
using MonoMod.Utils;
using On.EntityStates;
using On.EntityStates.Commando.CommandoWeapon;
using On.EntityStates.GlobalSkills.LunarNeedle;
using On.RoR2;
using On.RoR2.UI;
using RoR2;
using RoR2.Networking;
using RoR2.Projectile;
using RoR2.Skills;
using RoR2.UI;
using TMPro;
using ThreeChoiceChests;
using UnityEngine;
using UnityEngine.Networking;
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("ADAPPrototype")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.1.0")]
[assembly: AssemblyInformationalVersion("0.0.1")]
[assembly: AssemblyProduct("ADAPPrototype")]
[assembly: AssemblyTitle("ADAPPrototype")]
[assembly: AssemblyVersion("0.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ADAPPrototype
{
	[BepInPlugin("com.local.ror2adapprototype", "RoR2 AD/AP Commando Slice", "0.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static hook_RecalculateStats <0>__Recalculate;

			public static hook_ModifyProjectileInfo <1>__PhaseProjectile;

			public static hook_GetLocalizedStringByToken <2>__Localize;

			public static Action<Run> <3>__RunStarted;

			public static Action<Run> <4>__RunEnded;

			public static Action <5>__NetworkStopped;

			public static Action <6>__GrowthChanged;
		}

		public const string Guid = "com.local.ror2adapprototype";

		internal static ManualLogSource Log;

		internal static bool Active;

		private bool hooksStarted;

		private void Awake()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//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_0072: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				if (BridgeBootstrap.Install())
				{
					hooksStarted = true;
					Patches.Install();
					AlternateSkills.Install();
					SabotageSkill.Install();
					CombatHooks.Install();
					LunarItemDamage.Install();
					object obj = <>O.<0>__Recalculate;
					if (obj == null)
					{
						hook_RecalculateStats val = Recalculate;
						<>O.<0>__Recalculate = val;
						obj = (object)val;
					}
					CharacterBody.RecalculateStats += (hook_RecalculateStats)obj;
					object obj2 = <>O.<1>__PhaseProjectile;
					if (obj2 == null)
					{
						hook_ModifyProjectileInfo val2 = PhaseProjectile;
						<>O.<1>__PhaseProjectile = val2;
						obj2 = (object)val2;
					}
					FireFMJ.ModifyProjectileInfo += (hook_ModifyProjectileInfo)obj2;
					object obj3 = <>O.<2>__Localize;
					if (obj3 == null)
					{
						hook_GetLocalizedStringByToken val3 = Descriptions.Localize;
						<>O.<2>__Localize = val3;
						obj3 = (object)val3;
					}
					Language.GetLocalizedStringByToken += (hook_GetLocalizedStringByToken)obj3;
					BuildInfoUI.Install();
					RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(CatalogReady));
					Run.onRunStartGlobal += RunStarted;
					Run.onRunDestroyGlobal += RunEnded;
					NetworkManagerSystem.onStopClientGlobal += NetworkStopped;
					NetworkManagerSystem.onStopServerGlobal += NetworkStopped;
					ChoiceGrowth.Changed += GrowthChanged;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[ADAP Init] TCC API v1 bridge subscribed; ADAP owns frozen rolls, ledger and protocol v2. Legacy item hooks are not installed.");
				}
			}
			catch (Exception ex)
			{
				Active = false;
				((BaseUnityPlugin)this).Logger.LogError((object)("[ADAP FATAL] Slice registration failed: " + ex));
				RemoveHooks();
			}
		}

		private void CatalogReady()
		{
			try
			{
				Descriptions.ResolveSkills();
				AlternateSkills.Resolve();
				SabotageSkill.Resolve();
				if (FireBarrage.baseBulletCount <= 0)
				{
					throw new InvalidOperationException("Native Barrage baseBulletCount must be positive.");
				}
				Active = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"[ADAP Ready] Growth={0.01f:P0}; native Barrage base count={FireBarrage.baseBulletCount}; native item effects remain enabled.");
			}
			catch (Exception ex)
			{
				Active = false;
				((BaseUnityPlugin)this).Logger.LogError((object)("[ADAP FATAL] Catalog/API validation failed: " + ex));
			}
		}

		private static void RunStarted(Run run)
		{
			ChoiceGrowth.Reset(run);
			Stats.ResetDiagnostics();
			IndependentDamage.Reset();
		}

		private static void RunEnded(Run run)
		{
			ChoiceGrowth.Reset(null);
		}

		private static void NetworkStopped()
		{
			ChoiceGrowth.Reset(null);
		}

		private void Update()
		{
			if (hooksStarted)
			{
				ChoiceGrowth.Tick();
			}
		}

		private static void GrowthChanged()
		{
			foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList)
			{
				if (Stats.Applies(readOnlyInstances))
				{
					Stats.Report(readOnlyInstances);
				}
			}
		}

		private static void Recalculate(orig_RecalculateStats orig, CharacterBody self)
		{
			if (!Stats.Applies(self))
			{
				orig.Invoke(self);
				return;
			}
			float levelDamage = self.levelDamage;
			try
			{
				self.levelDamage = self.baseDamage * 0.01f;
				orig.Invoke(self);
			}
			finally
			{
				self.levelDamage = levelDamage;
			}
			Stats.Report(self);
		}

		private static void PhaseProjectile(orig_ModifyProjectileInfo orig, FireFMJ self, ref FireProjectileInfo info)
		{
			orig.Invoke(self, ref info);
			CharacterBody body = StateAccess.Body((EntityState)(object)self);
			if (Stats.Applies(body))
			{
				info.damage = Stats.SkillDamage(body, StateAccess.Damage((BaseState)(object)self), SliceTuning.PhaseRound, "PhaseRound");
			}
		}

		private void RemoveHooks()
		{
			//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_00f6: Expected O, but got Unknown
			//IL_010b: 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)
			//IL_0116: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			Active = false;
			if (hooksStarted)
			{
				hooksStarted = false;
				BridgeBootstrap.Uninstall();
				RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(CatalogReady));
				Run.onRunStartGlobal -= RunStarted;
				Run.onRunDestroyGlobal -= RunEnded;
				NetworkManagerSystem.onStopClientGlobal -= NetworkStopped;
				NetworkManagerSystem.onStopServerGlobal -= NetworkStopped;
				ChoiceGrowth.Changed -= GrowthChanged;
				object obj = <>O.<0>__Recalculate;
				if (obj == null)
				{
					hook_RecalculateStats val = Recalculate;
					<>O.<0>__Recalculate = val;
					obj = (object)val;
				}
				CharacterBody.RecalculateStats -= (hook_RecalculateStats)obj;
				object obj2 = <>O.<1>__PhaseProjectile;
				if (obj2 == null)
				{
					hook_ModifyProjectileInfo val2 = PhaseProjectile;
					<>O.<1>__PhaseProjectile = val2;
					obj2 = (object)val2;
				}
				FireFMJ.ModifyProjectileInfo -= (hook_ModifyProjectileInfo)obj2;
				object obj3 = <>O.<2>__Localize;
				if (obj3 == null)
				{
					hook_GetLocalizedStringByToken val3 = Descriptions.Localize;
					<>O.<2>__Localize = val3;
					obj3 = (object)val3;
				}
				Language.GetLocalizedStringByToken -= (hook_GetLocalizedStringByToken)obj3;
				BuildInfoUI.Uninstall();
				LunarItemDamage.Remove();
				CombatHooks.Remove();
				SabotageSkill.Remove();
				AlternateSkills.Remove();
				Patches.Remove();
				ChoiceGrowth.Reset(null);
			}
		}

		private void OnDestroy()
		{
			RemoveHooks();
		}
	}
	public static class Patches
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Manipulator <0>__Primary;

			public static Manipulator <1>__BarrageDamage;
		}

		internal static void Install()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			object obj = <>O.<0>__Primary;
			if (obj == null)
			{
				Manipulator val = Primary;
				<>O.<0>__Primary = val;
				obj = (object)val;
			}
			FirePistol2.FireBullet += (Manipulator)obj;
			object obj2 = <>O.<1>__BarrageDamage;
			if (obj2 == null)
			{
				Manipulator val2 = BarrageDamage;
				<>O.<1>__BarrageDamage = val2;
				obj2 = (object)val2;
			}
			FireBarrage.FireBullet += (Manipulator)obj2;
		}

		internal static void Remove()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			object obj = <>O.<0>__Primary;
			if (obj == null)
			{
				Manipulator val = Primary;
				<>O.<0>__Primary = val;
				obj = (object)val;
			}
			FirePistol2.FireBullet -= (Manipulator)obj;
			object obj2 = <>O.<1>__BarrageDamage;
			if (obj2 == null)
			{
				Manipulator val2 = BarrageDamage;
				<>O.<1>__BarrageDamage = val2;
				obj2 = (object)val2;
			}
			FireBarrage.FireBullet -= (Manipulator)obj2;
		}

		private static Instruction SingleField(ILContext il, Code code, string type, string name)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			Instruction[] array = ((IEnumerable<Instruction>)il.Body.Instructions).Where(delegate(Instruction i)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: 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)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				OpCode opCode = i.OpCode;
				if (((OpCode)(ref opCode)).Code == code)
				{
					object operand = i.Operand;
					FieldReference val = (FieldReference)((operand is FieldReference) ? operand : null);
					if (val != null && ((MemberReference)((MemberReference)val).DeclaringType).FullName == type)
					{
						return ((MemberReference)val).Name == name;
					}
				}
				return false;
			}).ToArray();
			if (array.Length != 1)
			{
				throw new InvalidOperationException($"{((MemberReference)il.Method).FullName}: {name} expected one anchor, got {array.Length}");
			}
			return array[0];
		}

		private static void ReplaceDamage(ILContext il, string type, Func<SkillCoefficients> coefficients, string source)
		{
			//IL_0037: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			Instruction val = SingleField(il, (Code)123, type, "damageCoefficient");
			Instruction val2 = SingleField(il, (Code)120, "EntityStates.BaseState", "damageStat");
			ILCursor val3 = new ILCursor(il);
			val3.Goto(val, (MoveType)2, false);
			val3.Emit(OpCodes.Ldarg_0);
			val3.EmitDelegate<Func<float, BaseState, float>>((Func<float, BaseState, float>)((float native, BaseState state) => (!Stats.Applies(StateAccess.Body((EntityState)(object)state))) ? native : 1f));
			val3.Goto(val2, (MoveType)2, false);
			val3.Emit(OpCodes.Ldarg_0);
			val3.EmitDelegate<Func<float, BaseState, float>>((Func<float, BaseState, float>)((float native, BaseState state) => (!Stats.Applies(StateAccess.Body((EntityState)(object)state))) ? native : Stats.SkillDamage(StateAccess.Body((EntityState)(object)state), native, coefficients(), source)));
		}

		public static void Primary(ILContext il)
		{
			ReplaceDamage(il, typeof(FirePistol2).FullName, () => SliceTuning.Primary, "DoubleTap");
		}

		public static void BarrageDamage(ILContext il)
		{
			ReplaceDamage(il, typeof(FireBarrage).FullName, () => Stats.BarragePerBullet, "BarrageBullet");
		}
	}
	internal static class Stats
	{
		private static readonly Dictionary<uint, string> lastReports = new Dictionary<uint, string>();

		private static int damageSamples;

		public static SkillCoefficients BarragePerBullet => SliceTuning.BarrageTotal.Divide(FireBarrage.baseBulletCount);

		public static bool IsCommando(CharacterBody body)
		{
			if (Object.op_Implicit((Object)(object)body))
			{
				return body.baseNameToken == "COMMANDO_BODY_NAME";
			}
			return false;
		}

		public static bool Applies(CharacterBody body)
		{
			if (Plugin.Active && IsCommando(body))
			{
				return body.isPlayerControlled;
			}
			return false;
		}

		public static float Level1BaseDamage(CharacterBody body)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)body))
			{
				return 0f;
			}
			CharacterBody bodyPrefabBodyComponent = BodyCatalog.GetBodyPrefabBodyComponent(body.bodyIndex);
			if (!Object.op_Implicit((Object)(object)bodyPrefabBodyComponent))
			{
				return body.baseDamage;
			}
			return bodyPrefabBodyComponent.baseDamage;
		}

		public static DamageValues Values(CharacterBody body)
		{
			if (!Object.op_Implicit((Object)(object)body))
			{
				return default(DamageValues);
			}
			AttributePool pool = ChoiceGrowth.GetPool(body.master);
			return DamageModel.Calculate(Level1BaseDamage(body), body.level, 0.01f, pool.ADPercent, pool.APPercent);
		}

		public static float ExistingDamageScale(CharacterBody body, float stateDamage)
		{
			if (!Object.op_Implicit((Object)(object)body) || !Applies(body))
			{
				return 1f;
			}
			return stateDamage / Mathf.Max(0.0001f, body.baseDamage * (1f + 0.01f * Mathf.Max(0f, body.level - 1f)));
		}

		public static float SkillDamage(CharacterBody body, float stateDamage, SkillCoefficients coefficients, string source)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			DamageValues damageValues = Values(body);
			float num = coefficients.Damage(damageValues.BaseDamage, damageValues.AD, damageValues.AP) * ExistingDamageScale(body, stateDamage);
			if (damageSamples++ < 80)
			{
				ManualLogSource log = Plugin.Log;
				object[] array = new object[7];
				CharacterMaster master = body.master;
				array[0] = ((master != null) ? new NetworkInstanceId?(((NetworkBehaviour)master).netId) : ((NetworkInstanceId?)null));
				array[1] = source;
				array[2] = body.level;
				array[3] = damageValues.BaseDamage;
				array[4] = damageValues.AD;
				array[5] = damageValues.AP;
				array[6] = num;
				log.LogInfo((object)string.Format("[ADAP Skill] owner={0} skill={1} level={2:F0} Base={3:F3} AD={4:F3} AP={5:F3} damage={6:F3}", array));
			}
			return num;
		}

		public static int BarrageShots(CharacterBody body)
		{
			return Mathf.Max(1, (int)((float)FireBarrage.baseBulletCount * body.attackSpeed));
		}

		public static void ResetDiagnostics()
		{
			damageSamples = 0;
			lastReports.Clear();
		}

		public static void Report(CharacterBody body)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			if (Applies(body))
			{
				AttributePool pool = ChoiceGrowth.GetPool(body.master);
				string text = $"{body.level:R}:{pool.ADPoints}:{pool.APPoints}";
				NetworkInstanceId netId;
				uint value;
				if (!Object.op_Implicit((Object)(object)body.master))
				{
					netId = ((NetworkBehaviour)body).netId;
					value = ((NetworkInstanceId)(ref netId)).Value;
				}
				else
				{
					netId = ((NetworkBehaviour)body.master).netId;
					value = ((NetworkInstanceId)(ref netId)).Value;
				}
				uint num = value;
				if (!lastReports.TryGetValue(num, out var value2) || !(value2 == text))
				{
					lastReports[num] = text;
					DamageValues damageValues = Values(body);
					Plugin.Log.LogInfo((object)$"[ADAP Stats] owner={num} level={body.level:F0} totalAD={pool.ADPoints}% totalAP={pool.APPoints}% Base={damageValues.BaseDamage:F3} AD={damageValues.AD:F3} AP={damageValues.AP:F3}");
				}
			}
		}
	}
	internal static class StateAccess
	{
		private static readonly Func<BaseState, float> damage = CreateDamageGetter();

		public static CharacterBody Body(EntityState state)
		{
			if (!Object.op_Implicit((Object)(object)state.outer))
			{
				return null;
			}
			return state.outer.commonComponents.characterBody;
		}

		public static float Damage(BaseState state)
		{
			return damage(state);
		}

		private static Func<BaseState, float> CreateDamageGetter()
		{
			FieldInfo field = typeof(BaseState).GetField("damageStat", BindingFlags.Instance | BindingFlags.NonPublic);
			if (field == null || field.FieldType != typeof(float))
			{
				throw new MissingFieldException("BaseState.damageStat changed.");
			}
			DynamicMethod dynamicMethod = new DynamicMethod("ADAP_ReadNativeDamageSnapshot", typeof(float), new Type[1] { typeof(BaseState) }, typeof(StateAccess), skipVisibility: true);
			ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
			iLGenerator.Emit(OpCodes.Ldarg_0);
			iLGenerator.Emit(OpCodes.Ldfld, field);
			iLGenerator.Emit(OpCodes.Ret);
			return (Func<BaseState, float>)dynamicMethod.CreateDelegate(typeof(Func<BaseState, float>));
		}
	}
	public readonly struct DamageValues
	{
		public float BaseDamage { get; }

		public float AD { get; }

		public float AP { get; }

		public float BonusAD => AD - BaseDamage;

		public float BonusAP => AP - BaseDamage;

		internal DamageValues(float baseDamage, float ad, float ap)
		{
			BaseDamage = baseDamage;
			AD = ad;
			AP = ap;
		}
	}
	public static class DamageModel
	{
		public static DamageValues Calculate(float level1BaseDamage, float level, float growth, float totalADPercent, float totalAPPercent)
		{
			float num = level1BaseDamage * (1f + growth * Math.Max(0f, level - 1f));
			return new DamageValues(num, num * (1f + totalADPercent), num * (1f + totalAPPercent));
		}

		public static float SkillDamage(DamageValues stats, float adCoefficient, float apCoefficient)
		{
			return adCoefficient * stats.AD + apCoefficient * stats.AP;
		}
	}
	public readonly struct IndependentScaling
	{
		public float ADWeight { get; }

		public float APWeight { get; }

		public static IndependentScaling PureAD => new IndependentScaling(1f, 0f);

		public static IndependentScaling PureAP => new IndependentScaling(0f, 1f);

		public static IndependentScaling Dual => new IndependentScaling(1f, 1f);

		private IndependentScaling(float adWeight, float apWeight)
		{
			ADWeight = adWeight;
			APWeight = apWeight;
		}

		public static IndependentScaling WeightedDual(float adWeight, float apWeight)
		{
			return new IndependentScaling(adWeight, apWeight);
		}

		public float EffectiveStat(DamageValues stats)
		{
			return stats.BaseDamage + ADWeight * stats.BonusAD + APWeight * stats.BonusAP;
		}

		public float Damage(DamageValues stats, float coefficient)
		{
			return coefficient * EffectiveStat(stats);
		}
	}
	internal static class AttributeText
	{
		public const string ADColor = "#F2A65A";

		public const string APColor = "#66B5F5";

		public const string BaseColor = "#F6D365";

		public static string Number(float value)
		{
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}

		public static string Percent(float value)
		{
			return Number(value * 100f) + "%";
		}

		public static string Color(string text, string color)
		{
			return "<color=" + color + ">" + text + "</color>";
		}

		public static string AD(float value)
		{
			return Color(Percent(value) + " AD", "#F2A65A");
		}

		public static string AP(float value)
		{
			return Color(Percent(value) + " AP", "#66B5F5");
		}

		public static string Base(float value)
		{
			return Color(Percent(value) + " Base", "#F6D365");
		}

		public static string StatLine(float ad, float ap)
		{
			string text = ((ad != 0f) ? Color("AD+" + Percent(ad), "#F2A65A") : "");
			if (ap != 0f)
			{
				text = text + ((text.Length > 0) ? " " : "") + Color("AP+" + Percent(ap), "#66B5F5");
			}
			return text;
		}
	}
	internal static class Descriptions
	{
		private static readonly Dictionary<string, string> skills = new Dictionary<string, string>();

		public static bool IsChinese => string.Equals(Language.currentLanguageName, "zh-CN", StringComparison.OrdinalIgnoreCase);

		public static string CurrentAttributes(float ad, float ap, bool prefix)
		{
			return ((!prefix) ? "" : (IsChinese ? "当前:" : "Current: ")) + AttributeText.Color("AD " + AttributeText.Number(ad), "#F2A65A") + "\u3000" + AttributeText.Color("AP " + AttributeText.Number(ap), "#66B5F5");
		}

		public static void ResolveSkills()
		{
			skills.Clear();
			foreach (SkillDef allSkillDef in SkillCatalog.allSkillDefs)
			{
				Type stateType = ((SerializableEntityStateType)(ref allSkillDef.activationState)).stateType;
				if (stateType == typeof(FirePistol2))
				{
					skills[allSkillDef.skillDescriptionToken] = "Primary";
				}
				else if (stateType == typeof(FireFMJ))
				{
					skills[allSkillDef.skillDescriptionToken] = "Phase";
				}
				else if (stateType == typeof(FireBarrage))
				{
					skills[allSkillDef.skillDescriptionToken] = "Barrage";
				}
			}
			if (skills.Count != 3)
			{
				throw new InvalidOperationException("Expected three Commando skill description tokens.");
			}
		}

		public static string Localize(orig_GetLocalizedStringByToken orig, Language self, string token)
		{
			string text = orig.Invoke(self, token);
			if (!Plugin.Active)
			{
				return text;
			}
			bool zh = string.Equals(self?.name, "zh-CN", StringComparison.OrdinalIgnoreCase);
			if (skills.TryGetValue(token, out var value))
			{
				return SkillFeedbackText.Description(value, zh, FireBarrage.baseBulletCount) ?? text;
			}
			return AlternateSkills.Localize(token, zh) ?? SabotageSkill.Localize(token, zh) ?? text;
		}
	}
	public static class BuildInfo
	{
		public static DamageValues GetCoreStats(CharacterBody body)
		{
			return Stats.Values(body);
		}

		public static float GetBaseDamage(CharacterBody body)
		{
			return Stats.Values(body).BaseDamage;
		}

		public static float GetCurrentAD(CharacterBody body)
		{
			if (!Object.op_Implicit((Object)(object)body))
			{
				return 0f;
			}
			return Stats.Values(body).AD * Stats.ExistingDamageScale(body, body.damage);
		}

		public static float GetCurrentAP(CharacterBody body)
		{
			if (!Object.op_Implicit((Object)(object)body))
			{
				return 0f;
			}
			return Stats.Values(body).AP * Stats.ExistingDamageScale(body, body.damage);
		}

		public static bool TryGetCurrent(CharacterBody body, out float ad, out float ap)
		{
			ad = (ap = 0f);
			if (!Stats.Applies(body))
			{
				return false;
			}
			ad = GetCurrentAD(body);
			ap = GetCurrentAP(body);
			return true;
		}

		internal static string SkillTooltip(GenericSkill skill)
		{
			CharacterBody val = (Object.op_Implicit((Object)(object)skill) ? skill.characterBody : null);
			if (!Stats.Applies(val) || !Object.op_Implicit((Object)(object)skill.skillDef))
			{
				return null;
			}
			Type stateType = ((SerializableEntityStateType)(ref skill.skillDef.activationState)).stateType;
			SkillCoefficients skillCoefficients;
			if (stateType == typeof(FirePistol2))
			{
				skillCoefficients = SliceTuning.Primary;
			}
			else if (stateType == typeof(FireFMJ))
			{
				skillCoefficients = SliceTuning.PhaseRound;
			}
			else
			{
				if (!(stateType == typeof(FireBarrage)))
				{
					return AlternateSkills.Tooltip(skill) ?? SabotageSkill.Tooltip(skill);
				}
				skillCoefficients = Stats.BarragePerBullet;
			}
			DamageValues damageValues = Stats.Values(val);
			float damage = skillCoefficients.Damage(damageValues.BaseDamage, damageValues.AD, damageValues.AP) * Stats.ExistingDamageScale(val, val.damage);
			string text = Language.GetString(skill.skillDef.skillDescriptionToken);
			bool isChinese = Descriptions.IsChinese;
			string kind = ((stateType == typeof(FireBarrage)) ? "Barrage" : ((stateType == typeof(FireFMJ)) ? "Phase" : "Primary"));
			return SkillFeedbackText.AppendDamage(text, kind, isChinese, damage, Stats.BarrageShots(val), val.attackSpeed);
		}
	}
	internal static class BuildInfoUI
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static hook_OnEnable <0>__ScoreboardEnable;

			public static hook_LateUpdate <1>__SkillUpdate;

			public static hook_SetTooltipProvider <2>__TooltipContent;
		}

		private static bool installed;

		internal static void Install()
		{
			//IL_001e: 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: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			if (installed)
			{
				return;
			}
			installed = true;
			try
			{
				object obj = <>O.<0>__ScoreboardEnable;
				if (obj == null)
				{
					hook_OnEnable val = ScoreboardEnable;
					<>O.<0>__ScoreboardEnable = val;
					obj = (object)val;
				}
				ScoreboardController.OnEnable += (hook_OnEnable)obj;
				object obj2 = <>O.<1>__SkillUpdate;
				if (obj2 == null)
				{
					hook_LateUpdate val2 = SkillUpdate;
					<>O.<1>__SkillUpdate = val2;
					obj2 = (object)val2;
				}
				SkillIcon.LateUpdate += (hook_LateUpdate)obj2;
				object obj3 = <>O.<2>__TooltipContent;
				if (obj3 == null)
				{
					hook_SetTooltipProvider val3 = TooltipContent;
					<>O.<2>__TooltipContent = val3;
					obj3 = (object)val3;
				}
				TooltipController.SetTooltipProvider += (hook_SetTooltipProvider)obj3;
				ChoiceAttributeUI.Install();
				Plugin.Log.LogInfo((object)"[ADAP UI] SkillIcon.LateUpdate / scoreboard / tooltip hooks installed.");
			}
			catch
			{
				Uninstall();
				throw;
			}
		}

		private static void ScoreboardEnable(orig_OnEnable orig, ScoreboardController self)
		{
			orig.Invoke(self);
			ScoreboardBuildInfo scoreboardBuildInfo = ((Component)self).GetComponent<ScoreboardBuildInfo>();
			if (!Object.op_Implicit((Object)(object)scoreboardBuildInfo))
			{
				scoreboardBuildInfo = ((Component)self).gameObject.AddComponent<ScoreboardBuildInfo>();
			}
			scoreboardBuildInfo.Initialize(self);
		}

		private static void SkillUpdate(orig_LateUpdate orig, SkillIcon self)
		{
			orig.Invoke(self);
			if (!Object.op_Implicit((Object)(object)self.tooltipProvider))
			{
				return;
			}
			string text = BuildInfo.SkillTooltip(self.targetSkill);
			SkillDamageTooltip skillDamageTooltip = ((Component)self).GetComponent<SkillDamageTooltip>();
			if (text == null)
			{
				if (Object.op_Implicit((Object)(object)skillDamageTooltip))
				{
					skillDamageTooltip.Restore();
				}
				return;
			}
			if (!Object.op_Implicit((Object)(object)skillDamageTooltip))
			{
				skillDamageTooltip = ((Component)self).gameObject.AddComponent<SkillDamageTooltip>();
			}
			skillDamageTooltip.Set(self.tooltipProvider, text);
		}

		private static void TooltipContent(orig_SetTooltipProvider orig, TooltipController self, TooltipProvider provider)
		{
			SkillIcon val = (Object.op_Implicit((Object)(object)provider) ? ((Component)provider).GetComponent<SkillIcon>() : null);
			if (Object.op_Implicit((Object)(object)val))
			{
				string text = BuildInfo.SkillTooltip(val.targetSkill);
				if (text != null)
				{
					provider.overrideBodyText = text;
				}
			}
			orig.Invoke(self, provider);
			LiveSkillDamageTooltip liveSkillDamageTooltip = ((Component)self).GetComponent<LiveSkillDamageTooltip>();
			if (Object.op_Implicit((Object)(object)val) && BuildInfo.SkillTooltip(val.targetSkill) != null)
			{
				if (!Object.op_Implicit((Object)(object)liveSkillDamageTooltip))
				{
					liveSkillDamageTooltip = ((Component)self).gameObject.AddComponent<LiveSkillDamageTooltip>();
				}
				liveSkillDamageTooltip.Bind(self, provider, val.targetSkill);
			}
			else if (Object.op_Implicit((Object)(object)liveSkillDamageTooltip))
			{
				liveSkillDamageTooltip.Bind(self, provider, null);
			}
		}

		internal static void Uninstall()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			if (installed)
			{
				installed = false;
				ChoiceAttributeUI.Uninstall();
				object obj = <>O.<0>__ScoreboardEnable;
				if (obj == null)
				{
					hook_OnEnable val = ScoreboardEnable;
					<>O.<0>__ScoreboardEnable = val;
					obj = (object)val;
				}
				ScoreboardController.OnEnable -= (hook_OnEnable)obj;
				object obj2 = <>O.<1>__SkillUpdate;
				if (obj2 == null)
				{
					hook_LateUpdate val2 = SkillUpdate;
					<>O.<1>__SkillUpdate = val2;
					obj2 = (object)val2;
				}
				SkillIcon.LateUpdate -= (hook_LateUpdate)obj2;
				object obj3 = <>O.<2>__TooltipContent;
				if (obj3 == null)
				{
					hook_SetTooltipProvider val3 = TooltipContent;
					<>O.<2>__TooltipContent = val3;
					obj3 = (object)val3;
				}
				TooltipController.SetTooltipProvider -= (hook_SetTooltipProvider)obj3;
				ScoreboardBuildInfo[] array = Object.FindObjectsOfType<ScoreboardBuildInfo>(true);
				foreach (ScoreboardBuildInfo obj4 in array)
				{
					obj4.Dispose();
					Object.Destroy((Object)(object)obj4);
				}
				SkillDamageTooltip[] array2 = Object.FindObjectsOfType<SkillDamageTooltip>(true);
				foreach (SkillDamageTooltip obj5 in array2)
				{
					obj5.Restore();
					Object.Destroy((Object)(object)obj5);
				}
				LiveSkillDamageTooltip[] array3 = Object.FindObjectsOfType<LiveSkillDamageTooltip>(true);
				foreach (LiveSkillDamageTooltip obj6 in array3)
				{
					obj6.Dispose();
					Object.Destroy((Object)(object)obj6);
				}
			}
		}
	}
	internal sealed class LiveSkillDamageTooltip : MonoBehaviour
	{
		private TooltipController controller;

		private TooltipProvider provider;

		private GenericSkill skill;

		private float nextRefresh;

		internal void Bind(TooltipController target, TooltipProvider source, GenericSkill targetSkill)
		{
			controller = target;
			provider = source;
			skill = targetSkill;
			nextRefresh = 0f;
		}

		private void LateUpdate()
		{
			if (Object.op_Implicit((Object)(object)skill) && Object.op_Implicit((Object)(object)controller) && Object.op_Implicit((Object)(object)controller.bodyLabel) && !(Time.unscaledTime < nextRefresh))
			{
				nextRefresh = Time.unscaledTime + 0.1f;
				string text = BuildInfo.SkillTooltip(skill);
				if (text != null && ((TMP_Text)controller.bodyLabel).text != text)
				{
					((TMP_Text)controller.bodyLabel).text = text;
				}
			}
		}

		internal void Dispose()
		{
			if (Object.op_Implicit((Object)(object)controller) && Object.op_Implicit((Object)(object)controller.bodyLabel) && Object.op_Implicit((Object)(object)provider))
			{
				((TMP_Text)controller.bodyLabel).text = provider.bodyText;
			}
			skill = null;
		}
	}
	internal sealed class SkillDamageTooltip : MonoBehaviour
	{
		private TooltipProvider provider;

		private string original;

		internal void Set(TooltipProvider target, string text)
		{
			if (!Object.op_Implicit((Object)(object)provider))
			{
				provider = target;
				original = target.overrideBodyText;
			}
			target.overrideBodyText = text;
			target.disableBodyRichText = false;
		}

		internal void Restore()
		{
			if (Object.op_Implicit((Object)(object)provider))
			{
				provider.overrideBodyText = original;
			}
			provider = null;
		}

		private void OnDestroy()
		{
			Restore();
		}
	}
	internal sealed class ScoreboardBuildInfo : MonoBehaviour
	{
		private TextMeshProUGUI label;

		private HUD hud;

		internal void Initialize(ScoreboardController scoreboard)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//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_0068: 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_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			hud = ((Component)scoreboard).GetComponentInParent<HUD>();
			if (!Object.op_Implicit((Object)(object)label) && Object.op_Implicit((Object)(object)scoreboard.container))
			{
				GameObject val = new GameObject("ADAPCurrentAttributes", new Type[1] { typeof(RectTransform) });
				val.transform.SetParent((Transform)(object)scoreboard.container, false);
				RectTransform val2 = (RectTransform)val.transform;
				val2.anchorMin = Vector2.zero;
				val2.anchorMax = new Vector2(1f, 0f);
				val2.pivot = new Vector2(0.5f, 0f);
				val2.sizeDelta = new Vector2(-24f, 36f);
				label = (TextMeshProUGUI)(object)val.AddComponent<HGTextMeshProUGUI>();
				((HGTextMeshProUGUI)label).useLanguageDefaultFont = true;
				if (Object.op_Implicit((Object)(object)HGTextMeshProUGUI.defaultLanguageFont))
				{
					((TMP_Text)label).font = HGTextMeshProUGUI.defaultLanguageFont;
				}
				((TMP_Text)label).fontSize = 22f;
				((TMP_Text)label).richText = true;
				((Graphic)label).raycastTarget = false;
				((TMP_Text)label).alignment = (TextAlignmentOptions)4100;
				LayoutElement obj = val.AddComponent<LayoutElement>();
				float minHeight = (obj.preferredHeight = 36f);
				obj.minHeight = minHeight;
				obj.flexibleWidth = 1f;
			}
			Refresh();
		}

		private void LateUpdate()
		{
			Refresh();
		}

		private void Refresh()
		{
			if (Object.op_Implicit((Object)(object)label))
			{
				if (((TMP_Text)label).transform.GetSiblingIndex() != ((TMP_Text)label).transform.parent.childCount - 1)
				{
					((TMP_Text)label).transform.SetAsLastSibling();
				}
				object body;
				if (!Object.op_Implicit((Object)(object)hud))
				{
					body = null;
				}
				else
				{
					LocalUser localUserViewer = hud.localUserViewer;
					body = ((localUserViewer != null) ? localUserViewer.cachedBody : null);
				}
				float ad;
				float ap;
				bool flag = BuildInfo.TryGetCurrent((CharacterBody)body, out ad, out ap);
				((Behaviour)label).enabled = flag;
				if (flag)
				{
					((TMP_Text)label).text = Descriptions.CurrentAttributes(ad, ap, prefix: false);
				}
			}
		}

		internal void Dispose()
		{
			if (Object.op_Implicit((Object)(object)label))
			{
				Object.Destroy((Object)(object)((Component)label).gameObject);
			}
			label = null;
			((Behaviour)this).enabled = false;
		}

		private void OnDestroy()
		{
			Dispose();
		}
	}
	internal static class SkillFeedbackText
	{
		public static string Coefficients(SkillCoefficients c)
		{
			return AttributeText.Base(c.Base) + " + " + AttributeText.AD(c.AD) + " + " + AttributeText.AP(c.AP);
		}

		public static string Description(string kind, bool zh, int nativeBarrageCount)
		{
			SkillCoefficients primary = SliceTuning.Primary;
			string text = AttributeText.Base(primary.Base) + " + " + AttributeText.AD(primary.AD);
			switch (kind)
			{
			case "Primary":
				if (!zh)
				{
					return "Shoot rapidly for " + text + " damage per bullet.";
				}
				return "快速射击,每发造成" + text + "伤害。";
			case "Phase":
				if (!zh)
				{
					return "Fire a piercing round for " + Coefficients(SliceTuning.PhaseRound) + " damage, gaining 40% damage after each enemy.";
				}
				return "发射穿透弹,造成" + Coefficients(SliceTuning.PhaseRound) + "伤害。每穿过一个敌人,伤害提高40%。";
			case "Barrage":
				if (!zh)
				{
					return "Fire repeatedly for " + Coefficients(SliceTuning.BarrageTotal.Divide(nativeBarrageCount)) + " damage per bullet, stunning enemies. Attack speed increases the number of shots.";
				}
				return "连续射击,每发造成" + Coefficients(SliceTuning.BarrageTotal.Divide(nativeBarrageCount)) + "伤害并眩晕敌人。攻击速度会增加射击次数。";
			default:
				return null;
			}
		}

		public static string AppendDamage(string text, string kind, bool zh, float damage, int shots, float speed)
		{
			if (kind == "Barrage")
			{
				return text + (zh ? "\n每发伤害:" : "\nDamage per bullet: ") + AttributeText.Number(damage) + (zh ? "\n当前攻速:" : "\nCurrent attack speed: ") + AttributeText.Number(speed) + (zh ? ";射击次数:" : "; shots: ") + shots + (zh ? ";总伤害:" : "; total damage: ") + AttributeText.Number(damage * (float)shots);
			}
			return text + (zh ? "\n当前伤害:" : "\nCurrent damage: ") + AttributeText.Number(damage) + ((!(kind == "Phase")) ? (zh ? "(每发)" : " (per bullet)") : (zh ? "(首次命中)" : " (first hit)"));
		}
	}
	public static class SliceTuning
	{
		public const float GrowthPerLevel = 0.01f;

		public const int ADTypeWeight = 500000;

		public const int TypeWeightTotal = 1000000;

		public const int RollMin = 5;

		public const int LowEnd = 15;

		public const int PlateauEnd = 35;

		public const int RollMax = 50;

		public const int WeightTotal = 1000000000;

		public const int LowWeight = 200000000;

		public const int PlateauWeight = 700000000;

		public const int HighWeight = 100000000;

		public const double LowDecay = 0.1212;

		public const double HighDecay = 0.2977;

		public const float SnapshotInterval = 2f;

		public static readonly SkillCoefficients Primary = new SkillCoefficients(0.5f, 0.5f, 0f);

		public static readonly SkillCoefficients PhaseRound = new SkillCoefficients(1f, 1f, 1f);

		public static readonly SkillCoefficients BarrageTotal = new SkillCoefficients(2f, 2f, 2f);

		public const string ADColor = "#F2A65A";

		public const string APColor = "#66B5F5";
	}
	public readonly struct SkillCoefficients
	{
		public readonly float Base;

		public readonly float AD;

		public readonly float AP;

		public SkillCoefficients(float basis, float ad, float ap)
		{
			Base = basis;
			AD = ad;
			AP = ap;
		}

		public float Damage(float basis, float ad, float ap)
		{
			return Base * basis + AD * ad + AP * ap;
		}

		public SkillCoefficients Divide(int count)
		{
			if (count <= 0)
			{
				throw new ArgumentOutOfRangeException("count");
			}
			return new SkillCoefficients(Base / (float)count, AD / (float)count, AP / (float)count);
		}
	}
	public readonly struct AttributeRoll
	{
		public readonly bool IsAD;

		public readonly int Percent;

		public string RichText => "<color=" + (IsAD ? "#F2A65A" : "#66B5F5") + ">+" + Percent.ToString(CultureInfo.InvariantCulture) + "% " + (IsAD ? "AD" : "AP") + "</color>";

		public AttributeRoll(bool isAD, int percent)
		{
			if (percent < 5 || percent > 50)
			{
				throw new ArgumentOutOfRangeException("percent");
			}
			IsAD = isAD;
			Percent = percent;
		}

		public override string ToString()
		{
			return "+" + Percent + "% " + (IsAD ? "AD" : "AP");
		}
	}
	public static class RollDistribution
	{
		private static readonly int[] weights = BuildWeights();

		public static IReadOnlyList<int> Weights => Array.AsReadOnly(weights);

		private static int[] BuildWeights()
		{
			int[] array = new int[46];
			Allocate(array, 5, 15, 200000000, (int v) => Math.Exp(0.1212 * (double)(v - 15)));
			int num = 20;
			if (700000000 % num != 0)
			{
				throw new InvalidOperationException("Nonintegral plateau weights.");
			}
			for (int num2 = 16; num2 <= 35; num2++)
			{
				array[num2 - 5] = 700000000 / num;
			}
			Allocate(array, 36, 50, 100000000, (int v) => Math.Exp(-0.2977 * (double)(v - 35 - 1)));
			if (array.Sum() != 1000000000 || array.Any((int w) => w <= 0))
			{
				throw new InvalidOperationException("Invalid roll weight budget.");
			}
			return array;
		}

		private static void Allocate(int[] output, int min, int max, int budget, Func<int, double> shape)
		{
			double num = Enumerable.Range(min, max - min + 1).Sum(shape);
			List<KeyValuePair<int, double>> list = new List<KeyValuePair<int, double>>();
			int num2 = 0;
			for (int i = min; i <= max; i++)
			{
				double num3 = shape(i) / num * (double)budget;
				int num4 = (output[i - 5] = (int)Math.Floor(num3));
				num2 += num4;
				list.Add(new KeyValuePair<int, double>(i, num3 - (double)num4));
			}
			foreach (KeyValuePair<int, double> item in (from p in list
				orderby p.Value descending, p.Key
				select p).Take(budget - num2))
			{
				output[item.Key - 5]++;
			}
		}

		public static int FromTicket(int ticket)
		{
			if (ticket < 0 || ticket >= 1000000000)
			{
				throw new ArgumentOutOfRangeException("ticket");
			}
			for (int i = 0; i < weights.Length; i++)
			{
				if (ticket < weights[i])
				{
					return i + 5;
				}
				ticket -= weights[i];
			}
			throw new InvalidOperationException("Uncovered roll ticket.");
		}

		public static AttributeRoll Next(Random rng)
		{
			return new AttributeRoll(rng.Next(1000000) < 500000, FromTicket(rng.Next(1000000000)));
		}
	}
	public readonly struct AttributePool
	{
		public readonly int ADPoints;

		public readonly int APPoints;

		public float ADPercent => (float)ADPoints / 100f;

		public float APPercent => (float)APPoints / 100f;

		public AttributePool(int ad, int ap)
		{
			ADPoints = ad;
			APPoints = ap;
		}

		public AttributePool Add(AttributeRoll roll)
		{
			return checked(new AttributePool(ADPoints + (roll.IsAD ? roll.Percent : 0), APPoints + ((!roll.IsAD) ? roll.Percent : 0)));
		}
	}
	public sealed class FrozenOffer
	{
		public readonly ulong Id;

		public readonly uint Owner;

		private readonly AttributeRoll[] rolls;

		public int Count => rolls.Length;

		public bool Consumed { get; private set; }

		public AttributeRoll this[int index] => rolls[index];

		public FrozenOffer(ulong id, uint owner, AttributeRoll[] candidates)
		{
			Id = id;
			Owner = owner;
			rolls = (AttributeRoll[])candidates.Clone();
		}

		public bool TryConsume(uint owner, int index, out AttributeRoll roll)
		{
			roll = default(AttributeRoll);
			if (Consumed || owner != Owner || index < 0 || index >= Count)
			{
				return false;
			}
			Consumed = true;
			roll = rolls[index];
			return true;
		}
	}
	public sealed class RunAttributeLedger
	{
		private readonly Dictionary<uint, AttributePool> pools = new Dictionary<uint, AttributePool>();

		public IReadOnlyDictionary<uint, AttributePool> Pools => pools;

		public AttributePool Get(uint owner)
		{
			if (!pools.TryGetValue(owner, out var value))
			{
				return default(AttributePool);
			}
			return value;
		}

		public bool Commit(uint owner, FrozenOffer offer, int index, out AttributeRoll roll)
		{
			roll = default(AttributeRoll);
			if (offer == null || offer.Consumed || offer.Owner != owner || index < 0 || index >= offer.Count)
			{
				return false;
			}
			AttributePool value = Get(owner).Add(offer[index]);
			if (!offer.TryConsume(owner, index, out roll))
			{
				return false;
			}
			pools[owner] = value;
			return true;
		}

		public void Replace(IEnumerable<KeyValuePair<uint, AttributePool>> snapshot)
		{
			pools.Clear();
			foreach (KeyValuePair<uint, AttributePool> item in snapshot)
			{
				pools.Add(item.Key, item.Value);
			}
		}

		public void Reset()
		{
			pools.Clear();
		}
	}
	public sealed class GrowthSnapshotData
	{
		public const byte ProtocolVersion = 2;

		public uint RunId;

		public ulong Revision;

		public Dictionary<uint, AttributePool> Pools = new Dictionary<uint, AttributePool>();

		public BridgeBinding Binding;

		public byte[] Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			if (RunId == 0 || Pools == null || Pools.Count > 1024)
			{
				throw new InvalidDataException("Invalid scope/pools.");
			}
			binaryWriter.Write((byte)2);
			binaryWriter.Write(RunId);
			binaryWriter.Write(Revision);
			binaryWriter.Write(Pools.Count);
			foreach (KeyValuePair<uint, AttributePool> item in Pools.OrderBy((KeyValuePair<uint, AttributePool> p) => p.Key))
			{
				if (item.Key == 0 || item.Value.ADPoints < 0 || item.Value.APPoints < 0)
				{
					throw new InvalidDataException("Invalid owner/pool.");
				}
				binaryWriter.Write(item.Key);
				binaryWriter.Write(item.Value.ADPoints);
				binaryWriter.Write(item.Value.APPoints);
			}
			bool flag = Binding != null && !Binding.Offer.Rolls.Consumed;
			binaryWriter.Write(flag);
			if (flag)
			{
				BridgeOffer offer = Binding.Offer;
				if (offer.RunId != RunId)
				{
					throw new InvalidDataException("Binding run mismatch.");
				}
				binaryWriter.Write(Binding.PickerId);
				binaryWriter.Write(Binding.Display);
				Guid session = offer.Key.Session;
				binaryWriter.Write(session.ToByteArray());
				binaryWriter.Write(offer.Key.Run);
				binaryWriter.Write(offer.Key.Reward);
				binaryWriter.Write(offer.Rolls.Owner);
				binaryWriter.Write(offer.Count);
				for (int num = 0; num < offer.Count; num++)
				{
					CandidateValue candidateValue = offer.Candidate(num);
					AttributeRoll attributeRoll = offer.Rolls[num];
					binaryWriter.Write(candidateValue.Index);
					binaryWriter.Write(candidateValue.Pickup);
					binaryWriter.Write(candidateValue.Decay);
					binaryWriter.Write(candidateValue.Upgrade);
					binaryWriter.Write(attributeRoll.IsAD);
					binaryWriter.Write(attributeRoll.Percent);
				}
			}
			return memoryStream.ToArray();
		}

		public static GrowthSnapshotData Decode(byte[] payload)
		{
			if (payload == null || payload.Length > 16384)
			{
				throw new InvalidDataException("Invalid payload size.");
			}
			using MemoryStream memoryStream = new MemoryStream(payload, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			if (binaryReader.ReadByte() != 2)
			{
				throw new InvalidDataException("Growth protocol mismatch (expected 2).");
			}
			GrowthSnapshotData growthSnapshotData = new GrowthSnapshotData
			{
				RunId = binaryReader.ReadUInt32(),
				Revision = binaryReader.ReadUInt64()
			};
			if (growthSnapshotData.RunId == 0)
			{
				throw new InvalidDataException("Invalid run identity.");
			}
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 1024)
			{
				throw new InvalidDataException("Invalid pool count.");
			}
			for (int i = 0; i < num; i++)
			{
				uint num2 = binaryReader.ReadUInt32();
				int num3 = binaryReader.ReadInt32();
				int num4 = binaryReader.ReadInt32();
				if (num2 == 0 || num3 < 0 || num4 < 0 || growthSnapshotData.Pools.ContainsKey(num2))
				{
					throw new InvalidDataException("Invalid/duplicate owner.");
				}
				growthSnapshotData.Pools.Add(num2, new AttributePool(num3, num4));
			}
			if (ReadFlag(binaryReader))
			{
				uint picker = binaryReader.ReadUInt32();
				ulong display = binaryReader.ReadUInt64();
				byte[] array = binaryReader.ReadBytes(16);
				if (array.Length != 16)
				{
					throw new EndOfStreamException();
				}
				RewardKey key = new RewardKey(new Guid(array), binaryReader.ReadUInt64(), binaryReader.ReadUInt64());
				uint owner = binaryReader.ReadUInt32();
				int num5 = binaryReader.ReadInt32();
				if (num5 < 1 || num5 > 3)
				{
					throw new InvalidDataException("Invalid candidate count.");
				}
				CandidateValue[] array2 = new CandidateValue[num5];
				AttributeRoll[] array3 = new AttributeRoll[num5];
				for (int j = 0; j < num5; j++)
				{
					array2[j] = new CandidateValue(binaryReader.ReadInt32(), binaryReader.ReadInt32(), binaryReader.ReadSingle(), binaryReader.ReadInt32());
					array3[j] = new AttributeRoll(ReadFlag(binaryReader), binaryReader.ReadInt32());
				}
				growthSnapshotData.Binding = new BridgeBinding(new BridgeOffer(key, growthSnapshotData.RunId, owner, array2, array3), picker, display);
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new InvalidDataException("Unexpected payload suffix.");
			}
			return growthSnapshotData;
		}

		private static bool ReadFlag(BinaryReader reader)
		{
			byte num = reader.ReadByte();
			if (num > 1)
			{
				throw new InvalidDataException("Invalid boolean flag.");
			}
			return num != 0;
		}
	}
	public static class ChoiceGrowth
	{
		private static readonly BridgeState server = new BridgeState();

		private static readonly BridgeReplica replica = new BridgeReplica();

		private static readonly Random rng = new Random(Guid.NewGuid().GetHashCode());

		private static Run scope;

		private static float nextSnapshot;

		private static readonly HashSet<string> diagnostics = new HashSet<string>();

		public static event Action Changed;

		internal static void Install()
		{
			GrowthTransport.Install();
		}

		internal static void Uninstall()
		{
			GrowthTransport.Uninstall();
		}

		public static AttributePool GetPool(CharacterMaster master)
		{
			//IL_0050: 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_0037: 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)
			if (!Object.op_Implicit((Object)(object)Run.instance) || scope != Run.instance || !Object.op_Implicit((Object)(object)master))
			{
				return default(AttributePool);
			}
			NetworkInstanceId netId;
			if (!NetworkServer.active)
			{
				BridgeReplica bridgeReplica = replica;
				netId = ((NetworkBehaviour)master).netId;
				return bridgeReplica.Get(((NetworkInstanceId)(ref netId)).Value);
			}
			BridgeState bridgeState = server;
			netId = ((NetworkBehaviour)master).netId;
			return bridgeState.Get(((NetworkInstanceId)(ref netId)).Value);
		}

		public static bool TryGetOffer(PickupPickerController picker, out FrozenOffer offer)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			BridgeBinding bridgeBinding = (NetworkServer.active ? server.Visible : null);
			offer = bridgeBinding?.Offer.Rolls;
			if (Object.op_Implicit((Object)(object)Run.instance) && scope == Run.instance && Object.op_Implicit((Object)(object)picker) && bridgeBinding != null)
			{
				NetworkInstanceId netId = ((NetworkBehaviour)picker).netId;
				if (((NetworkInstanceId)(ref netId)).Value == bridgeBinding.PickerId)
				{
					return !offer.Consumed;
				}
			}
			return false;
		}

		internal static BridgeOffer Queue(RewardKey key, uint run, CharacterMaster owner, CandidateValue[] values)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (!Authoritative(run, owner))
			{
				return null;
			}
			EnsureScope();
			BridgeState bridgeState = server;
			NetworkInstanceId netId = ((NetworkBehaviour)owner).netId;
			BridgeOffer offer = bridgeState.Queue(key, run, ((NetworkInstanceId)(ref netId)).Value, values, () => RollDistribution.Next(rng));
			if (offer != null)
			{
				Plugin.Log.LogInfo((object)string.Format("[Choice Roll] choice={0} owner={1} candidates={2}", key, ((NetworkBehaviour)owner).netId, string.Join(", ", from i in Enumerable.Range(0, offer.Count)
					select offer.Candidate(i).Index + ":" + offer.Rolls[i])));
			}
			return offer;
		}

		internal static void Bind(RewardKey key, uint run, CharacterMaster owner, uint picker, ulong display)
		{
			//IL_0017: 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)
			if (Authoritative(run, owner))
			{
				EnsureScope();
				BridgeState bridgeState = server;
				NetworkInstanceId netId = ((NetworkBehaviour)owner).netId;
				if (bridgeState.Bind(key, run, ((NetworkInstanceId)(ref netId)).Value, picker, display))
				{
					Publish();
				}
				else
				{
					Diagnose("binding", $"Rejected unknown/terminal/duplicate binding {key}, picker={picker}, display={display}.");
				}
			}
		}

		internal static void Succeeded(RewardKey key, uint run, CharacterMaster owner, uint picker, CandidateValue selected)
		{
			//IL_0017: 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_004a: Unknown result type (might be due to invalid IL or missing references)
			if (Authoritative(run, owner))
			{
				EnsureScope();
				BridgeState bridgeState = server;
				NetworkInstanceId netId = ((NetworkBehaviour)owner).netId;
				if (!bridgeState.RecordSuccess(key, run, ((NetworkInstanceId)(ref netId)).Value, picker, selected))
				{
					Diagnose("success", $"Rejected duplicate/invalid success {key}, owner={((NetworkBehaviour)owner).netId}, picker={picker}, index={selected.Index}.");
				}
				else
				{
					Recover(key);
				}
			}
		}

		private static void Recover(RewardKey key)
		{
			try
			{
				if (server.TryCommit(key))
				{
					Plugin.Log.LogInfo((object)string.Format("[Choice Growth] choice={0} committed once; pools={1}", key, string.Join(", ", server.Pools.Select((KeyValuePair<uint, AttributePool> p) => p.Key + ":AD=" + p.Value.ADPoints + "%/AP=" + p.Value.APPoints + "%"))));
					Publish();
				}
			}
			catch (Exception ex)
			{
				Diagnose("commit:" + key, "Native item already granted; recorded growth intent retained for periodic same-run retry. " + ex);
			}
		}

		internal static void Cancel(RewardKey key, uint run)
		{
			if (scope != null && server.RunId == run && server.Cancel(key) && NetworkServer.active)
			{
				Publish();
			}
		}

		internal static string CandidateText(uint run, uint picker, uint owner, RewardKey? logical, CandidateValue candidate)
		{
			//IL_001d: 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 (Object.op_Implicit((Object)(object)Run.instance) && scope == Run.instance)
			{
				NetworkInstanceId netId = ((NetworkBehaviour)Run.instance).netId;
				if (((NetworkInstanceId)(ref netId)).Value == run)
				{
					if (!NetworkServer.active)
					{
						return replica.Text(run, picker, owner, logical, candidate);
					}
					BridgeBinding visible = server.Visible;
					if (visible == null || visible.PickerId != picker || visible.Offer.Rolls.Owner != owner || !logical.HasValue || !logical.Value.Equals(visible.Offer.Key))
					{
						return null;
					}
					int num = visible.Offer.Slot(candidate);
					if (num >= 0)
					{
						return visible.Offer.Rolls[num].RichText;
					}
					return null;
				}
			}
			return null;
		}

		private static bool Authoritative(uint run, CharacterMaster owner)
		{
			//IL_0018: 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_0031: 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)
			int num;
			if (NetworkServer.active && Object.op_Implicit((Object)(object)Run.instance))
			{
				NetworkInstanceId netId = ((NetworkBehaviour)Run.instance).netId;
				if (((NetworkInstanceId)(ref netId)).Value == run && Object.op_Implicit((Object)(object)owner))
				{
					netId = ((NetworkBehaviour)owner).netId;
					num = ((((NetworkInstanceId)(ref netId)).Value != 0) ? 1 : 0);
					if (num == 0)
					{
						goto IL_0047;
					}
					goto IL_0056;
				}
			}
			num = 0;
			goto IL_0047;
			IL_0047:
			Diagnose("authority", "Rejected TCC callback outside current server run/owner.");
			goto IL_0056;
			IL_0056:
			return (byte)num != 0;
		}

		private static void EnsureScope()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//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 (scope != Run.instance)
			{
				Reset(Run.instance);
			}
			else if (NetworkServer.active && Object.op_Implicit((Object)(object)Run.instance) && server.RunId == 0)
			{
				NetworkInstanceId netId = ((NetworkBehaviour)Run.instance).netId;
				if (((NetworkInstanceId)(ref netId)).Value != 0)
				{
					BridgeState bridgeState = server;
					netId = ((NetworkBehaviour)Run.instance).netId;
					bridgeState.Reset(((NetworkInstanceId)(ref netId)).Value);
				}
			}
		}

		internal static void Reset(Run run)
		{
			//IL_0011: 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)
			BridgeState bridgeState = server;
			int run2;
			if (!Object.op_Implicit((Object)(object)run))
			{
				run2 = 0;
			}
			else
			{
				NetworkInstanceId netId = ((NetworkBehaviour)run).netId;
				run2 = (int)((NetworkInstanceId)(ref netId)).Value;
			}
			bridgeState.Reset((uint)run2);
			replica.Reset();
			scope = run;
			nextSnapshot = 0f;
			diagnostics.Clear();
			Notify();
		}

		internal static void Tick()
		{
			if (!NetworkServer.active || !Object.op_Implicit((Object)(object)Run.instance))
			{
				return;
			}
			EnsureScope();
			if (!(Time.unscaledTime < nextSnapshot))
			{
				nextSnapshot = Time.unscaledTime + 2f;
				RewardKey[] array = server.PendingSuccesses();
				for (int i = 0; i < array.Length; i++)
				{
					Recover(array[i]);
				}
				SendSnapshot();
			}
		}

		private static void Publish()
		{
			Notify();
			SendSnapshot();
		}

		private static void Notify()
		{
			if (ChoiceGrowth.Changed == null)
			{
				return;
			}
			Delegate[] invocationList = ChoiceGrowth.Changed.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action action = (Action)invocationList[i];
				try
				{
					action();
				}
				catch (Exception ex)
				{
					Diagnose("changed:" + action.Method.Name, ex.ToString());
				}
			}
		}

		private static void SendSnapshot()
		{
			if (!Object.op_Implicit((Object)(object)Run.instance) || !NetworkServer.active || RoR2Application.isInSinglePlayer)
			{
				return;
			}
			try
			{
				GrowthTransport.Send(new GrowthSnapshotData
				{
					RunId = server.RunId,
					Revision = server.Revision,
					Pools = server.Pools.ToDictionary((KeyValuePair<uint, AttributePool> p) => p.Key, (KeyValuePair<uint, AttributePool> p) => p.Value),
					Binding = server.Visible
				});
			}
			catch (Exception ex)
			{
				Diagnose("send", "Growth snapshot send failed; next periodic resync will retry. " + ex);
			}
		}

		internal static void Receive(GrowthSnapshotData value, bool fromServer)
		{
			//IL_0037: 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)
			if (NetworkServer.active || !fromServer)
			{
				Diagnose("source", "Rejected unauthorized growth snapshot.");
			}
			else if (Object.op_Implicit((Object)(object)Run.instance))
			{
				EnsureScope();
				BridgeReplica bridgeReplica = replica;
				NetworkInstanceId netId = ((NetworkBehaviour)Run.instance).netId;
				if (bridgeReplica.Receive(value, ((NetworkInstanceId)(ref netId)).Value, isServer: false, fromServer, out var rejection))
				{
					Notify();
				}
				else if (!rejection.StartsWith("Duplicate", StringComparison.Ordinal))
				{
					Diagnose("receive", rejection);
				}
			}
		}

		internal static void Diagnose(string key, string message)
		{
			if (diagnostics.Count < 16 && diagnostics.Add(key))
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[ADAP Bridge] " + message));
				}
			}
		}
	}
	public readonly struct RewardKey : IEquatable<RewardKey>
	{
		public readonly Guid Session;

		public readonly ulong Run;

		public readonly ulong Reward;

		public RewardKey(Guid session, ulong run, ulong reward)
		{
			if (session == Guid.Empty || run == 0L || reward == 0L)
			{
				throw new ArgumentException("Invalid reward identity.");
			}
			Session = session;
			Run = run;
			Reward = reward;
		}

		public bool Equals(RewardKey other)
		{
			if (Session == other.Session && Run == other.Run)
			{
				return Reward == other.Reward;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is RewardKey other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (((Session.GetHashCode() * 397) ^ Run.GetHashCode()) * 397) ^ Reward.GetHashCode();
		}

		public override string ToString()
		{
			return $"{Session:N}/{Run}/{Reward}";
		}
	}
	public readonly struct CandidateValue : IEquatable<CandidateValue>
	{
		public readonly int Index;

		public readonly int Pickup;

		public readonly int Upgrade;

		public readonly float Decay;

		public CandidateValue(int index, int pickup, float decay, int upgrade)
		{
			if (index < 0 || float.IsNaN(decay) || float.IsInfinity(decay))
			{
				throw new ArgumentException("Invalid candidate value.");
			}
			Index = index;
			Pickup = pickup;
			Decay = decay;
			Upgrade = upgrade;
		}

		public bool Equals(CandidateValue other)
		{
			if (Index == other.Index && Pickup == other.Pickup && Decay.Equals(other.Decay))
			{
				return Upgrade == other.Upgrade;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is CandidateValue other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return Index;
		}
	}
	public sealed class BridgeOffer
	{
		public readonly RewardKey Key;

		public readonly uint RunId;

		public readonly FrozenOffer Rolls;

		private readonly CandidateValue[] candidates;

		public int Count => candidates.Length;

		public CandidateValue Candidate(int slot)
		{
			return candidates[slot];
		}

		public BridgeOffer(RewardKey key, uint runId, uint owner, CandidateValue[] values, AttributeRoll[] rolls)
		{
			if (runId == 0 || owner == 0 || values == null || rolls == null || values.Length < 1 || values.Length > 3 || values.Length != rolls.Length || values.Select((CandidateValue v) => v.Index).Distinct().Count() != values.Length)
			{
				throw new ArgumentException("Invalid frozen candidate mapping.");
			}
			Key = key;
			RunId = runId;
			candidates = (CandidateValue[])values.Clone();
			Rolls = new FrozenOffer(key.Reward, owner, rolls);
		}

		public int Slot(CandidateValue candidate)
		{
			for (int i = 0; i < Count; i++)
			{
				if (candidates[i].Equals(candidate))
				{
					return i;
				}
			}
			return -1;
		}
	}
	public sealed class BridgeBinding
	{
		public readonly BridgeOffer Offer;

		public readonly uint PickerId;

		public readonly ulong Display;

		public BridgeBinding(BridgeOffer offer, uint picker, ulong display)
		{
			if (offer == null || picker == 0 || display == 0L)
			{
				throw new ArgumentException("Invalid picker binding.");
			}
			Offer = offer;
			PickerId = picker;
			Display = display;
		}
	}
	public sealed class BridgeState
	{
		private sealed class Entry
		{
			internal BridgeOffer Offer;

			internal ulong Display;

			internal uint Picker;

			internal bool Cancelled;

			internal bool SuccessRecorded;

			internal bool Committed;

			internal int SelectedSlot;
		}

		private readonly Dictionary<RewardKey, Entry> entries = new Dictionary<RewardKey, Entry>();

		private readonly HashSet<RewardKey> terminal = new HashSet<RewardKey>();

		private readonly RunAttributeLedger ledger = new RunAttributeLedger();

		public uint RunId { get; private set; }

		public ulong Revision { get; private set; }

		public BridgeBinding Visible { get; private set; }

		public IReadOnlyDictionary<uint, AttributePool> Pools => ledger.Pools;

		public AttributePool Get(uint owner)
		{
			return ledger.Get(owner);
		}

		public void Reset(uint run)
		{
			entries.Clear();
			terminal.Clear();
			ledger.Reset();
			Visible = null;
			Revision = 0uL;
			RunId = run;
		}

		private void Advance()
		{
			checked
			{
				Revision++;
			}
		}

		public BridgeOffer Queue(RewardKey key, uint run, uint owner, CandidateValue[] candidates, Func<AttributeRoll> next)
		{
			if (run == 0 || run != RunId || owner == 0)
			{
				throw new InvalidOperationException("Reward outside current server run/owner.");
			}
			if (terminal.Contains(key))
			{
				return null;
			}
			if (entries.TryGetValue(key, out var old))
			{
				if (old.Offer.RunId != run || old.Offer.Rolls.Owner != owner || old.Offer.Count != candidates.Length || !Enumerable.Range(0, candidates.Length).All((int i) => old.Offer.Candidate(i).Equals(candidates[i])))
				{
					throw new InvalidOperationException("Conflicting frozen reward identity.");
				}
				return old.Offer;
			}
			new BridgeOffer(key, run, owner, candidates, candidates.Select((CandidateValue _) => new AttributeRoll(isAD: true, 5)).ToArray());
			BridgeOffer bridgeOffer = new BridgeOffer(key, run, owner, candidates, candidates.Select((CandidateValue _) => next()).ToArray());
			entries.Add(key, new Entry
			{
				Offer = bridgeOffer
			});
			return bridgeOffer;
		}

		public bool Bind(RewardKey key, uint run, uint owner, uint picker, ulong display)
		{
			if (run != RunId || !entries.TryGetValue(key, out var value) || value.Cancelled || value.SuccessRecorded || value.Offer.Rolls.Owner != owner || picker == 0 || display <= value.Display)
			{
				return false;
			}
			value.Display = display;
			value.Picker = picker;
			Visible = new BridgeBinding(value.Offer, picker, display);
			Advance();
			return true;
		}

		public bool RecordSuccess(RewardKey key, uint run, uint owner, uint picker, CandidateValue selected)
		{
			if (run != RunId || !entries.TryGetValue(key, out var value) || value.Cancelled || value.Committed || value.Offer.Rolls.Owner != owner || value.Picker != picker || picker == 0)
			{
				return false;
			}
			int num = value.Offer.Slot(selected);
			if (num < 0 || (value.SuccessRecorded && value.SelectedSlot != num))
			{
				return false;
			}
			value.SelectedSlot = num;
			value.SuccessRecorded = true;
			return true;
		}

		public bool TryCommit(RewardKey key, Action beforeCommit = null)
		{
			if (!entries.TryGetValue(key, out var value) || !value.SuccessRecorded || value.Committed)
			{
				return false;
			}
			beforeCommit?.Invoke();
			if (!ledger.Commit(value.Offer.Rolls.Owner, value.Offer.Rolls, value.SelectedSlot, out var _))
			{
				return false;
			}
			value.Committed = true;
			terminal.Add(key);
			entries.Remove(key);
			HideIf(key);
			Advance();
			return true;
		}

		public RewardKey[] PendingSuccesses()
		{
			return (from p in entries
				where p.Value.SuccessRecorded && !p.Value.Committed
				select p.Key).ToArray();
		}

		public bool Cancel(RewardKey key)
		{
			if (!entries.TryGetValue(key, out var value) || value.Cancelled || value.SuccessRecorded || value.Committed)
			{
				return false;
			}
			value.Cancelled = true;
			terminal.Add(key);
			entries.Remove(key);
			HideIf(key);
			Advance();
			return true;
		}

		private void HideIf(RewardKey key)
		{
			if (Visible != null && Visible.Offer.Key.Equals(key))
			{
				Visible = null;
			}
		}
	}
	public sealed class BridgeReplica
	{
		private GrowthSnapshotData snapshot;

		public void Reset()
		{
			snapshot = null;
		}

		public AttributePool Get(uint owner)
		{
			if (snapshot == null || !snapshot.Pools.TryGetValue(owner, out var value))
			{
				return default(AttributePool);
			}
			return value;
		}

		public bool Receive(GrowthSnapshotData value, uint run, bool isServer, bool fromServer, out string rejection)
		{
			rejection = null;
			if (isServer || !fromServer)
			{
				rejection = "Unauthorized growth snapshot source.";
			}
			else if (run == 0 || value.RunId != run)
			{
				rejection = "Growth snapshot run mismatch.";
			}
			else if (snapshot != null && value.Revision <= snapshot.Revision)
			{
				rejection = "Duplicate or stale growth revision.";
			}
			if (rejection != null)
			{
				return false;
			}
			snapshot = GrowthSnapshotData.Decode(value.Encode());
			return true;
		}

		public string Text(uint run, uint picker, uint owner, RewardKey? logical, CandidateValue candidate)
		{
			BridgeBinding bridgeBinding = snapshot?.Binding;
			if (bridgeBinding == null || snapshot.RunId != run || bridgeBinding.PickerId != picker || bridgeBinding.Offer.Rolls.Owner != owner || (logical.HasValue && !logical.Value.Equals(bridgeBinding.Offer.Key)))
			{
				return null;
			}
			int num = bridgeBinding.Offer.Slot(candidate);
			if (num >= 0)
			{
				return bridgeBinding.Offer.Rolls[num].RichText;
			}
			return null;
		}
	}
	public static class BridgeCompatibility
	{
		public static bool Supports(Assembly assembly, out string diagnostic)
		{
			diagnostic = null;
			try
			{
				Type type = assembly?.GetType("ThreeChoiceChests.ChoiceExtensionApi", throwOnError: false);
				if (type == null || !(type.GetField("ApiVersion", BindingFlags.Static | BindingFlags.Public)?.GetRawConstantValue() is int num) || num != 1)
				{
					diagnostic = "TCC Choice Extension API v1 missing; plugin version 1.3.1 alone is insufficient. ADAP disabled.";
					return false;
				}
				string[] array = new string[5] { "RewardQueued", "PickerBound", "RewardSucceeded", "RewardCancelled", "CandidateText" };
				foreach (string text in array)
				{
					if (type.GetEvent(text) == null)
					{
						throw new MissingMemberException(type.FullName, text);
					}
				}
				if (type.GetMethod("TryGetServerBinding") == null)
				{
					throw new MissingMethodException(type.FullName, "TryGetServerBinding");
				}
				return true;
			}
			catch (Exception ex)
			{
				diagnostic = "TCC API probe failed; ADAP disabled. " + ex;
				return false;
			}
		}
	}
	internal static class BridgeBootstrap
	{
		private static Action uninstall;

		internal static bool Install()
		{
			if (uninstall != null)
			{
				return true;
			}
			if (!Chainloader.PluginInfos.TryGetValue("com.local.threechoicechests", out var value) || !BridgeCompatibility.Supports(((object)value.Instance).GetType().Assembly, out var _))
			{
				string diagnostic2 = "TCC plugin missing.";
				if (value != null)
				{
					BridgeCompatibility.Supports(((object)value.Instance).GetType().Assembly, out diagnostic2);
				}
				Plugin.Log.LogError((object)("[ADAP Bridge] " + diagnostic2));
				return false;
			}
			StartTyped();
			return true;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void StartTyped()
		{
			uninstall = StopTyped;
			try
			{
				ChoiceGrowth.Install();
				TccChoiceBridge.Install();
			}
			catch
			{
				Uninstall();
				throw;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void StopTyped()
		{
			TccChoiceBridge.Uninstall();
			ChoiceGrowth.Uninstall();
		}

		internal static void Uninstall()
		{
			Action action = uninstall;
			uninstall = null;
			action?.Invoke();
		}
	}
	internal static class TccChoiceBridge
	{
		private static bool installed;

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void Install()
		{
			if (installed)
			{
				return;
			}
			installed = true;
			try
			{
				ChoiceExtensionApi.RewardQueued += Queued;
				ChoiceExtensionApi.PickerBound += Bound;
				ChoiceExtensionApi.RewardSucceeded += Succeeded;
				ChoiceExtensionApi.RewardCancelled += Cancelled;
				ChoiceExtensionApi.CandidateText += Describe;
			}
			catch
			{
				Uninstall();
				throw;
			}
		}

		internal static void Uninstall()
		{
			if (installed)
			{
				installed = false;
				ChoiceExtensionApi.RewardQueued -= Queued;
				ChoiceExtensionApi.PickerBound -= Bound;
				ChoiceExtensionApi.RewardSucceeded -= Succeeded;
				ChoiceExtensionApi.RewardCancelled -= Cancelled;
				ChoiceExtensionApi.CandidateText -= Describe;
			}
		}

		private static RewardKey Key(ChoiceId id)
		{
			return new RewardKey(((ChoiceId)(ref id)).SessionId, ((ChoiceId)(ref id)).RunNumber, ((ChoiceId)(ref id)).RewardNumber);
		}

		private static CandidateValue Value(int index, UniquePickup pickup)
		{
			//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_000c: 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)
			return new CandidateValue(index, pickup.pickupIndex.value, pickup.decayValue, pickup.upgradeValue);
		}

		private static bool OwnerValid(ChoiceSnapshot reward)
		{
			//IL_0016: 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)
			int num;
			if (reward != null && Object.op_Implicit((Object)(object)reward.Owner))
			{
				NetworkInstanceId netId = ((NetworkBehaviour)reward.Owner).netId;
				num = ((((NetworkInstanceId)(ref netId)).Value == reward.OwnerNetId) ? 1 : 0);
				if (num != 0)
				{
					goto IL_0040;
				}
			}
			else
			{
				num = 0;
			}
			ChoiceGrowth.Diagnose("owner", "Rejected TCC reward owner mismatch/destroyed owner.");
			goto IL_0040;
			IL_0040:
			return (byte)num != 0;
		}

		private static void Queued(ChoiceSnapshot reward)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (!OwnerValid(reward))
			{
				return;
			}
			CandidateValue[] values = (from c in reward.Candidates.Where(delegate(ChoiceCandidate c)
				{
					//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)
					if (((ChoiceCandidate)(ref c)).Available)
					{
						UniquePickup pickup = ((ChoiceCandidate)(ref c)).Pickup;
						return ((UniquePickup)(ref pickup)).isValid;
					}
					return false;
				})
				select Value(((ChoiceCandidate)(ref c)).Index, ((ChoiceCandidate)(ref c)).Pickup)).ToArray();
			ChoiceGrowth.Queue(Key(reward.Id), reward.RunNetId, reward.Owner, values);
		}

		private static void Bound(ChoiceBinding binding)
		{
			//IL_0016: 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_003f: Unknown result type (might be due to invalid IL or missing references)
			if (binding != null && Object.op_Implicit((Object)(object)binding.Picker))
			{
				NetworkInstanceId netId = ((NetworkBehaviour)binding.Picker).netId;
				if (((NetworkInstanceId)(ref netId)).Value == binding.PickerNetId && OwnerValid(binding.Reward))
				{
					ChoiceGrowth.Bind(Key(binding.Reward.Id), binding.Reward.RunNetId, binding.Reward.Owner, binding.PickerNetId, binding.DisplayNumber);
				}
			}
		}

		private static void Succeeded(ChoiceSuccess success)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: 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_00f7: Unknown result type (might be due to invalid IL or missing references)
			if (success != null && OwnerValid(success.Reward))
			{
				ChoiceCandidate[] array = success.Reward.Candidates.Where((ChoiceCandidate c) => ((ChoiceCandidate)(ref c)).Index == success.SelectedIndex && ((ChoiceCandidate)(ref c)).Available).ToArray();
				if (array.Length != 1 || !Value(success.SelectedIndex, ((ChoiceCandidate)(ref array[0])).Pickup).Equals(Value(success.SelectedIndex, success.SelectedPickup)))
				{
					ChoiceGrowth.Diagnose("selection", "Rejected TCC success with unavailable/mismatched candidate.");
				}
				else
				{
					ChoiceGrowth.Succeeded(Key(success.Reward.Id), success.Reward.RunNetId, success.Reward.Owner, success.PickerNetId, Value(success.SelectedIndex, success.SelectedPickup));
				}
			}
		}

		private static void Cancelled(ChoiceCancellation cancellation)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (cancellation != null)
			{
				ChoiceGrowth.Cancel(Key(cancellation.Reward.Id), cancellation.Reward.RunNetId);
			}
		}

		private static string Describe(CandidateTooltipContext context)
		{
			//IL_0016: 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_002c: 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_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_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: 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_00ba: 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_00c2: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			if (context != null && Object.op_Implicit((Object)(object)context.Picker))
			{
				NetworkInstanceId netId = ((NetworkBehaviour)context.Picker).netId;
				if (((NetworkInstanceId)(ref netId)).Value == context.PickerNetId)
				{
					ChoiceCandidate candidate = context.Candidate;
					if (((ChoiceCandidate)(ref candidate)).Available && Object.op_Implicit((Object)(object)context.Owner))
					{
						netId = ((NetworkBehaviour)context.Owner).netId;
						if (((NetworkInstanceId)(ref netId)).Value == context.OwnerNetId)
						{
							uint runNetId = context.RunNetId;
							uint pickerNetId = context.PickerNetId;
							uint ownerNetId = context.OwnerNetId;
							RewardKey? logical = (context.LogicalId.HasValue ? new RewardKey?(Key(context.LogicalId.Value)) : ((RewardKey?)null));
							candidate = context.Candidate;
							int index = ((ChoiceCandidate)(ref candidate)).Index;
							candidate = context.Candidate;
							string text = ChoiceGrowth.CandidateText(runNetId, pickerNetId, ownerNetId, logical, Value(index, ((ChoiceCandidate)(ref candidate)).Pickup));
							PickupPickerController picker = context.Picker;
							candidate = context.Candidate;
							ChoiceAttributeUI.SetCandidate(picker, ((ChoiceCandidate)(ref candidate)).Index, text);
							return text;
						}
					}
				}
			}
			return null;
		}
	}
	internal static class GrowthTransport
	{
		private sealed class SnapshotMessage : MessageBase
		{
			internal byte[] Payload;

			public override void Serialize(NetworkWriter writer)
			{
				writer.WriteBytesFull(Payload);
			}
		}

		internal const short MessageId = 18741;

		private static bool installed;

		private static readonly Dictionary<NetworkClient, NetworkMessageDelegate> clients = new Dictionary<NetworkClient, NetworkMessageDelegate>();

		private static readonly NetworkMessageDelegate rejectUpload = (NetworkMessageDelegate)delegate
		{
			ChoiceGrowth.Diagnose("upload", "Rejected client-uploaded growth snapshot; no deserialize, mutation or forwarding.");
		};

		internal static void Install()
		{
			if (installed)
			{
				return;
			}
			installed = true;
			NetworkManagerSystem.onStartServerGlobal += RegisterServer;
			NetworkManagerSystem.onStartClientGlobal += RegisterClient;
			NetworkManagerSystem.onStopClientGlobal += ClearClients;
			if (NetworkServer.active)
			{
				RegisterServer();
			}
			foreach (NetworkClient allClient in NetworkClient.allClients)
			{
				RegisterClient(allClient);
			}
		}

		private static void RegisterServer()
		{
			if (NetworkServer.handlers.TryGetValue(18741, out var value) && (Delegate?)(object)value != (Delegate?)(object)rejectUpload)
			{
				ChoiceGrowth.Diagnose("channel", "Growth message ID collision (18741); server sync disabled, existing handler preserved.");
			}
			else
			{
				NetworkServer.RegisterHandler((short)18741, rejectUpload);
			}
		}

		private static void RegisterClient(NetworkClient client)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			if (client == null || (clients.TryGetValue(client, out var value) && client.handlers.TryGetValue(18741, out var value2) && (Delegate?)(object)value2 == (Delegate?)(object)value))
			{
				return;
			}
			if (client.handlers.ContainsKey(18741))
			{
				ChoiceGrowth.Diagnose("channel", "Growth message ID collision (18741); client sync disabled, existing handler preserved.");
				return;
			}
			NetworkMessageDelegate val = (NetworkMessageDelegate)delegate(NetworkMessage message)
			{
				if (!NetworkServer.active)
				{
					if (message.conn != null && message.conn == client.connection)
					{
						try
						{
							ChoiceGrowth.Receive(GrowthSnapshotData.Decode(message.reader.ReadBytesAndSize()), fromServer: true);
							return;
						}
						catch (Exception ex)
						{
							ChoiceGrowth.Diagnose("decode", "Rejected malformed growth snapshot. " + ex);
							return;
						}
					}
					ChoiceGrowth.Diagnose("source", "Rejected growth message outside the client's server connection.");
				}
			};
			clients[client] = val;
			client.RegisterHandler((short)18741, val);
		}

		internal static void Send(GrowthSnapshotData data)
		{
			if (NetworkServer.active && NetworkServer.handlers.TryGetValue(18741, out var value) && !((Delegate?)(object)value != (Delegate?)(object)rejectUpload) && !NetworkServer.SendToAll((short)18741, (MessageBase)(object)new SnapshotMessage
			{
				Payload = data.Encode()
			}))
			{
				ChoiceGrowth.Diagnose("delivery", "Growth snapshot transport returned false; periodic resync retained.");
			}
		}

		internal static void Uninstall()
		{
			if (installed)
			{
				installed = false;
				NetworkManagerSystem.onStartServerGlobal -= RegisterServer;
				NetworkManagerSystem.onStartClientGlobal -= RegisterClient;
				NetworkManagerSystem.onStopClientGlobal -= ClearClients;
				if (NetworkServer.handlers.TryGetValue(18741, out var value) && (Delegate?)(object)value == (Delegate?)(object)rejectUpload)
				{
					NetworkServer.UnregisterHandler((short)18741);
				}
				ClearClients();
			}
		}

		private static void ClearClients()
		{
			foreach (KeyValuePair<NetworkClient, NetworkMessageDelegate> client in clients)
			{
				if (client.Key.handlers.TryGetValue(18741, out var value) && (Delegate?)(object)value == (Delegate?)(object)client.Value)
				{
					client.Key.UnregisterHandler((short)18741);
				}
			}
			clients.Clear();
		}
	}
	public static class CombatModel
	{
		public static readonly SkillCoefficients PhaseBlast = new SkillCoefficients(1f, 0.5f, 0.5f);

		public static readonly SkillCoefficients Grenade = new SkillCoefficients(3f, 2f, 2f);

		public static float Neutral(DamageValues values)
		{
			return (values.AD + values.AP) * 0.5f;
		}

		public static float Independent(DamageValues values, float nativeScale)
		{
			return Neutral(values) * nativeScale;
		}

		public static bool Eligible(bool active, bool server, bool player, bool commando, bool playerTeam, bool owner)
		{
			return active && server && player && commando && playerTeam && owner;
		}

		public static bool IndependentDot(int index, bool explicitTotal)
		{
			if (!explicitTotal)
			{
				if (index != 0 && index != 1 && index != 6 && index != 7 && index != 8)
				{
					return index == 11;
				}
				return true;
			}
			return false;
		}
	}
	internal static class AlternateSkillText
	{
		public static string Description(bool blast, bool zh)
		{
			if (!blast)
			{
				if (!zh)
				{
					return "Throw a frag grenade. The explosion center deals " + AttributeText.Base(3f) + " + " + AD("200% AD") + " + " + AP("200% AP") + " damage.";
				}
				return "投掷破片手雷,爆炸中心造成" + AttributeText.Base(3f) + " + " + AD("200% AD") + " + " + AP("200% AP") + "伤害。";
			}
			if (!zh)
			{
				return "Fire two bursts of 4 pellets. Each pellet deals " + AttributeText.Base(1f) + " + " + AD("50% AD") + " + " + AP("50% AP") + " damage.";
			}
			return "连续发射两轮霰弹,每轮4发;每弹造成" + AttributeText.Base(1f) + " + " + AD("50% AD") + " + " + AP("50% AP") + "伤害。";
		}

		public static string Append(string text, bool blast, bool zh, float damage)
		{
			return text + "\n" + ((!zh) ? (blast ? "Current per pellet: " : "Current center: ") : (blast ? "当前每弹:" : "当前爆炸中心:")) + AttributeText.Number(damage) + (blast ? ((zh ? "\u30008弹合计:" : "\u30008 pellets: ") + AttributeText.Number(damage * 8f)) : "");
		}

		private static string AD(string text)
		{
			return AttributeText.Color(text, "#F2A65A");
		}

		private static string AP(string text)
		{
			return AttributeText.Color(text, "#66B5F5");
		}
	}
	internal static class AlternateSkills
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static hook_GenerateBulletAttack <0>__Blast;

			public static hook_ModifyProjectileInfo <1>__Grenade;
		}

		private static readonly Dictionary<string, bool> tokens = new Dictionary<string, bool>();

		private static bool installed;

		public static void Install()
		{
			//IL_0016: 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_0021: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			installed = true;
			object obj = <>O.<0>__Blast;
			if (obj == null)
			{
				hook_GenerateBulletAttack val = Blast;
				<>O.<0>__Blast = val;
				obj = (object)val;
			}
			GenericBulletBaseState.GenerateBulletAttack += (hook_GenerateBulletAttack)obj;
			object obj2 = <>O.<1>__Grenade;
			if (obj2 == null)
			{
				hook_ModifyProjectileInfo val2 = Grenade;
				<>O.<1>__Grenade = val2;
				obj2 = (object)val2;
			}
			ThrowGrenade.ModifyProjectileInfo += (hook_ModifyProjectileInfo)obj2;
		}

		public static void Remove()
		{
			//IL_0018: 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: Expected O, but got Unknown
			//IL_0038: 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_0043: Expected O, but got Unknown
			if (installed)
			{
				object obj = <>O.<0>__Blast;
				if (obj == null)
				{
					hook_GenerateBulletAttack val = Blast;
					<>O.<0>__Blast = val;
					obj = (object)val;
				}
				GenericBulletBaseState.GenerateBulletAttack -= (hook_GenerateBulletAttack)obj;
				object obj2 = <>O.<1>__Grenade;
				if (obj2 == null)
				{
					hook_ModifyProjectileInfo val2 = Grenade;
					<>O.<1>__Grenade = val2;
					obj2 = (object)val2;
				}
				ThrowGrenade.ModifyProjectileInfo -= (hook_ModifyProjectileInfo)obj2;
				installed = false;
				tokens.Clear();
			}
		}

		public static void Resolve()
		{
			tokens.Clear();
			foreach (SkillDef allSkillDef in SkillCatalog.allSkillDefs)
			{
				Type stateType = ((SerializableEntityStateType)(ref allSkillDef.activationState)).stateType;
				if (stateType == typeof(FireShotgunBlast))
				{
					tokens[allSkillDef.skillDescriptionToken] = true;
				}
				if (stateType == typeof(ThrowGrenade))
				{
					tokens[allSkillDef.skillDescriptionToken] = false;
				}
			}
			if (!tokens.ContainsValue(value: true) || !tokens.ContainsValue(value: false))
			{
				throw new InvalidOperationException("Both native Commando alternate skill states must be present.");
			}
		}

		public static string Localize(string token, bool zh)
		{
			if (!tokens.TryGetValue(token, out var value))
			{
				return null;
			}
			return AlternateSkillText.Description(value, zh);
		}

		private static bool Supported(CharacterBody body)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			if (Stats.Applies(body) && Object.op_Implicit((Object)(object)body.teamComponent) && (int)body.teamComponent.teamIndex == 1)
			{
				return Object.op_Implicit((Object)(object)body.master);
			}
			return false;
		}

		private static BulletAttack Blast(orig_GenerateBulletAttack orig, GenericBulletBaseState self, Ray ray)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			BulletAttack val = orig.Invoke(self, ray);
			CharacterBody body = StateAccess.Body((EntityState)(object)self);
			if (self is FireShotgunBlast && Supported(body))
			{
				val.damage = Stats.SkillDamage(body, StateAccess.Damage((BaseState)(object)self), CombatModel.PhaseBlast, "PhaseBlast/pellet");
			}
			return val;
		}

		private static void Grenade(orig_ModifyProjectileInfo orig, ThrowGrenade self, ref FireProjectileInfo info)
		{
			orig.Invoke(self, ref info);
			CharacterBody body = StateAccess.Body((EntityState)(object)self);
			if (Supported(body))
			{
				info.damage = Stats.SkillDamage(body, StateAccess.Damage((BaseState)(object)self), CombatModel.Grenade, "Grenade/center");
			}
		}

		public static string Tooltip(GenericSkill skill)
		{
			CharacterBody val = (Object.op_Implicit((Object)(object)skill) ? skill.characterBody : null);
			if (!Supported(val) || !Object.op_Implicit((Object)(object)skill.skillDef))
			{
				return null;
			}
			Type stateType = ((SerializableEntityStateType)(ref skill.skillDef.activationState)).stateType;
			bool flag = stateType == typeof(FireShotgunBlast);
			if (!flag && stateType != typeof(ThrowGrenade))
			{
				return null;
			}
			SkillCoefficients skillCoefficients = (flag ? CombatModel.PhaseBlast : CombatModel.Grenade);
			DamageValues damageValues = Stats.Values(val);
			float damage = skillCoefficients.Damage(damageValues.BaseDamage, damageValues.AD, damageValues.AP) * Stats.ExistingDamageScale(val, val.damage);
			return AlternateSkillText.Append(Language.GetString(skill.skillDef.skillDescriptionToken), flag, Descriptions.IsChinese, damage);
		}
	}
	internal static class IndependentDamage
	{
		private static int samples;

		[ThreadStatic]
		private static CharacterBody gasBody;

		[ThreadStatic]
		private static DotIndex gasIndex;

		public static bool Supported(CharacterBody body, bool nativeAuthority = false)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Invalid comparison between Unknown and I4
			return CombatModel.Eligible(Plugin.Active, NetworkServer.active || nativeAuthority, Object.op_Implicit((Object)(object)body) && body.isPlayerControlled, Stats.IsCommando(body), Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)body.teamComponent) && (int)body.teamComponent.teamIndex == 1, Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)body.master));
		}

		public static float Read(CharacterBody body, float nativeDamage, string source, bool nativeAuthority = false)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!Supported(body, nativeAuthority))
			{
				return nativeDamage;
			}
			DamageValues values = Stats.Values(body);
			float num = CombatModel.Independent(values, Stats.ExistingDamageScale(body, nativeDamage));
			if (samples++ < 120)
			{
				Plugin.Log.LogInfo((object)$"[ADAP Independent] source={source} owner={((NetworkBehaviour)body.master).netId} Base={values.BaseDamage:F3} AD={values.AD:F3} AP={values.AP:F3} Neutral={CombatModel.Neutral(values):F3} native={nativeDamage:F3} effective={num:F3}");
			}
			return num;
		}

		public unsafe static float Dot(CharacterBody body, float nativeDamage, DotIndex index, float? total)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected I4, but got Unknown
			//IL_0011: 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)
			bool flag = total.HasValue && body == gasBody && index == gasIndex;
			if (!(CombatModel.IndependentDot((int)index, total.HasValue) || flag))
			{
				return nativeDamage;
			}
			return Read(body, nativeDamage, "Dot/" + ((object)(*(DotIndex*)(&index))/*cast due to .constrained prefix*/).ToString());
		}

		public static void GasBurn(ref InflictDotInfo info)
		{
			//IL_0079: 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)
			//IL_0051: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//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)
			CharacterBody body = (Object.op_Implicit((Object)(object)info.attackerObject) ? info.attackerObject.GetComponent<CharacterBody>() : null);
			bool flag = info.totalDamage.HasValue && Supported(body) && ((int)info.dotIndex == 1 || (int)info.dotIndex == 7);
			CharacterBody val = gasBody;
			DotIndex val2 = gasIndex;
			try
			{
				if (flag)
				{
					gasBody = body;
					gasIndex = info.dotIndex;
				}
				DotController.InflictDot(ref info);
			}
			finally
			{
				gasBody = val;
				gasIndex = val2;
			}
		}

		public static void Reset()
		{
			samples = 0;
		}
	}
	internal sealed class SourceAnchor
	{
		public readonly int Ordinal;

		public readonly string[] Before;

		public readonly string[] After;

		public SourceAnchor(int ordinal, string[] before, string[] after)
		{
			Ordinal = ordinal;
			Before = before;
			After = after;
		}
	}
	internal sealed class SourceSpec
	{
		public readonly string Key;

		public readonly string Method;

		public readonly int ExpectedReads;

		public readonly SourceRule Rule;

		public readonly int ReplacementCount;

		public readonly SourceAnchor[] Anchors;

		public readonly bool Dot;

		public readonly bool Gas;

		public readonly bool NativeAuthority;

		public SourceSpec(string key, string method, int reads, SourceRule rule, int replacements)
			: this(key, method, reads, rule == SourceRule.Dot, false, false)
		{
			Rule = rule;
			ReplacementCount = replacements;
		}

		public SourceSpec(string key, string method, int reads, bool dot, bool gas, bool nativeAuthority, params SourceAnchor[] anchors)
		{
			Key = key;
			Method = method;
			ExpectedReads = reads;
			Dot = dot;
			Gas = gas;
			NativeAuthority = nativeAuthority;
			Anchors = anchors;
			ReplacementCount = anchors.Length;
		}
	}
	internal static class CombatSourcePlan
	{
		public static readonly SourceSpec[] All = new SourceSpec[33]
		{
			new SourceSpec("Gasoline", "System.Void RoR2.GlobalEventManager::ProcIgniteOnKill(RoR2.DamageReport,System.Int32,RoR2.CharacterBody,RoR2.TeamIndex)", 2, false, true, false, new SourceAnchor(0, new string[2] { "ldarg.0", "ldfld RoR2.CharacterBody RoR2.DamageReport::attackerBody" }, new string[3] { "ldloc.2", "mul", "stloc.3" }), new SourceAnchor(1, new string[2] { "ldarg.0", "ldfld RoR2.CharacterBody RoR2.DamageReport::attackerBody" }, new string[3] { "mul", "stloc.s V_5", "ldc.i4.0" })),
			new SourceSpec("OnKill", "System.Void RoR2.GlobalEventManager::OnCharacterDeath(RoR2.DamageReport)", 4, SourceRule.OnKill, 3),
			new SourceSpec("StunAndPierce", "System.Void RoR2.GlobalEventManager::ProcessHitEnemy(RoR2.DamageInfo,UnityEngine.GameObject)", 15, SourceRule.StunAndPierce, 1),
			new SourceSpec("VoidsentFlame+Razorwire", "System.Void RoR2.HealthComponent::TakeDamageProcess(RoR2.DamageInfo)", 4, SourceRule.Retaliation, 2),
			new SourceSpec("ShieldBooster", "System.Single RoR2.HealthComponent::GetShieldBoosterDamage(System.Int32)", 1, false, false, false, new SourceAnchor(0, new string[2] { "ldarg.0", "ldfld RoR2.CharacterBody RoR2.HealthComponent::body" }, new string[3] { "ldarg.0", "ldfld RoR2.CharacterBody RoR2.HealthComponent::body", "callvirt System.Single RoR2.CharacterBody::get_maxShield()" })),
			new SourceSpec("Firework", "System.Void RoR2.FireworkLauncher::FireMissile()", 1, false, false, false, new SourceAnchor(0, new string[2] { "ldnull", "ldloc.0" }, new string[3] { "ldarg.0", "ldfld System.Single RoR2.FireworkLauncher::damageCoefficient", "mul" })),
			new SourceSpec("Shuriken", "System.Void RoR2.PrimarySkillShurikenBehavior::FireShuriken()", 1, SourceRule.Shuriken, 1),
			new SourceSpec("RunicLens", "System.Single RoR2.MeteorAttackOnHighDamageBodyBehavior::CalculateDamage(System.Int32,System.Int32)", 1, false, false, false, new SourceAnchor(0, new string[2] { "ldarg.0", "ldfld RoR2.CharacterBody RoR2.MeteorAttackOnHighDamageBodyBehavior::attackerBody" }, new string[3] { "ldloc.3", "mul", "ret" })),
			new SourceSpec("Tesla", "System.Void RoR2.Items.ShockNearbyBodyBehavior::FixedUpdate()", 1, false, false, false, new SourceAnchor(0, new string[2] { "ldarg.0", "call RoR2.CharacterBody RoR2.Items.BaseItemBodyBehavior::get_body()" }, new string[3] { "ldc.r4 2", "mul", "stfld System.Single RoR2.Orbs.LightningOrb::damageValue" })),
			new SourceSpec("LittleDisciple", "System.Void RoR2.Items.SprintWispBodyBehavior::Fire()", 1, false, false, false, new SourceAnchor(0, new