Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ADAPPrototype v0.0.1
plugins/ADAPPrototype/ADAPPrototype.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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