using System; using System.Collections; 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 System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("GooCombatOverhaul")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("GooCombatOverhaul")] [assembly: AssemblyTitle("GooCombatOverhaul")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace GooCombatOverhaul { [BepInPlugin("goo.valheim.gooscombatoverhaul", "Goo's Combat Overhaul", "1.4.0")] public sealed class GooCombatOverhaulPlugin : BaseUnityPlugin { private enum DamageBonusKind { Backstab, StaggeredEnemy, Flanking, CounterDamage } private enum ModFeature { Damage, Staggering, HyperArmor, CounterDamage, HitStop, AttackMovement, AttackRotation, HitRay, StealthAttributes, MultiTargetPenalty, Pvp, Resources, Blocking } private enum IncomingParryStaggerSource { None, Ranged, Aoe } private sealed class SoulsLikeBlockStaminaState { public readonly ItemData Blocker; public readonly float StaminaCost; public readonly float ValidUntil; public bool Consumed; public SoulsLikeBlockStaminaState(ItemData blocker, float staminaCost, float validUntil) { Blocker = blocker; StaminaCost = staminaCost; ValidUntil = validUntil; } } private sealed class SoulsLikeBlockBreakState { public readonly ItemData Blocker; public readonly float StaminaBefore; public readonly float StaminaCost; public readonly bool StaminaWillBeCleared; public readonly float ValidUntil; public SoulsLikeBlockBreakState(ItemData blocker, float staminaBefore, float staminaCost, bool staminaWillBeCleared, float validUntil) { Blocker = blocker; StaminaBefore = staminaBefore; StaminaCost = staminaCost; StaminaWillBeCleared = staminaWillBeCleared; ValidUntil = validUntil; } } private sealed class DirectSledgeHitPoint { public GameObject Go; public Collider Collider; public Vector3 Point; public float Distance; } internal sealed class GearDurabilityConfig { public ConfigEntry Unbreakable { get; } public ConfigEntry DurabilityConsumptionMultiplier { get; } public ConfigEntry BaseDurabilityMultiplier { get; } public ConfigEntry DurabilityMultiplier { get; } public GearDurabilityConfig(ConfigEntry unbreakable, ConfigEntry durabilityConsumptionMultiplier, ConfigEntry baseDurabilityMultiplier, ConfigEntry durabilityMultiplier) { Unbreakable = unbreakable; DurabilityConsumptionMultiplier = durabilityConsumptionMultiplier; BaseDurabilityMultiplier = baseDurabilityMultiplier; DurabilityMultiplier = durabilityMultiplier; } } public sealed class DurabilityPatchState { public readonly Dictionary Before = new Dictionary(); public DurabilityPatchState(IEnumerable items) { foreach (ItemData item in items) { if (item != null && item.m_shared != null && ItemUsesDurability(item) && !Before.ContainsKey(item)) { Before[item] = item.m_durability; } } } } public sealed class FoodFreezePatchState { public readonly List FoodSnapshots = new List(); public readonly Dictionary FoodRegenBefore = new Dictionary(); public bool HasWork { get { if (FoodSnapshots.Count <= 0) { return FoodRegenBefore.Count > 0; } return true; } } } public sealed class FoodSnapshot { public readonly Food Food; private readonly float _time; public FoodSnapshot(Food food) { Food = food; _time = food.m_time; } public void RestoreTimeAndRecalculateValues() { Food.m_time = _time; SharedData val = Food.m_item?.m_shared; if (val != null) { float num = 1f; if (val.m_foodBurnTime > 0f) { num = Mathf.Pow(Mathf.Clamp01(Food.m_time / val.m_foodBurnTime), 0.3f); } Food.m_health = val.m_food * num; Food.m_stamina = val.m_foodStamina * num; Food.m_eitr = val.m_foodEitr * num; } } } internal sealed class BlockedHitDamageTakenConfig { public ConfigEntry Generic { get; } public ConfigEntry Blunt { get; } public ConfigEntry Slash { get; } public ConfigEntry Pierce { get; } public ConfigEntry Chop { get; } public ConfigEntry Pickaxe { get; } public ConfigEntry Fire { get; } public ConfigEntry Frost { get; } public ConfigEntry Lightning { get; } public ConfigEntry Poison { get; } public ConfigEntry Spirit { get; } public BlockedHitDamageTakenConfig(ConfigEntry generic, ConfigEntry blunt, ConfigEntry slash, ConfigEntry pierce, ConfigEntry chop, ConfigEntry pickaxe, ConfigEntry fire, ConfigEntry frost, ConfigEntry lightning, ConfigEntry poison, ConfigEntry spirit) { Generic = generic; Blunt = blunt; Slash = slash; Pierce = pierce; Chop = chop; Pickaxe = pickaxe; Fire = fire; Frost = frost; Lightning = lightning; Poison = poison; Spirit = spirit; } } internal sealed class WeaponBlockExtraConfig { public ConfigEntry CanParry { get; } public ConfigEntry ParryBonusMultiplier { get; } public ConfigEntry ParryWindowTimeMultiplier { get; } public ConfigEntry PvpParryBonusMultiplier { get; } public ConfigEntry PvpBlockForceMultiplier { get; } public ConfigEntry PvpBlockBreakThresholdMultiplier { get; } public ConfigEntry SoulsLikeBlockBreak { get; } public ConfigEntry SoulsLikeBlockStaminaMode { get; } public ConfigEntry SoulsLikeBlockStaminaBaseRate { get; } public ConfigEntry KnockbackTakenWhileBlockingMultiplier { get; } public ConfigEntry BlockingMovementSpeedMultiplier { get; } public ConfigEntry EnableRunningWhileBlocking { get; } public ConfigEntry RunningWhileBlockingMovementSpeedMultiplier { get; } public WeaponBlockExtraConfig(ConfigEntry canParry, ConfigEntry parryBonusMultiplier, ConfigEntry parryWindowTimeMultiplier, ConfigEntry pvpParryBonusMultiplier, ConfigEntry pvpBlockForceMultiplier, ConfigEntry pvpBlockBreakThresholdMultiplier, ConfigEntry soulsLikeBlockBreak, ConfigEntry soulsLikeBlockStaminaMode, ConfigEntry soulsLikeBlockStaminaBaseRate, ConfigEntry knockbackTakenWhileBlockingMultiplier, ConfigEntry blockingMovementSpeedMultiplier, ConfigEntry enableRunningWhileBlocking, ConfigEntry runningWhileBlockingMovementSpeedMultiplier) { CanParry = canParry; ParryBonusMultiplier = parryBonusMultiplier; ParryWindowTimeMultiplier = parryWindowTimeMultiplier; PvpParryBonusMultiplier = pvpParryBonusMultiplier; PvpBlockForceMultiplier = pvpBlockForceMultiplier; PvpBlockBreakThresholdMultiplier = pvpBlockBreakThresholdMultiplier; SoulsLikeBlockBreak = soulsLikeBlockBreak; SoulsLikeBlockStaminaMode = soulsLikeBlockStaminaMode; SoulsLikeBlockStaminaBaseRate = soulsLikeBlockStaminaBaseRate; KnockbackTakenWhileBlockingMultiplier = knockbackTakenWhileBlockingMultiplier; BlockingMovementSpeedMultiplier = blockingMovementSpeedMultiplier; EnableRunningWhileBlocking = enableRunningWhileBlocking; RunningWhileBlockingMovementSpeedMultiplier = runningWhileBlockingMovementSpeedMultiplier; } } internal sealed class ShieldTypeBlockConfig { public ConfigEntry CanParry { get; } public ConfigEntry BlockArmorMultiplier { get; } public ConfigEntry BlockForceMultiplier { get; } public ConfigEntry ParryBonusMultiplier { get; } public ConfigEntry ParryWindowTimeMultiplier { get; } public ConfigEntry ParryAdrenalineMultiplier { get; } public ConfigEntry BlockStaminaConsumptionRate { get; } public ConfigEntry BlockingStaminaRegenMultiplier { get; } public ConfigEntry BlockBreakThresholdMultiplier { get; } public ConfigEntry PvpBlockArmorMultiplier { get; } public ConfigEntry PvpBlockForceMultiplier { get; } public ConfigEntry PvpParryBonusMultiplier { get; } public ConfigEntry PvpBlockBreakThresholdMultiplier { get; } public ConfigEntry SoulsLikeBlockBreak { get; } public ConfigEntry SoulsLikeBlockStaminaMode { get; } public ConfigEntry SoulsLikeBlockStaminaBaseRate { get; } public ConfigEntry KnockbackTakenWhileBlockingMultiplier { get; } public ConfigEntry MovementModifierPercent { get; } public ConfigEntry BlockingMovementSpeedMultiplier { get; } public ConfigEntry EnableRunningWhileBlocking { get; } public ConfigEntry RunningWhileBlockingMovementSpeedMultiplier { get; } public ShieldTypeBlockConfig(ConfigEntry canParry, ConfigEntry blockArmorMultiplier, ConfigEntry blockForceMultiplier, ConfigEntry parryBonusMultiplier, ConfigEntry parryWindowTimeMultiplier, ConfigEntry parryAdrenalineMultiplier, ConfigEntry blockStaminaConsumptionRate, ConfigEntry blockingStaminaRegenMultiplier, ConfigEntry blockBreakThresholdMultiplier, ConfigEntry pvpBlockArmorMultiplier, ConfigEntry pvpBlockForceMultiplier, ConfigEntry pvpParryBonusMultiplier, ConfigEntry pvpBlockBreakThresholdMultiplier, ConfigEntry soulsLikeBlockBreak, ConfigEntry soulsLikeBlockStaminaMode, ConfigEntry soulsLikeBlockStaminaBaseRate, ConfigEntry knockbackTakenWhileBlockingMultiplier, ConfigEntry movementModifierPercent, ConfigEntry blockingMovementSpeedMultiplier, ConfigEntry enableRunningWhileBlocking, ConfigEntry runningWhileBlockingMovementSpeedMultiplier) { CanParry = canParry; BlockArmorMultiplier = blockArmorMultiplier; BlockForceMultiplier = blockForceMultiplier; ParryBonusMultiplier = parryBonusMultiplier; ParryWindowTimeMultiplier = parryWindowTimeMultiplier; ParryAdrenalineMultiplier = parryAdrenalineMultiplier; BlockStaminaConsumptionRate = blockStaminaConsumptionRate; BlockingStaminaRegenMultiplier = blockingStaminaRegenMultiplier; BlockBreakThresholdMultiplier = blockBreakThresholdMultiplier; PvpBlockArmorMultiplier = pvpBlockArmorMultiplier; PvpBlockForceMultiplier = pvpBlockForceMultiplier; PvpParryBonusMultiplier = pvpParryBonusMultiplier; PvpBlockBreakThresholdMultiplier = pvpBlockBreakThresholdMultiplier; SoulsLikeBlockBreak = soulsLikeBlockBreak; SoulsLikeBlockStaminaMode = soulsLikeBlockStaminaMode; SoulsLikeBlockStaminaBaseRate = soulsLikeBlockStaminaBaseRate; KnockbackTakenWhileBlockingMultiplier = knockbackTakenWhileBlockingMultiplier; MovementModifierPercent = movementModifierPercent; BlockingMovementSpeedMultiplier = blockingMovementSpeedMultiplier; EnableRunningWhileBlocking = enableRunningWhileBlocking; RunningWhileBlockingMovementSpeedMultiplier = runningWhileBlockingMovementSpeedMultiplier; } } internal sealed class StaffBlockConfig { public ConfigEntry CanParry { get; } public ConfigEntry BlockArmorMultiplier { get; } public ConfigEntry BlockForceMultiplier { get; } public ConfigEntry ParryBonusMultiplier { get; } public ConfigEntry ParryWindowTimeMultiplier { get; } public ConfigEntry ParryAdrenalineMultiplier { get; } public ConfigEntry BlockStaminaConsumptionRate { get; } public ConfigEntry BlockingStaminaRegenMultiplier { get; } public ConfigEntry BlockBreakThresholdMultiplier { get; } public ConfigEntry PvpBlockArmorMultiplier { get; } public ConfigEntry PvpBlockForceMultiplier { get; } public ConfigEntry PvpParryBonusMultiplier { get; } public ConfigEntry PvpBlockBreakThresholdMultiplier { get; } public ConfigEntry SoulsLikeBlockBreak { get; } public ConfigEntry SoulsLikeBlockStaminaMode { get; } public ConfigEntry SoulsLikeBlockStaminaBaseRate { get; } public ConfigEntry KnockbackTakenWhileBlockingMultiplier { get; } public ConfigEntry EnableRunningWhileBlocking { get; } public ConfigEntry RunningWhileBlockingMovementSpeedMultiplier { get; } public ConfigEntry AttackMovementModifier { get; } public ConfigEntry AttackRotationFactor { get; } public StaffBlockConfig(ConfigEntry canParry, ConfigEntry blockArmorMultiplier, ConfigEntry blockForceMultiplier, ConfigEntry parryBonusMultiplier, ConfigEntry parryWindowTimeMultiplier, ConfigEntry parryAdrenalineMultiplier, ConfigEntry blockStaminaConsumptionRate, ConfigEntry blockingStaminaRegenMultiplier, ConfigEntry blockBreakThresholdMultiplier, ConfigEntry pvpBlockArmorMultiplier, ConfigEntry pvpBlockForceMultiplier, ConfigEntry pvpParryBonusMultiplier, ConfigEntry pvpBlockBreakThresholdMultiplier, ConfigEntry soulsLikeBlockBreak, ConfigEntry soulsLikeBlockStaminaMode, ConfigEntry soulsLikeBlockStaminaBaseRate, ConfigEntry knockbackTakenWhileBlockingMultiplier, ConfigEntry enableRunningWhileBlocking, ConfigEntry runningWhileBlockingMovementSpeedMultiplier, ConfigEntry attackMovementModifier, ConfigEntry attackRotationFactor) { CanParry = canParry; BlockArmorMultiplier = blockArmorMultiplier; BlockForceMultiplier = blockForceMultiplier; ParryBonusMultiplier = parryBonusMultiplier; ParryWindowTimeMultiplier = parryWindowTimeMultiplier; ParryAdrenalineMultiplier = parryAdrenalineMultiplier; BlockStaminaConsumptionRate = blockStaminaConsumptionRate; BlockingStaminaRegenMultiplier = blockingStaminaRegenMultiplier; BlockBreakThresholdMultiplier = blockBreakThresholdMultiplier; PvpBlockArmorMultiplier = pvpBlockArmorMultiplier; PvpBlockForceMultiplier = pvpBlockForceMultiplier; PvpParryBonusMultiplier = pvpParryBonusMultiplier; PvpBlockBreakThresholdMultiplier = pvpBlockBreakThresholdMultiplier; SoulsLikeBlockBreak = soulsLikeBlockBreak; SoulsLikeBlockStaminaMode = soulsLikeBlockStaminaMode; SoulsLikeBlockStaminaBaseRate = soulsLikeBlockStaminaBaseRate; KnockbackTakenWhileBlockingMultiplier = knockbackTakenWhileBlockingMultiplier; EnableRunningWhileBlocking = enableRunningWhileBlocking; RunningWhileBlockingMovementSpeedMultiplier = runningWhileBlockingMovementSpeedMultiplier; AttackMovementModifier = attackMovementModifier; AttackRotationFactor = attackRotationFactor; } } internal sealed class StaffShieldActiveConfig { public ConfigEntry HealthConsumptionRate { get; } public ConfigEntry StaggerMitigationMultiplier { get; } public ConfigEntry PvpStaggerMitigationMultiplier { get; } public ConfigEntry KnockbackMitigationMultiplier { get; } public ConfigEntry PvpKnockbackMitigationMultiplier { get; } public ConfigEntry ActiveParryBonusMultiplier { get; } public StaffShieldActiveConfig(ConfigEntry healthConsumptionRate, ConfigEntry staggerMitigationMultiplier, ConfigEntry pvpStaggerMitigationMultiplier, ConfigEntry knockbackMitigationMultiplier, ConfigEntry pvpKnockbackMitigationMultiplier, ConfigEntry activeParryBonusMultiplier) { HealthConsumptionRate = healthConsumptionRate; StaggerMitigationMultiplier = staggerMitigationMultiplier; PvpStaggerMitigationMultiplier = pvpStaggerMitigationMultiplier; KnockbackMitigationMultiplier = knockbackMitigationMultiplier; PvpKnockbackMitigationMultiplier = pvpKnockbackMitigationMultiplier; ActiveParryBonusMultiplier = activeParryBonusMultiplier; } } internal sealed class StaffConfig { public string PrefabName { get; } public bool DirectAttack { get; } public ConfigEntry? DamageMultiplier { get; } public ConfigEntry? StaggerMultiplier { get; } public ConfigEntry AttackAnimationSpeedMultiplier { get; } public ConfigEntry EitrRateMultiplier { get; } public ConfigEntry AttackStartNoiseMultiplier { get; } public ConfigEntry AttackHitNoiseMultiplier { get; } public ConfigEntry BackstabDamageMultiplier { get; } public ConfigEntry? PvpDamageMultiplier { get; } public ConfigEntry? PvpStaggerMultiplier { get; } public ConfigEntry? PvpKnockbackForceMultiplier { get; } public ConfigEntry? ProjectileSpeedMultiplier { get; } public ConfigEntry? AoeRadiusMultiplier { get; } public ConfigEntry? SpreadMultiplier { get; } public ConfigEntry? ShieldHealthMultiplier { get; } public ConfigEntry? DamageTakenFromPlayersMultiplier { get; } public StaffConfig(string prefabName, bool directAttack, ConfigEntry? damageMultiplier, ConfigEntry? staggerMultiplier, ConfigEntry attackAnimationSpeedMultiplier, ConfigEntry eitrRateMultiplier, ConfigEntry attackStartNoiseMultiplier, ConfigEntry attackHitNoiseMultiplier, ConfigEntry backstabDamageMultiplier, ConfigEntry? pvpDamageMultiplier, ConfigEntry? pvpStaggerMultiplier, ConfigEntry? pvpKnockbackForceMultiplier, ConfigEntry? projectileSpeedMultiplier, ConfigEntry? aoeRadiusMultiplier, ConfigEntry? spreadMultiplier, ConfigEntry? shieldHealthMultiplier, ConfigEntry? damageTakenFromPlayersMultiplier) { PrefabName = prefabName; DirectAttack = directAttack; DamageMultiplier = damageMultiplier; StaggerMultiplier = staggerMultiplier; AttackAnimationSpeedMultiplier = attackAnimationSpeedMultiplier; EitrRateMultiplier = eitrRateMultiplier; AttackStartNoiseMultiplier = attackStartNoiseMultiplier; AttackHitNoiseMultiplier = attackHitNoiseMultiplier; BackstabDamageMultiplier = backstabDamageMultiplier; PvpDamageMultiplier = pvpDamageMultiplier; PvpStaggerMultiplier = pvpStaggerMultiplier; PvpKnockbackForceMultiplier = pvpKnockbackForceMultiplier; ProjectileSpeedMultiplier = projectileSpeedMultiplier; AoeRadiusMultiplier = aoeRadiusMultiplier; SpreadMultiplier = spreadMultiplier; ShieldHealthMultiplier = shieldHealthMultiplier; DamageTakenFromPlayersMultiplier = damageTakenFromPlayersMultiplier; } } internal sealed class Setting { private readonly ConfigEntry? _entry; private T _value; public T Value { get { if (_entry == null) { return _value; } return _entry.Value; } set { if (_entry != null) { _entry.Value = value; } else { _value = value; } } } public T DefaultValue { get { if (_entry == null) { return _value; } return (T)((ConfigEntryBase)_entry).DefaultValue; } } public Setting(ConfigEntry entry) { _entry = entry; _value = entry.Value; } public Setting(T value) { _entry = null; _value = value; } } internal sealed class RoleSettings { public Setting Primary1 { get; } public Setting Primary2 { get; } public Setting Primary3 { get; } public Setting Primary4 { get; } public Setting Secondary { get; } public RoleSettings(Setting primary1, Setting primary2, Setting primary3, Setting secondary, Setting? primary4 = null) { Primary1 = primary1; Primary2 = primary2; Primary3 = primary3; Primary4 = primary4 ?? primary3; Secondary = secondary; } public Setting Get(AttackRole role) { return role switch { AttackRole.Primary2 => Primary2, AttackRole.Primary3 => Primary3, AttackRole.Primary4 => Primary4, AttackRole.Secondary => Secondary, _ => Primary1, }; } public void SetPrimary(T value) { Primary1.Value = value; Primary2.Value = value; Primary3.Value = value; Primary4.Value = value; } public void SetAll(T primary, T secondary) { SetPrimary(primary); Secondary.Value = secondary; } } internal sealed class RunningAttackConfig { public ConfigEntry Enabled { get; } public ConfigEntry MinimumVelocity { get; } public ConfigEntry UsedAttack { get; } public ConfigEntry DamageMultiplier { get; } public ConfigEntry StaggerMultiplier { get; } public ConfigEntry StaminaRateMultiplier { get; } public ConfigEntry AnimationSpeedMultiplier { get; } public ConfigEntry RecoveryAnimationSpeedMultiplier { get; } public ConfigEntry BlockCancelMode { get; } public ConfigEntry BlockCancelAnimationSpeedMultiplier { get; } public ConfigEntry HyperArmorMode { get; } public ConfigEntry DamageTakenMultiplier { get; } public ConfigEntry StaggerTakenMultiplier { get; } public ConfigEntry KnockbackTakenMultiplier { get; } public ConfigEntry PvpDamageTakenMultiplier { get; } public ConfigEntry PvpStaggerTakenMultiplier { get; } public ConfigEntry PvpKnockbackTakenMultiplier { get; } public ConfigEntry CounterDamageEnabled { get; } public ConfigEntry CounterDamageMultiplier { get; } public ConfigEntry PvpCounterDamageMultiplier { get; } public ConfigEntry HitStopDurationMultiplier { get; } public ConfigEntry PvpHitStopDurationMultiplier { get; } public ConfigEntry ChopDamageMultiplier { get; } public ConfigEntry PickaxeDamageMultiplier { get; } public ConfigEntry BackstabDamageMultiplier { get; } public ConfigEntry MovementSpeedMultiplier { get; } public ConfigEntry MovementApplicationMode { get; } public ConfigEntry RotationFactor { get; } public ConfigEntry LockRotationAfterAttackTrigger { get; } public ConfigEntry PvpDamageMultiplier { get; } public ConfigEntry PvpStaggerMultiplier { get; } public ConfigEntry PvpStaggerDurationSeconds { get; } public ConfigEntry PvpKnockbackForceMultiplier { get; } public ConfigEntry KnockbackForceMultiplier { get; } public ConfigEntry AdrenalineMultiplier { get; } public ConfigEntry LungeDistanceMultiplier { get; } public ConfigEntry LungeApplicationMode { get; } public ConfigEntry StartNoiseMultiplier { get; } public ConfigEntry HitNoiseMultiplier { get; } public ConfigEntry RangeMultiplier { get; } public ConfigEntry RayWidthMultiplier { get; } public ConfigEntry HitboxHeightMultiplier { get; } public ConfigEntry OffsetMultiplier { get; } public ConfigEntry HitboxType { get; } public ConfigEntry AngleMultiplier { get; } public ConfigEntry MultiTargetDamagePenalty { get; } public RunningAttackConfig(ConfigEntry enabled, ConfigEntry minimumVelocity, ConfigEntry usedAttack, ConfigEntry damageMultiplier, ConfigEntry staggerMultiplier, ConfigEntry staminaRateMultiplier, ConfigEntry animationSpeedMultiplier, ConfigEntry recoveryAnimationSpeedMultiplier, ConfigEntry blockCancelMode, ConfigEntry blockCancelAnimationSpeedMultiplier, ConfigEntry hyperArmorMode, ConfigEntry damageTakenMultiplier, ConfigEntry staggerTakenMultiplier, ConfigEntry knockbackTakenMultiplier, ConfigEntry pvpDamageTakenMultiplier, ConfigEntry pvpStaggerTakenMultiplier, ConfigEntry pvpKnockbackTakenMultiplier, ConfigEntry counterDamageEnabled, ConfigEntry counterDamageMultiplier, ConfigEntry pvpCounterDamageMultiplier, ConfigEntry hitStopDurationMultiplier, ConfigEntry pvpHitStopDurationMultiplier, ConfigEntry chopDamageMultiplier, ConfigEntry pickaxeDamageMultiplier, ConfigEntry backstabDamageMultiplier, ConfigEntry movementSpeedMultiplier, ConfigEntry movementApplicationMode, ConfigEntry rotationFactor, ConfigEntry lockRotationAfterAttackTrigger, ConfigEntry pvpDamageMultiplier, ConfigEntry pvpStaggerMultiplier, ConfigEntry pvpStaggerDurationSeconds, ConfigEntry pvpKnockbackForceMultiplier, ConfigEntry knockbackForceMultiplier, ConfigEntry adrenalineMultiplier, ConfigEntry lungeDistanceMultiplier, ConfigEntry lungeApplicationMode, ConfigEntry startNoiseMultiplier, ConfigEntry hitNoiseMultiplier, ConfigEntry rangeMultiplier, ConfigEntry rayWidthMultiplier, ConfigEntry hitboxHeightMultiplier, ConfigEntry offsetMultiplier, ConfigEntry hitboxType, ConfigEntry angleMultiplier, ConfigEntry multiTargetDamagePenalty) { Enabled = enabled; MinimumVelocity = minimumVelocity; UsedAttack = usedAttack; DamageMultiplier = damageMultiplier; StaggerMultiplier = staggerMultiplier; StaminaRateMultiplier = staminaRateMultiplier; AnimationSpeedMultiplier = animationSpeedMultiplier; RecoveryAnimationSpeedMultiplier = recoveryAnimationSpeedMultiplier; BlockCancelMode = blockCancelMode; BlockCancelAnimationSpeedMultiplier = blockCancelAnimationSpeedMultiplier; HyperArmorMode = hyperArmorMode; DamageTakenMultiplier = damageTakenMultiplier; StaggerTakenMultiplier = staggerTakenMultiplier; KnockbackTakenMultiplier = knockbackTakenMultiplier; PvpDamageTakenMultiplier = pvpDamageTakenMultiplier; PvpStaggerTakenMultiplier = pvpStaggerTakenMultiplier; PvpKnockbackTakenMultiplier = pvpKnockbackTakenMultiplier; CounterDamageEnabled = counterDamageEnabled; CounterDamageMultiplier = counterDamageMultiplier; PvpCounterDamageMultiplier = pvpCounterDamageMultiplier; HitStopDurationMultiplier = hitStopDurationMultiplier; PvpHitStopDurationMultiplier = pvpHitStopDurationMultiplier; ChopDamageMultiplier = chopDamageMultiplier; PickaxeDamageMultiplier = pickaxeDamageMultiplier; BackstabDamageMultiplier = backstabDamageMultiplier; MovementSpeedMultiplier = movementSpeedMultiplier; MovementApplicationMode = movementApplicationMode; RotationFactor = rotationFactor; LockRotationAfterAttackTrigger = lockRotationAfterAttackTrigger; PvpDamageMultiplier = pvpDamageMultiplier; PvpStaggerMultiplier = pvpStaggerMultiplier; PvpStaggerDurationSeconds = pvpStaggerDurationSeconds; PvpKnockbackForceMultiplier = pvpKnockbackForceMultiplier; KnockbackForceMultiplier = knockbackForceMultiplier; AdrenalineMultiplier = adrenalineMultiplier; LungeDistanceMultiplier = lungeDistanceMultiplier; LungeApplicationMode = lungeApplicationMode; StartNoiseMultiplier = startNoiseMultiplier; HitNoiseMultiplier = hitNoiseMultiplier; RangeMultiplier = rangeMultiplier; RayWidthMultiplier = rayWidthMultiplier; HitboxHeightMultiplier = hitboxHeightMultiplier; OffsetMultiplier = offsetMultiplier; HitboxType = hitboxType; AngleMultiplier = angleMultiplier; MultiTargetDamagePenalty = multiTargetDamagePenalty; } } internal sealed class JumpAttackUsedSetting { private readonly WeaponCategory _category; private readonly ConfigEntry? _fullEntry; private readonly ConfigEntry? _basicEntry; public JumpAttackUsed Value { get { if (_fullEntry != null) { return NormalizeJumpAttackUsed(_category, _fullEntry.Value); } if (_basicEntry == null) { return DefaultJumpAttackUsed(_category); } return ToFullJumpAttackUsed(_basicEntry.Value); } set { JumpAttackUsed jumpAttackUsed = NormalizeJumpAttackUsed(_category, value); if (_fullEntry != null) { _fullEntry.Value = jumpAttackUsed; } else if (_basicEntry != null) { _basicEntry.Value = ToBasicJumpAttackUsed(jumpAttackUsed); } } } public JumpAttackUsedSetting(WeaponCategory category, ConfigEntry fullEntry) { _category = category; _fullEntry = fullEntry; } public JumpAttackUsedSetting(WeaponCategory category, ConfigEntry basicEntry) { _category = category; _basicEntry = basicEntry; } } internal sealed class JumpAttackConfig { public ConfigEntry Enabled { get; } public JumpAttackUsedSetting UsedAttack { get; } public ConfigEntry DamageMultiplier { get; } public ConfigEntry StaggerMultiplier { get; } public ConfigEntry StaminaRateMultiplier { get; } public ConfigEntry AnimationSpeedMultiplier { get; } public ConfigEntry RecoveryAnimationSpeedMultiplier { get; } public ConfigEntry BlockCancelMode { get; } public ConfigEntry BlockCancelAnimationSpeedMultiplier { get; } public ConfigEntry HyperArmorMode { get; } public ConfigEntry DamageTakenMultiplier { get; } public ConfigEntry StaggerTakenMultiplier { get; } public ConfigEntry KnockbackTakenMultiplier { get; } public ConfigEntry PvpDamageTakenMultiplier { get; } public ConfigEntry PvpStaggerTakenMultiplier { get; } public ConfigEntry PvpKnockbackTakenMultiplier { get; } public ConfigEntry CounterDamageEnabled { get; } public ConfigEntry CounterDamageMultiplier { get; } public ConfigEntry PvpCounterDamageMultiplier { get; } public ConfigEntry HitStopDurationMultiplier { get; } public ConfigEntry PvpHitStopDurationMultiplier { get; } public ConfigEntry ChopDamageMultiplier { get; } public ConfigEntry PickaxeDamageMultiplier { get; } public ConfigEntry BackstabDamageMultiplier { get; } public ConfigEntry MovementSpeedMultiplier { get; } public ConfigEntry MovementApplicationMode { get; } public ConfigEntry RotationFactor { get; } public ConfigEntry LockRotationAfterAttackTrigger { get; } public ConfigEntry PvpDamageMultiplier { get; } public ConfigEntry PvpStaggerMultiplier { get; } public ConfigEntry PvpStaggerDurationSeconds { get; } public ConfigEntry PvpKnockbackForceMultiplier { get; } public ConfigEntry KnockbackForceMultiplier { get; } public ConfigEntry AdrenalineMultiplier { get; } public ConfigEntry LungeDistanceMultiplier { get; } public ConfigEntry LungeApplicationMode { get; } public ConfigEntry StartNoiseMultiplier { get; } public ConfigEntry HitNoiseMultiplier { get; } public ConfigEntry RangeMultiplier { get; } public ConfigEntry RayWidthMultiplier { get; } public ConfigEntry HitboxHeightMultiplier { get; } public ConfigEntry OffsetMultiplier { get; } public ConfigEntry HitboxType { get; } public ConfigEntry AngleMultiplier { get; } public ConfigEntry MultiTargetDamagePenalty { get; } public JumpAttackConfig(ConfigEntry enabled, JumpAttackUsedSetting usedAttack, ConfigEntry damageMultiplier, ConfigEntry staggerMultiplier, ConfigEntry staminaRateMultiplier, ConfigEntry animationSpeedMultiplier, ConfigEntry recoveryAnimationSpeedMultiplier, ConfigEntry blockCancelMode, ConfigEntry blockCancelAnimationSpeedMultiplier, ConfigEntry hyperArmorMode, ConfigEntry damageTakenMultiplier, ConfigEntry staggerTakenMultiplier, ConfigEntry knockbackTakenMultiplier, ConfigEntry pvpDamageTakenMultiplier, ConfigEntry pvpStaggerTakenMultiplier, ConfigEntry pvpKnockbackTakenMultiplier, ConfigEntry counterDamageEnabled, ConfigEntry counterDamageMultiplier, ConfigEntry pvpCounterDamageMultiplier, ConfigEntry hitStopDurationMultiplier, ConfigEntry pvpHitStopDurationMultiplier, ConfigEntry chopDamageMultiplier, ConfigEntry pickaxeDamageMultiplier, ConfigEntry backstabDamageMultiplier, ConfigEntry movementSpeedMultiplier, ConfigEntry movementApplicationMode, ConfigEntry rotationFactor, ConfigEntry lockRotationAfterAttackTrigger, ConfigEntry pvpDamageMultiplier, ConfigEntry pvpStaggerMultiplier, ConfigEntry pvpStaggerDurationSeconds, ConfigEntry pvpKnockbackForceMultiplier, ConfigEntry knockbackForceMultiplier, ConfigEntry adrenalineMultiplier, ConfigEntry lungeDistanceMultiplier, ConfigEntry lungeApplicationMode, ConfigEntry startNoiseMultiplier, ConfigEntry hitNoiseMultiplier, ConfigEntry rangeMultiplier, ConfigEntry rayWidthMultiplier, ConfigEntry hitboxHeightMultiplier, ConfigEntry offsetMultiplier, ConfigEntry hitboxType, ConfigEntry angleMultiplier, ConfigEntry multiTargetDamagePenalty) { Enabled = enabled; UsedAttack = usedAttack; DamageMultiplier = damageMultiplier; StaggerMultiplier = staggerMultiplier; StaminaRateMultiplier = staminaRateMultiplier; AnimationSpeedMultiplier = animationSpeedMultiplier; RecoveryAnimationSpeedMultiplier = recoveryAnimationSpeedMultiplier; BlockCancelMode = blockCancelMode; BlockCancelAnimationSpeedMultiplier = blockCancelAnimationSpeedMultiplier; HyperArmorMode = hyperArmorMode; DamageTakenMultiplier = damageTakenMultiplier; StaggerTakenMultiplier = staggerTakenMultiplier; KnockbackTakenMultiplier = knockbackTakenMultiplier; PvpDamageTakenMultiplier = pvpDamageTakenMultiplier; PvpStaggerTakenMultiplier = pvpStaggerTakenMultiplier; PvpKnockbackTakenMultiplier = pvpKnockbackTakenMultiplier; CounterDamageEnabled = counterDamageEnabled; CounterDamageMultiplier = counterDamageMultiplier; PvpCounterDamageMultiplier = pvpCounterDamageMultiplier; HitStopDurationMultiplier = hitStopDurationMultiplier; PvpHitStopDurationMultiplier = pvpHitStopDurationMultiplier; ChopDamageMultiplier = chopDamageMultiplier; PickaxeDamageMultiplier = pickaxeDamageMultiplier; BackstabDamageMultiplier = backstabDamageMultiplier; MovementSpeedMultiplier = movementSpeedMultiplier; MovementApplicationMode = movementApplicationMode; RotationFactor = rotationFactor; LockRotationAfterAttackTrigger = lockRotationAfterAttackTrigger; PvpDamageMultiplier = pvpDamageMultiplier; PvpStaggerMultiplier = pvpStaggerMultiplier; PvpStaggerDurationSeconds = pvpStaggerDurationSeconds; PvpKnockbackForceMultiplier = pvpKnockbackForceMultiplier; KnockbackForceMultiplier = knockbackForceMultiplier; AdrenalineMultiplier = adrenalineMultiplier; LungeDistanceMultiplier = lungeDistanceMultiplier; LungeApplicationMode = lungeApplicationMode; StartNoiseMultiplier = startNoiseMultiplier; HitNoiseMultiplier = hitNoiseMultiplier; RangeMultiplier = rangeMultiplier; RayWidthMultiplier = rayWidthMultiplier; HitboxHeightMultiplier = hitboxHeightMultiplier; OffsetMultiplier = offsetMultiplier; HitboxType = hitboxType; AngleMultiplier = angleMultiplier; MultiTargetDamagePenalty = multiTargetDamagePenalty; } } internal sealed class CategoryConfig { public Setting BaseDamageMultiplier { get; } public RoleSettings StaggerMode { get; } public RoleSettings StaggerPowerValue { get; } public RoleSettings DamageToStaggeredEnemyMultiplier { get; } public RoleSettings HyperArmorMode { get; } public RoleSettings DamageTakenMultiplier { get; } public RoleSettings StaggerTakenMultiplier { get; } public RoleSettings KnockbackTakenMultiplier { get; } public RoleSettings PvpDamageTakenMultiplierDuringHyperArmor { get; } public RoleSettings PvpStaggerTakenMultiplierDuringHyperArmor { get; } public RoleSettings PvpKnockbackTakenMultiplierDuringHyperArmor { get; } public RoleSettings HitStopDurationMultiplier { get; } public RoleSettings PvpHitStopDurationMultiplier { get; } public RoleSettings CounterDamageEnabled { get; } public RoleSettings CounterDamageMultiplier { get; } public RoleSettings PvpCounterDamageMultiplier { get; } public RoleSettings DamageRateMultiplier { get; } public RoleSettings ChopDamageMultiplier { get; } public RoleSettings PickaxeDamageMultiplier { get; } public RoleSettings AirborneDamageMultiplier { get; } public RoleSettings AirborneStaggerMultiplier { get; } public RoleSettings AirborneRotationFactor { get; } public RoleSettings StaminaRateMultiplier { get; } public RoleSettings EitrRateMultiplier { get; } public RoleSettings AttackMovementModifier { get; } public RoleSettings AttackMovementApplicationMode { get; } public RoleSettings LungeDistanceMultiplier { get; } public RoleSettings LungeApplicationMode { get; } public RoleSettings AttackAnimationSpeedMultiplier { get; } public RoleSettings RecoveryAnimationSpeedMultiplier { get; } public RoleSettings AttackRotationFactor { get; } public RoleSettings LockRotationAfterAttackTrigger { get; } public RoleSettings AdrenalineMultiplier { get; } public RoleSettings PvpDamageMultiplier { get; } public RoleSettings PvpStaggerMultiplier { get; } public RoleSettings PvpStaggerDurationSeconds { get; } public RoleSettings PvpKnockbackForceMultiplier { get; } public RoleSettings KnockbackForceMultiplier { get; } public Setting PvpBlockArmorMultiplier { get; } public Setting BlockArmorMultiplier { get; } public Setting BlockForceMultiplier { get; } public Setting BlockStaminaConsumptionRate { get; } public Setting BlockingStaminaRegenMultiplier { get; } public Setting BlockBreakThresholdMultiplier { get; } public RoleSettings FullAttackDuration { get; } public RoleSettings BlockCancelMode { get; } public RoleSettings BlockCancelAnimationSpeedMultiplier { get; } public WeaponCategory Category { get; } public Setting PrimaryStaggerMode => StaggerMode.Primary1; public Setting PrimaryStaggerPowerValue => StaggerPowerValue.Primary1; public Setting SecondaryStaggerMode => StaggerMode.Secondary; public Setting SecondaryStaggerPowerValue => StaggerPowerValue.Secondary; public Setting PrimaryHyperArmorMode => HyperArmorMode.Primary1; public Setting SecondaryHyperArmorMode => HyperArmorMode.Secondary; public Setting PrimaryDamageTakenMultiplier => DamageTakenMultiplier.Primary1; public Setting SecondaryDamageTakenMultiplier => DamageTakenMultiplier.Secondary; public Setting PrimaryStaggerTakenMultiplier => StaggerTakenMultiplier.Primary1; public Setting SecondaryStaggerTakenMultiplier => StaggerTakenMultiplier.Secondary; public Setting PrimaryKnockbackTakenMultiplier => KnockbackTakenMultiplier.Primary1; public Setting SecondaryKnockbackTakenMultiplier => KnockbackTakenMultiplier.Secondary; public Setting PrimaryPvpDamageTakenMultiplierDuringHyperArmor => PvpDamageTakenMultiplierDuringHyperArmor.Primary1; public Setting SecondaryPvpDamageTakenMultiplierDuringHyperArmor => PvpDamageTakenMultiplierDuringHyperArmor.Secondary; public Setting PrimaryPvpStaggerTakenMultiplierDuringHyperArmor => PvpStaggerTakenMultiplierDuringHyperArmor.Primary1; public Setting SecondaryPvpStaggerTakenMultiplierDuringHyperArmor => PvpStaggerTakenMultiplierDuringHyperArmor.Secondary; public Setting PrimaryPvpKnockbackTakenMultiplierDuringHyperArmor => PvpKnockbackTakenMultiplierDuringHyperArmor.Primary1; public Setting SecondaryPvpKnockbackTakenMultiplierDuringHyperArmor => PvpKnockbackTakenMultiplierDuringHyperArmor.Secondary; public Setting PrimaryCounterDamageEnabled => CounterDamageEnabled.Primary1; public Setting PrimaryCounterDamageMultiplier => CounterDamageMultiplier.Primary1; public Setting SecondaryCounterDamageEnabled => CounterDamageEnabled.Secondary; public Setting SecondaryCounterDamageMultiplier => CounterDamageMultiplier.Secondary; public Setting PrimaryPvpCounterDamageMultiplier => PvpCounterDamageMultiplier.Primary1; public Setting SecondaryPvpCounterDamageMultiplier => PvpCounterDamageMultiplier.Secondary; public Setting PrimaryDamageRateMultiplier => DamageRateMultiplier.Primary1; public Setting SecondaryDamageRateMultiplier => DamageRateMultiplier.Secondary; public Setting PrimaryChopDamageMultiplier => ChopDamageMultiplier.Primary1; public Setting SecondaryChopDamageMultiplier => ChopDamageMultiplier.Secondary; public Setting PrimaryPickaxeDamageMultiplier => PickaxeDamageMultiplier.Primary1; public Setting SecondaryPickaxeDamageMultiplier => PickaxeDamageMultiplier.Secondary; public Setting PrimaryAirborneDamageMultiplier => AirborneDamageMultiplier.Primary1; public Setting SecondaryAirborneDamageMultiplier => AirborneDamageMultiplier.Secondary; public Setting PrimaryAirborneStaggerMultiplier => AirborneStaggerMultiplier.Primary1; public Setting SecondaryAirborneStaggerMultiplier => AirborneStaggerMultiplier.Secondary; public Setting PrimaryAirborneRotationFactor => AirborneRotationFactor.Primary1; public Setting SecondaryAirborneRotationFactor => AirborneRotationFactor.Secondary; public Setting PrimaryStaminaRateMultiplier => StaminaRateMultiplier.Primary1; public Setting SecondaryStaminaRateMultiplier => StaminaRateMultiplier.Secondary; public Setting PrimaryEitrRateMultiplier => EitrRateMultiplier.Primary1; public Setting SecondaryEitrRateMultiplier => EitrRateMultiplier.Secondary; public Setting PrimaryAttackMovementModifier => AttackMovementModifier.Primary1; public Setting SecondaryAttackMovementModifier => AttackMovementModifier.Secondary; public Setting PrimaryLungeDistanceMultiplier => LungeDistanceMultiplier.Primary1; public Setting SecondaryLungeDistanceMultiplier => LungeDistanceMultiplier.Secondary; public Setting PrimaryAttackAnimationSpeedMultiplier => AttackAnimationSpeedMultiplier.Primary1; public Setting SecondaryAttackAnimationSpeedMultiplier => AttackAnimationSpeedMultiplier.Secondary; public Setting PrimaryRecoveryAnimationSpeedMultiplier => RecoveryAnimationSpeedMultiplier.Primary1; public Setting SecondaryRecoveryAnimationSpeedMultiplier => RecoveryAnimationSpeedMultiplier.Secondary; public Setting PrimaryAttackRotationFactor => AttackRotationFactor.Primary1; public Setting SecondaryAttackRotationFactor => AttackRotationFactor.Secondary; public Setting PrimaryLockRotationAfterAttackTrigger => LockRotationAfterAttackTrigger.Primary1; public Setting SecondaryLockRotationAfterAttackTrigger => LockRotationAfterAttackTrigger.Secondary; public Setting PrimaryAdrenalineMultiplier => AdrenalineMultiplier.Primary1; public Setting SecondaryAdrenalineMultiplier => AdrenalineMultiplier.Secondary; public Setting PrimaryPvpDamageMultiplier => PvpDamageMultiplier.Primary1; public Setting SecondaryPvpDamageMultiplier => PvpDamageMultiplier.Secondary; public Setting PrimaryPvpStaggerMultiplier => PvpStaggerMultiplier.Primary1; public Setting SecondaryPvpStaggerMultiplier => PvpStaggerMultiplier.Secondary; public Setting PrimaryPvpStaggerDurationSeconds => PvpStaggerDurationSeconds.Primary1; public Setting SecondaryPvpStaggerDurationSeconds => PvpStaggerDurationSeconds.Secondary; public Setting PrimaryPvpKnockbackForceMultiplier => PvpKnockbackForceMultiplier.Primary1; public Setting SecondaryPvpKnockbackForceMultiplier => PvpKnockbackForceMultiplier.Secondary; public Setting PrimaryKnockbackForceMultiplier => KnockbackForceMultiplier.Primary1; public Setting SecondaryKnockbackForceMultiplier => KnockbackForceMultiplier.Secondary; public Setting PrimaryFullAttackDuration => FullAttackDuration.Primary1; public Setting SecondaryFullAttackDuration => FullAttackDuration.Secondary; public Setting PrimaryBlockCancelMode => BlockCancelMode.Primary1; public Setting SecondaryBlockCancelMode => BlockCancelMode.Secondary; public CategoryConfig(WeaponCategory category, Setting baseDamageMultiplier, RoleSettings staggerMode, RoleSettings staggerPowerValue, RoleSettings damageToStaggeredEnemyMultiplier, RoleSettings hyperArmorMode, RoleSettings damageTakenMultiplier, RoleSettings staggerTakenMultiplier, RoleSettings knockbackTakenMultiplier, RoleSettings pvpDamageTakenMultiplierDuringHyperArmor, RoleSettings pvpStaggerTakenMultiplierDuringHyperArmor, RoleSettings pvpKnockbackTakenMultiplierDuringHyperArmor, RoleSettings hitStopDurationMultiplier, RoleSettings pvpHitStopDurationMultiplier, RoleSettings counterDamageEnabled, RoleSettings counterDamageMultiplier, RoleSettings pvpCounterDamageMultiplier, RoleSettings damageRateMultiplier, RoleSettings chopDamageMultiplier, RoleSettings pickaxeDamageMultiplier, RoleSettings airborneDamageMultiplier, RoleSettings airborneStaggerMultiplier, RoleSettings airborneRotationFactor, RoleSettings staminaRateMultiplier, RoleSettings eitrRateMultiplier, RoleSettings attackMovementSpeedMultiplier, RoleSettings attackMovementApplicationMode, RoleSettings lungeDistanceMultiplier, RoleSettings lungeApplicationMode, RoleSettings attackAnimationSpeedMultiplier, RoleSettings recoveryAnimationSpeedMultiplier, RoleSettings attackRotationFactor, RoleSettings lockRotationAfterAttackTrigger, RoleSettings adrenalineMultiplier, RoleSettings pvpDamageMultiplier, RoleSettings pvpStaggerMultiplier, RoleSettings pvpStaggerDurationSeconds, RoleSettings pvpKnockbackForceMultiplier, RoleSettings knockbackForceMultiplier, Setting pvpBlockArmorMultiplier, Setting blockPowerMultiplier, Setting deflectionForceMultiplier, Setting blockStaminaConsumptionRate, Setting blockingStaminaRegenMultiplier, Setting blockBreakThresholdMultiplier, RoleSettings blockCancelMode, RoleSettings blockCancelAnimationSpeedMultiplier, RoleSettings fullAttackDuration) { Category = category; BaseDamageMultiplier = baseDamageMultiplier; StaggerMode = staggerMode; StaggerPowerValue = staggerPowerValue; DamageToStaggeredEnemyMultiplier = damageToStaggeredEnemyMultiplier; HyperArmorMode = hyperArmorMode; DamageTakenMultiplier = damageTakenMultiplier; StaggerTakenMultiplier = staggerTakenMultiplier; KnockbackTakenMultiplier = knockbackTakenMultiplier; PvpDamageTakenMultiplierDuringHyperArmor = pvpDamageTakenMultiplierDuringHyperArmor; PvpStaggerTakenMultiplierDuringHyperArmor = pvpStaggerTakenMultiplierDuringHyperArmor; PvpKnockbackTakenMultiplierDuringHyperArmor = pvpKnockbackTakenMultiplierDuringHyperArmor; HitStopDurationMultiplier = hitStopDurationMultiplier; PvpHitStopDurationMultiplier = pvpHitStopDurationMultiplier; CounterDamageEnabled = counterDamageEnabled; CounterDamageMultiplier = counterDamageMultiplier; PvpCounterDamageMultiplier = pvpCounterDamageMultiplier; DamageRateMultiplier = damageRateMultiplier; ChopDamageMultiplier = chopDamageMultiplier; PickaxeDamageMultiplier = pickaxeDamageMultiplier; AirborneDamageMultiplier = airborneDamageMultiplier; AirborneStaggerMultiplier = airborneStaggerMultiplier; AirborneRotationFactor = airborneRotationFactor; StaminaRateMultiplier = staminaRateMultiplier; EitrRateMultiplier = eitrRateMultiplier; AttackMovementModifier = attackMovementSpeedMultiplier; AttackMovementApplicationMode = attackMovementApplicationMode; LungeDistanceMultiplier = lungeDistanceMultiplier; LungeApplicationMode = lungeApplicationMode; AttackAnimationSpeedMultiplier = attackAnimationSpeedMultiplier; RecoveryAnimationSpeedMultiplier = recoveryAnimationSpeedMultiplier; AttackRotationFactor = attackRotationFactor; LockRotationAfterAttackTrigger = lockRotationAfterAttackTrigger; AdrenalineMultiplier = adrenalineMultiplier; PvpDamageMultiplier = pvpDamageMultiplier; PvpStaggerMultiplier = pvpStaggerMultiplier; PvpStaggerDurationSeconds = pvpStaggerDurationSeconds; PvpKnockbackForceMultiplier = pvpKnockbackForceMultiplier; KnockbackForceMultiplier = knockbackForceMultiplier; PvpBlockArmorMultiplier = pvpBlockArmorMultiplier; BlockArmorMultiplier = blockPowerMultiplier; BlockForceMultiplier = deflectionForceMultiplier; BlockStaminaConsumptionRate = blockStaminaConsumptionRate; BlockingStaminaRegenMultiplier = blockingStaminaRegenMultiplier; BlockBreakThresholdMultiplier = blockBreakThresholdMultiplier; BlockCancelMode = blockCancelMode; BlockCancelAnimationSpeedMultiplier = blockCancelAnimationSpeedMultiplier; FullAttackDuration = fullAttackDuration; } public float GetBaseDamageMultiplier() { if (!IsFeatureApplied(Category, ModFeature.Damage)) { return 1f; } return BaseDamageMultiplier.Value; } public float GetBlockStaminaConsumptionRate() { if (!IsFeatureApplied(Category, ModFeature.Blocking)) { return 1f; } return BlockStaminaConsumptionRate.Value; } public float GetBlockingStaminaRegenMultiplier() { if (!IsFeatureApplied(Category, ModFeature.Blocking)) { return 1f; } return BlockingStaminaRegenMultiplier.Value; } public float GetBlockBreakThresholdMultiplier() { if (!IsFeatureApplied(Category, ModFeature.Blocking)) { return 1f; } return BlockBreakThresholdMultiplier.Value; } public StaggerApplicationMode GetStaggerMode(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Staggering)) { return StaggerApplicationMode.MultiplyVanilla; } return StaggerMode.Get(role).Value; } public float GetStaggerPowerValue(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Staggering)) { return 1f; } return StaggerPowerValue.Get(role).Value; } public float GetDamageToStaggeredEnemyMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Staggering)) { return 1f; } return DamageToStaggeredEnemyMultiplier.Get(role).Value; } public HyperArmorMode GetHyperArmorMode(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return GooCombatOverhaul.HyperArmorMode.Off; } return HyperArmorMode.Get(role).Value; } public float GetDamageTakenMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return DamageTakenMultiplier.Get(role).Value; } public float GetStaggerTakenMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return StaggerTakenMultiplier.Get(role).Value; } public float GetKnockbackTakenMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return KnockbackTakenMultiplier.Get(role).Value; } public float GetPvpDamageTakenMultiplierDuringHyperArmor(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return PvpDamageTakenMultiplierDuringHyperArmor.Get(role).Value; } public float GetPvpStaggerTakenMultiplierDuringHyperArmor(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return PvpStaggerTakenMultiplierDuringHyperArmor.Get(role).Value; } public float GetPvpKnockbackTakenMultiplierDuringHyperArmor(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HyperArmor)) { return 1f; } return PvpKnockbackTakenMultiplierDuringHyperArmor.Get(role).Value; } public bool GetHitStopOnHit(AttackRole role) { if (IsFeatureApplied(Category, ModFeature.HitStop)) { return GetHitStopDurationMultiplier(role) > 0f; } return false; } public float GetHitStopDurationMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HitStop)) { return 1f; } return HitStopDurationMultiplier.Get(role).Value; } public float GetPvpHitStopDurationMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.HitStop)) { return 1f; } return PvpHitStopDurationMultiplier.Get(role).Value; } public bool GetCounterDamageEnabled(AttackRole role) { if (IsFeatureApplied(Category, ModFeature.CounterDamage)) { return CounterDamageEnabled.Get(role).Value; } return false; } public float GetCounterDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.CounterDamage)) { return 1f; } return CounterDamageMultiplier.Get(role).Value; } public float GetPvpCounterDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.CounterDamage)) { return 1f; } return PvpCounterDamageMultiplier.Get(role).Value; } public float GetDamageRateMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Damage)) { return 1f; } return DamageRateMultiplier.Get(role).Value; } public float GetChopDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Damage)) { return DefaultChopDamageMultiplier(Category); } return ChopDamageMultiplier.Get(role).Value; } public float GetPickaxeDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Damage)) { return VanillaPickaxeDamageMultiplier(Category); } return PickaxeDamageMultiplier.Get(role).Value; } public float GetAirborneDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Damage)) { return 1f; } return AirborneDamageMultiplier.Get(role).Value; } public float GetAirborneStaggerMultiplier(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 1f; } return AirborneStaggerMultiplier.Get(role).Value; } public float GetAirborneRotationFactor(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackRotation)) { return 1f; } return AirborneRotationFactor.Get(role).Value; } public float GetStaminaRateMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Resources)) { return 1f; } return StaminaRateMultiplier.Get(role).Value; } public float GetEitrRateMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Resources)) { return 1f; } return EitrRateMultiplier.Get(role).Value; } public float GetAttackMovementModifier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackMovement)) { return 1f; } return AttackMovementModifier.Get(role).Value; } public AttackMovementApplicationMode GetAttackMovementApplicationMode(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackMovement)) { return GooCombatOverhaul.AttackMovementApplicationMode.FullAnimation; } return AttackMovementApplicationMode.Get(role).Value; } public float GetLungeDistanceMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackMovement)) { return 1f; } return LungeDistanceMultiplier.Get(role).Value; } public LungeApplicationMode GetLungeApplicationMode(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackMovement)) { return GooCombatOverhaul.LungeApplicationMode.BeforeHitbox; } return LungeApplicationMode.Get(role).Value; } public float GetAttackAnimationSpeedMultiplier(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 1f; } return AttackAnimationSpeedMultiplier.Get(role).Value; } public float GetRecoveryAnimationSpeedMultiplier(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 1f; } return RecoveryAnimationSpeedMultiplier.Get(role).Value; } public float GetAttackRotationFactor(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.AttackRotation)) { return 1f; } return AttackRotationFactor.Get(role).Value; } public bool GetLockRotationAfterAttackTrigger(AttackRole role) { if (IsFeatureApplied(Category, ModFeature.AttackRotation)) { return LockRotationAfterAttackTrigger.Get(role).Value; } return false; } public float GetAdrenalineMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Resources)) { return 1f; } return AdrenalineMultiplier.Get(role).Value; } public float GetPvpDamageMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Pvp)) { return 1f; } return PvpDamageMultiplier.Get(role).Value; } public float GetPvpStaggerMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Pvp)) { return 1f; } return PvpStaggerMultiplier.Get(role).Value; } public float GetPvpStaggerDurationSeconds(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Pvp)) { return DefaultPvpStaggerDuration(Category, role); } return PvpStaggerDurationSeconds.Get(role).Value; } public float GetPvpKnockbackForceMultiplier(AttackRole role) { if (!IsFeatureApplied(Category, ModFeature.Pvp)) { return 1f; } return PvpKnockbackForceMultiplier.Get(role).Value; } public float GetKnockbackForceMultiplier(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 1f; } return KnockbackForceMultiplier.Get(role).Value; } public float GetFullAttackDuration(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 0f; } return FullAttackDuration.Get(role).Value; } public BlockCancelMode GetBlockCancelMode(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return GooCombatOverhaul.BlockCancelMode.Disabled; } return BlockCancelMode.Get(role).Value; } public float GetBlockCancelAnimationSpeedMultiplier(AttackRole role) { if (!IsWeaponCategoryApplied(Category)) { return 1f; } return BlockCancelAnimationSpeedMultiplier.Get(role).Value; } } private sealed class ParryStaggerSuppressionState { private Character? _attacker; private bool _originalStaggerWhenBlocked; public void Suppress(Character attacker) { if (!((Object)(object)attacker == (Object)null) && !((Object)(object)_attacker != (Object)null)) { _attacker = attacker; _originalStaggerWhenBlocked = attacker.m_staggerWhenBlocked; } } public void Restore() { if (!((Object)(object)_attacker == (Object)null)) { _attacker.m_staggerWhenBlocked = _originalStaggerWhenBlocked; _attacker = null; } } } private readonly struct SuppressedFieldValue { public object Target { get; } public FieldInfo Field { get; } public object? OriginalValue { get; } public SuppressedFieldValue(object target, FieldInfo field, object? originalValue) { Target = target; Field = field; OriginalValue = originalValue; } } private readonly struct CounterVulnerableState { public object? AttackInstance { get; } public CounterVulnerableState(object? attackInstance) { AttackInstance = attackInstance; } } private readonly struct AttackRuntimeState { public WeaponCategory Category { get; } public AttackRole Role { get; } public float EndTime { get; } public object? AttackInstance { get; } public bool StartedAirborne { get; } public bool IsPvpHitStop { get; } public AttackRuntimeState(WeaponCategory category, AttackRole role, float endTime, object? attackInstance, bool startedAirborne = false, bool isPvpHitStop = false) { Category = category; Role = role; EndTime = endTime; AttackInstance = attackInstance; StartedAirborne = startedAirborne; IsPvpHitStop = isPvpHitStop; } } private readonly struct PendingAttackRoleState { public AttackRole Role { get; } public float EndTime { get; } public PendingAttackRoleState(AttackRole role, float endTime) { Role = role; EndTime = endTime; } } private readonly struct HyperArmorRuntimeState { public WeaponCategory Category { get; } public AttackRole Role { get; } public HyperArmorMode Mode { get; } public float EndTime { get; } public object? AttackInstance { get; } public HyperArmorRuntimeState(WeaponCategory category, AttackRole role, HyperArmorMode mode, float endTime, object? attackInstance) { Category = category; Role = role; Mode = mode; EndTime = endTime; AttackInstance = attackInstance; } } private sealed class CharacterRuntimeDefaults { public readonly Vector3 LocalScale; public readonly float JogSpeed; public readonly float WalkSpeed; public readonly float RunSpeed; public readonly float TurnSpeed; public readonly float RunTurnSpeed; public CharacterRuntimeDefaults(Character character) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) LocalScale = ((Component)character).transform.localScale; JogSpeed = character.m_speed; WalkSpeed = character.m_walkSpeed; RunSpeed = character.m_runSpeed; TurnSpeed = character.m_turnSpeed; RunTurnSpeed = character.m_runTurnSpeed; } } private sealed class PlayerRuntimeDefaults { public readonly float JumpForce; public readonly float JumpStaminaUsage; public readonly float RunStaminaDrain; public readonly float DodgeStaminaUsage; public readonly float SneakStaminaDrain; public readonly float RunSpeed; public readonly float JogSpeed; public readonly float WalkSpeed; public readonly float CrouchSpeed; public readonly float StaminaRegen; public readonly float StaminaRegenTimeMultiplier; public PlayerRuntimeDefaults(Player player) { JumpForce = ((Character)player).m_jumpForce; JumpStaminaUsage = ((Character)player).m_jumpStaminaUsage; RunStaminaDrain = player.m_runStaminaDrain; DodgeStaminaUsage = player.m_dodgeStaminaUsage; SneakStaminaDrain = player.m_sneakStaminaDrain; RunSpeed = ((Character)player).m_runSpeed; JogSpeed = ((Character)player).m_speed; WalkSpeed = ((Character)player).m_walkSpeed; CrouchSpeed = ((Character)player).m_crouchSpeed; StaminaRegen = player.m_staminaRegen; StaminaRegenTimeMultiplier = player.m_staminaRegenTimeMultiplier; } } public sealed class BlockingMovementSpeedPatchState { private readonly Player _player; private readonly float _speed; private readonly float _walkSpeed; private readonly float _runSpeed; private readonly float _crouchSpeed; public BlockingMovementSpeedPatchState(Player player, float speed, float walkSpeed, float runSpeed, float crouchSpeed) { _player = player; _speed = speed; _walkSpeed = walkSpeed; _runSpeed = runSpeed; _crouchSpeed = crouchSpeed; } public void Restore() { if (!((Object)(object)_player == (Object)null)) { ((Character)_player).m_speed = _speed; ((Character)_player).m_walkSpeed = _walkSpeed; ((Character)_player).m_runSpeed = _runSpeed; ((Character)_player).m_crouchSpeed = _crouchSpeed; } } } private readonly struct AnimatorSpeedValue { private readonly Animator? _animator; private readonly object? _target; private readonly FieldInfo? _field; private readonly float _originalSpeed; private readonly float _multiplier; public AnimatorSpeedValue(Animator animator, float originalSpeed, float multiplier) { _animator = animator; _target = null; _field = null; _originalSpeed = originalSpeed; _multiplier = multiplier; } public AnimatorSpeedValue(object target, FieldInfo field, float originalSpeed, float multiplier) { _animator = null; _target = target; _field = field; _originalSpeed = originalSpeed; _multiplier = multiplier; } public void Restore() { if ((Object)(object)_animator != (Object)null) { _animator.speed = _originalSpeed; } else if (_target != null && _field != null) { _field.SetValue(_target, _originalSpeed); } } public void Reapply() { float num = _originalSpeed * _multiplier; if ((Object)(object)_animator != (Object)null) { if (!(_animator.speed <= 0.001f)) { _animator.speed = num; } } else if (_target != null && _field != null) { _field.SetValue(_target, num); } } } private sealed class GcoPvpTestDummyBehaviour : MonoBehaviour { private Character _character; private void Awake() { _character = ((Component)this).GetComponent(); if ((Object)(object)_character != (Object)null) { DisableDummyAI(_character); ApplyPvpTestDummyConfigToInstance(_character, restoreHealth: true); SetPrivateFloat(_character, "m_staggerDamage", 0f); } } private void FixedUpdate() { if ((Object)(object)_character == (Object)null) { _character = ((Component)this).GetComponent(); } if ((Object)(object)_character == (Object)null || !IsPvpTestDummy(_character)) { return; } DisableDummyAI(_character); ZDO val = null; ZNetView component = ((Component)_character).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { val = component.GetZDO(); if (val != null) { val.Persistent = true; } if (!component.IsOwner()) { return; } } float pvpTestDummyMaxHealth = GetPvpTestDummyMaxHealth(_character); _character.SetMaxHealth(pvpTestDummyMaxHealth); } } private sealed class StaffGreenRootSpawnRecord { public Character Character { get; } public long SpawnTicks { get; } public StaffGreenRootSpawnRecord(Character character, long spawnTicks) { Character = character; SpawnTicks = spawnTicks; } } private sealed class StaffSpawnOrigin { public Character Owner { get; } public long OwnerPlayerId { get; } public string StaffPrefabName { get; } public bool IsGreenRoots { get; } public bool IsSkeleton { get; } public float CreatedTime { get; } public StaffSpawnOrigin(Character owner, StaffConfig staffConfig, ItemData? item = null) { Owner = owner; Player val = (Player)(object)((owner is Player) ? owner : null); OwnerPlayerId = ((val != null) ? val.GetPlayerID() : 0); StaffPrefabName = staffConfig.PrefabName; IsGreenRoots = IsStaffGreenRoots(staffConfig); IsSkeleton = IsStaffSkeleton(staffConfig); CreatedTime = Time.time; } } private sealed class DamageBonusStackState { public static readonly DamageBonusStackState Empty = new DamageBonusStackState(backstab: false, staggeredEnemy: false, flanking: false, counterDamage: false); public bool Backstab { get; } public bool StaggeredEnemy { get; } public bool Flanking { get; } public bool CounterDamage { get; } public int ActiveCount => (Backstab ? 1 : 0) + (StaggeredEnemy ? 1 : 0) + (Flanking ? 1 : 0) + (CounterDamage ? 1 : 0); public DamageBonusStackState(bool backstab, bool staggeredEnemy, bool flanking, bool counterDamage) { Backstab = backstab; StaggeredEnemy = staggeredEnemy; Flanking = flanking; CounterDamage = counterDamage; } public bool IsActive(DamageBonusKind kind) { return kind switch { DamageBonusKind.Backstab => Backstab, DamageBonusKind.StaggeredEnemy => StaggeredEnemy, DamageBonusKind.Flanking => Flanking, DamageBonusKind.CounterDamage => CounterDamage, _ => false, }; } } private sealed class CombatDingState { public bool CounterDamageDingPlayed; public bool FlankingDingPlayed; } private sealed class SkillDamageFactorState { public float SkillDamageFactor { get; } public SkillDamageFactorState(float skillDamageFactor) { SkillDamageFactor = skillDamageFactor; } } private sealed class PvpStaggerDurationState { public float DurationSeconds { get; } public PvpStaggerDurationState(float durationSeconds) { DurationSeconds = durationSeconds; } } private sealed class PvpDummyDebugState { public float HealthBefore { get; } public float StaggerBefore { get; } public float HealthAfterDamage { get; set; } = -1f; public float StaggerAfterDamage { get; set; } = -1f; public PvpDummyDebugState(float healthBefore, float staggerBefore) { HealthBefore = healthBefore; StaggerBefore = staggerBefore; } } private sealed class ProcessedHitMarker { public static readonly ProcessedHitMarker Instance = new ProcessedHitMarker(); } private sealed class LightningAttackSpawnState { public GameObject? SpawnOnHit { get; } public float SpawnOnHitChance { get; } public LightningAttackSpawnState(GameObject? spawnOnHit, float spawnOnHitChance) { SpawnOnHit = spawnOnHit; SpawnOnHitChance = spawnOnHitChance; } } public sealed class ConfigSyncSaveState { public bool EnteredLocalReadScope { get; } public bool ExitedLocalReadScope { get; set; } public ConfigSyncSaveState(bool enteredLocalReadScope) { EnteredLocalReadScope = enteredLocalReadScope; } } public sealed class RpcDamageFlankSoundState { public EffectList? PreviousEffectList { get; } public int PreviousDepth { get; } public bool Restored { get; set; } public RpcDamageFlankSoundState(EffectList? previousEffectList, int previousDepth) { PreviousEffectList = previousEffectList; PreviousDepth = previousDepth; } } public sealed class SledgeAreaAttackScaleState { private readonly object _attackInstance; private readonly float _originalRadius; private readonly float _originalCharacterExtraRadius; private bool _restored; public SledgeAreaAttackScaleState(object attackInstance, float originalRadius, float originalCharacterExtraRadius) { _attackInstance = attackInstance; _originalRadius = originalRadius; _originalCharacterExtraRadius = originalCharacterExtraRadius; } public void Restore() { if (_restored) { return; } _restored = true; try { SetFieldValueRaw(_attackInstance, "m_attackRayWidth", _originalRadius); SetFieldValueRaw(_attackInstance, "m_attackRayWidthCharExtra", _originalCharacterExtraRadius); } catch { } } } public sealed class AlternateSledgeSecondarySwapState { private readonly Character _character; private readonly ItemData _weapon; private readonly Attack? _originalSecondary; private readonly Attack _temporarySecondary; private bool _restored; private bool _requestCleaned; public AlternateSledgeSecondarySwapState(Character character, ItemData weapon, Attack? originalSecondary, Attack temporarySecondary) { _character = character; _weapon = weapon; _originalSecondary = originalSecondary; _temporarySecondary = temporarySecondary; } public void Restore(bool cleanRequestState = false) { if (!_restored) { _restored = true; try { if (_weapon?.m_shared != null && _weapon.m_shared.m_secondaryAttack == _temporarySecondary) { _weapon.m_shared.m_secondaryAttack = _originalSecondary; } } catch { } } if (cleanRequestState && !_requestCleaned) { _requestCleaned = true; PendingAttackRoles.Remove(_character); AlternateSledgeSecondaryRequests.Remove(_character); } } } public sealed class ParryCapabilityPatchState { private readonly ItemData _blocker; private readonly float _originalTimedBlockBonus; private bool _restored; public ParryCapabilityPatchState(ItemData blocker, float originalTimedBlockBonus) { _blocker = blocker; _originalTimedBlockBonus = originalTimedBlockBonus; } public void Restore() { if (_restored) { return; } _restored = true; try { if (_blocker?.m_shared != null) { _blocker.m_shared.m_timedBlockBonus = _originalTimedBlockBonus; } } catch { } } } public sealed class DamagePatchState { public Character Victim { get; } public float HealthBefore { get; } public float DamageMultiplier { get; } public DurabilityPatchState? DurabilityState { get; set; } public float TargetHealthAfterMitigation { get; set; } public DamagePatchState(Character victim, float healthBefore, float damageMultiplier) { Victim = victim; HealthBefore = healthBefore; DamageMultiplier = damageMultiplier; TargetHealthAfterMitigation = -1f; } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Func <>9__564_0; public static Func <>9__566_0; public static Func <>9__577_0; public static Func <>9__577_1; public static Func <>9__599_0; public static Predicate <>9__614_0; public static Func <>9__666_0; public static Func <>9__668_0; public static Func <>9__668_1; public static Func <>9__668_2; public static Func <>9__668_4; public static Func <>9__669_0; public static Func <>9__669_1; public static Func <>9__674_0; public static Func <>9__674_1; public static Func <>9__674_2; public static Func <>9__691_0; public static Func <>9__694_1; public static ConsoleOptionsFetcher <>9__694_0; public static Func, ConfigEntryBase> <>9__698_1; public static Func>, KeyValuePair> <>9__698_2; public static Func <>9__714_0; public static Func <>9__792_0; public static Func <>9__792_1; public static Func <>9__794_0; public static Func <>9__800_0; public static Func <>9__851_0; public static Func <>9__856_0; public static Func <>9__856_1; public static Func <>9__939_0; public static Func <>9__939_1; public static Func <>9__940_0; public static Func <>9__945_0; public static Func <>9__946_0; public static Comparison <>9__1036_0; public static Func <>9__1036_1; public static Func, object> <>9__1060_1; public static ConditionalWeakTable.CreateValueCallback <>9__1203_0; public static Predicate <>9__1331_0; public static Predicate <>9__1333_0; public static Comparison <>9__1333_1; public static Func <>9__1333_2; public static Func <>9__1528_0; internal Type b__564_0(ConfigEntryBase entry) { return entry.SettingType; } internal bool b__566_0(Type type) { return type != null; } internal string b__577_0(ConfigEntryBase entry) { return entry.Definition.Section; } internal string b__577_1(ConfigEntryBase entry) { return entry.Definition.Key; } internal bool b__599_0(string k) { return !string.IsNullOrWhiteSpace(k); } internal bool b__614_0(string value) { return value.Equals("Sledges", StringComparison.OrdinalIgnoreCase); } internal bool b__666_0(string token) { return !string.IsNullOrWhiteSpace(token); } internal bool b__668_0(FieldInfo field) { return typeof(IDictionary).IsAssignableFrom(field.FieldType); } internal bool b__668_1(ConfigEntryBase entry) { if (IsRunnableCommandEntry(entry) && GcoPrimaryCommandRegisteredEntries.Contains(entry)) { return !GcoAllCommandRegisteredEntries.Contains(entry); } return true; } internal string b__668_2(ConfigEntryBase entry) { return "[" + entry.Definition.Section + "] " + entry.Definition.Key; } internal string b__668_4(ConfigEntryBase entry) { return "[" + entry.Definition.Section + "] " + entry.Definition.Key; } internal bool b__669_0(string part) { return !string.IsNullOrWhiteSpace(part); } internal bool b__669_1(string part) { return !string.IsNullOrWhiteSpace(part); } internal string b__674_0(string token) { return ReplaceOrdinalIgnoreCase(token, "Backstab", "Flank"); } internal bool b__674_1(string t) { return !string.IsNullOrWhiteSpace(t); } internal string b__674_2(string t) { return t.Trim(); } internal bool b__691_0(ConfigEntryBase entry) { return entry != null; } internal string b__694_1(string key) { if (!GcoFlatBranchDisplayNames.TryGetValue(key, out string value)) { return key; } return value; } internal List b__694_0() { return new List { "goo", "vanilla" }; } internal ConfigEntryBase b__698_1(KeyValuePair pair) { return pair.Value; } internal KeyValuePair b__698_2(IGrouping> group) { return group.First(); } internal bool b__714_0(string x) { return !string.IsNullOrWhiteSpace(x); } internal bool b__792_0(MethodInfo method) { return method.Name == "Create"; } internal bool b__792_1(MethodInfo m) { return m.Name == "Stagger"; } internal bool b__794_0(Type t) { return t != null; } internal string b__800_0(Type p) { return p.Name; } internal bool b__851_0(Food food) { if (food != null) { return food.CanEatAgain(); } return false; } internal bool b__856_0(Food food) { if (food != null) { return food.CanEatAgain(); } return false; } internal float b__856_1(Food food) { return food.m_time; } internal bool b__939_0(MethodInfo m) { if (m.Name == "Start") { return m.ReturnType == typeof(bool); } return false; } internal bool b__939_1(MethodInfo m) { return m.Name == "Start"; } internal bool b__940_0(MethodInfo m) { return m.Name == "DoAreaAttack"; } internal bool b__945_0(MethodInfo method) { if (method.Name == "BlockAttack" && method.ReturnType == typeof(bool) && method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(HitData)) { return typeof(Character).IsAssignableFrom(method.GetParameters()[1].ParameterType); } return false; } internal bool b__946_0(MethodInfo m) { if (m.Name == "StartAttack") { return m.ReturnType == typeof(bool); } return false; } internal int b__1036_0(RaycastHit x, RaycastHit y) { return ((RaycastHit)(ref x)).distance.CompareTo(((RaycastHit)(ref y)).distance); } internal float b__1036_1(DirectSledgeHitPoint h) { return h.Distance; } internal object b__1060_1(KeyValuePair kvp) { return kvp.Key; } internal CombatDingState b__1203_0(HitData _) { return new CombatDingState(); } internal bool b__1331_0(StaffGreenRootSpawnRecord record) { if (!((Object)(object)record.Character == (Object)null)) { return (Object)(object)((Component)record.Character).gameObject == (Object)null; } return true; } internal bool b__1333_0(StaffGreenRootSpawnRecord record) { if (!((Object)(object)record.Character == (Object)null)) { return (Object)(object)((Component)record.Character).gameObject == (Object)null; } return true; } internal int b__1333_1(StaffGreenRootSpawnRecord a, StaffGreenRootSpawnRecord b) { return a.SpawnTicks.CompareTo(b.SpawnTicks); } internal bool b__1333_2(StaffGreenRootSpawnRecord candidate) { return CanAuthoritativelyDestroyStaffGreenRoot(candidate.Character); } internal string b__1528_0(Type t) { return t.FullName; } } public const string ModGuid = "goo.valheim.gooscombatoverhaul"; public const string ModName = "Goo's Combat Overhaul"; public const string ModVersion = "1.4.0"; private readonly Harmony _harmony = new Harmony("goo.valheim.gooscombatoverhaul"); private bool _bindPrimary3ForCurrentCategory = true; private bool _bindPrimary4ForCurrentCategory; private WeaponCategory _bindCategoryForCurrentCategory; private bool _processingGlobalRulesetButton; private static bool _suppressGcoChatBroadcast; private bool _startupConfigVersionDecisionInitialized; private bool _startupConfigVersionNeedsBump; private readonly Dictionary _startupRawConfigValues = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _startupInheritedConfigValues = new Dictionary(); private readonly List _startupConfigInheritanceFailures = new List(); private bool _startupRawConfigSnapshotLoaded; private bool _startupHadExistingConfigFile; private static bool _clientMultiplayerConfigSyncActive; private static bool _clientGuestDebugAuthorityLimited; private static bool _applyingGuestDebugAuthorityRestriction; private static bool _configSyncBroadcastPending; private static bool _configRuntimeRefreshBatchActive; private static bool _configRuntimeRefreshBatchPending; [ThreadStatic] private static int _readClientLocalConfigDepth; [ThreadStatic] private static int _configurationManagerLocalDisplayDepth; private static readonly Dictionary GcoConfigSyncEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ClientSyncedConfigValues = new Dictionary(); private static readonly Dictionary ClientSyncedTypedValues = new Dictionary(); private static readonly HashSet PatchedConfigEntryValueGetters = new HashSet(); private const string ConfigSyncRpcName = "GCO_ConfigSync"; private const string ConfigSyncRequestRpcName = "GCO_ConfigSyncRequest"; private const int ConfigSyncProtocolVersion = 3; private static readonly Dictionary GcoCommandPrimaryPaths = new Dictionary(); private static readonly HashSet GcoPrimaryCommandRegisteredEntries = new HashSet(); private static readonly HashSet GcoAllCommandRegisteredEntries = new HashSet(); private static readonly Dictionary> GcoFlatCommandEntries = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> GcoFlatCommandOptions = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary GcoFlatBranchDisplayNames = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary GearDurabilityConfigs = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _gcoFlatConsoleCommandsRegistered; private static int GcoConsoleFeatureCollisionCount; private static bool _gcoDebugFlyDesired; private static bool _gcoNoPlacementCostDesired; private static bool _gcoGhostModeDesired; private static readonly Dictionary DebugShortcutDebounceUntil = new Dictionary(); private static readonly HashSet SuppressedButtonDownNamesThisFrame = new HashSet(StringComparer.OrdinalIgnoreCase); private static int SuppressedButtonDownFrame = -1; private static bool _lastGooCombatDebugModeActive; internal static ConfigEntry Enabled = null; internal static ConfigEntry ConfigVersion = null; internal static ConfigEntry SetToGoosRuleset = null; internal static ConfigEntry SetToVanillaRuleset = null; internal static ConfigEntry BroadcastConfigChanges = null; internal static ConfigEntry SyncConfigInMultiplayer = null; internal static ConfigEntry LimitGuestsAuthority = null; internal static ConfigEntry Roll = null; internal static ConfigEntry DebugLogging = null; internal static ConfigEntry VerboseDebugLogging = null; internal static ConfigEntry DebugAttackLifecycle = null; internal static ConfigEntry DebugSledgeAlternateMoveset = null; internal static ConfigEntry DebugCounterDamage = null; internal static ConfigEntry CounterDamageDing = null; internal static ConfigEntry DebugHitStop = null; internal static ConfigEntry DebugMovement = null; internal static ConfigEntry DebugHyperArmorLifecycle = null; internal static ConfigEntry EnableCounterDamage = null; internal static ConfigEntry OnlyPlayersCanDealCounterDamage = null; internal static ConfigEntry HyperArmorMitigationMode = null; internal static ConfigEntry MitigateUnknownAttackerDamageDuringHyperArmor = null; internal static ConfigEntry DebugHyperArmorDamageMitigation = null; internal static ConfigEntry EnablePvpDamageModifiers = null; internal static ConfigEntry ClearStaggerMeterWhenStaggered = null; internal static ConfigEntry ClearEnemyStaggerMeterWhenStaggered = null; internal static ConfigEntry MobMaintainDirectionWhenStaggered = null; internal static ConfigEntry PlayerMaintainDirectionWhenStaggered = null; internal static ConfigEntry AlwaysDoMaxDamage = null; internal static ConfigEntry AlwaysDoMaxDamageInPvp = null; internal static ConfigEntry GlobalDamageMultiplier = null; internal static ConfigEntry GlobalHyperArmorDamageTakenMultiplier = null; internal static ConfigEntry GlobalHyperArmorKnockbackTakenMultiplier = null; internal static ConfigEntry GlobalPvpDamageMultiplier = null; internal static ConfigEntry GlobalPvpDamageTakenMultiplier = null; internal static ConfigEntry UseDamageCurveInPve = null; internal static ConfigEntry PveAlternateDamageCurveBaseRawDamage = null; internal static ConfigEntry PveAlternateDamageCurveExponent = null; internal static ConfigEntry PveAlternateDamageCurveMinimumMultiplier = null; internal static ConfigEntry PveAlternateDamageCurveMaximumMultiplier = null; internal static ConfigEntry UseDamageCurveInPvp = null; internal static ConfigEntry PvpAlternateDamageCurveBaseRawDamage = null; internal static ConfigEntry PvpAlternateDamageCurveExponent = null; internal static ConfigEntry PvpAlternateDamageCurveMinimumMultiplier = null; internal static ConfigEntry PvpAlternateDamageCurveMaximumMultiplier = null; internal static ConfigEntry GlobalPlayerKnockbackMultiplier = null; internal static ConfigEntry GlobalPvpKnockbackMultiplier = null; internal static ConfigEntry GlobalPlayerStaggerPowerMultiplier = null; internal static ConfigEntry GlobalDamageToStaggeredEnemiesMultiplier = null; internal static ConfigEntry StaggeredEnemyDamageStackableWithOtherBonuses = null; internal static ConfigEntry BackstabDamageStackableWithOtherBonuses = null; internal static ConfigEntry CounterDamageStackableWithOtherBonuses = null; internal static ConfigEntry EnableFlankingDamage = null; internal static ConfigEntry FlankingDamageStackableWithOtherBonuses = null; internal static ConfigEntry SuccessfulFlankDing = null; internal static ConfigEntry FlankingPveRearAngleDegrees = null; internal static ConfigEntry FlankingPvpRearAngleDegrees = null; internal static ConfigEntry GlobalBackstabDamageMultiplier = null; internal static ConfigEntry GlobalPvpBackstabDamageMultiplier = null; internal static ConfigEntry GlobalPlayerDamageTakenMultiplier = null; internal static ConfigEntry PlayerDamageToBuildingsMultiplier = null; internal static ConfigEntry EnemyDamageToBuildingsMultiplier = null; internal static ConfigEntry GlobalCounterDamageMultiplier = null; internal static ConfigEntry GlobalHitStopEnabled = null; internal static ConfigEntry GlobalBlockingStaminaRegenMultiplier = null; internal static ConfigEntry GlobalBlockArmorMultiplier = null; internal static ConfigEntry GlobalBlockForceMultiplier = null; internal static ConfigEntry GlobalParryBonusMultiplier = null; internal static ConfigEntry GlobalParryWindowTimeMultiplier = null; internal static ConfigEntry GlobalEffectiveBlockAngleMultiplier = null; internal static ConfigEntry GlobalParryAdrenalineMultiplier = null; internal static ConfigEntry CanStaggerRangedAttacksWithParryPve = null; internal static ConfigEntry CanStaggerRangedAttacksWithParryPvp = null; internal static ConfigEntry CanStaggerAoeAttacksWithParryPve = null; internal static ConfigEntry CanStaggerAoeAttacksWithParryPvp = null; internal static ConfigEntry GlobalBlockStaminaConsumptionRate = null; internal static ConfigEntry GlobalBlockBreakThresholdMultiplier = null; internal static ConfigEntry GlobalPvpBlockArmorMultiplier = null; internal static ConfigEntry GlobalPvpBlockForceMultiplier = null; internal static ConfigEntry GlobalPvpParryBonusMultiplier = null; internal static ConfigEntry GlobalPvpBlockBreakThresholdMultiplier = null; internal static ConfigEntry GlobalBlockingMovementSpeedMultiplier = null; internal static ConfigEntry GlobalKnockbackTakenWhileBlockingMultiplier = null; internal static ConfigEntry GlobalSoulsLikeBlockStaminaBaseRateMultiplier = null; internal static BlockedHitDamageTakenConfig GlobalDamageTakenOnBlockedHit = null; internal static BlockedHitDamageTakenConfig ShieldBucklerDamageTakenOnBlockedHit = null; internal static BlockedHitDamageTakenConfig ShieldMediumDamageTakenOnBlockedHit = null; internal static BlockedHitDamageTakenConfig ShieldTowerDamageTakenOnBlockedHit = null; private static readonly Dictionary WeaponDamageTakenOnBlockedHitConfigs = new Dictionary(); private static readonly Dictionary StaffDamageTakenOnBlockedHitConfigs = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static ConfigEntry GlobalHitRayRangeMultiplier = null; internal static ConfigEntry GlobalHitRayThicknessMultiplier = null; internal static ConfigEntry GlobalHitRayStartHeightMultiplier = null; internal static ConfigEntry GlobalHitRayOffsetMultiplier = null; internal static ConfigEntry GlobalHitRayAngleMultiplier = null; internal static ConfigEntry OneHandedAxeSecondaryHitRayAddedThicknessMeters = null; internal static ConfigEntry OneHandedAxeJumpAttackHitRayAddedThicknessMeters = null; internal static ConfigEntry GlobalAttackStartNoiseMultiplier = null; internal static ConfigEntry GlobalAttackHitNoiseMultiplier = null; internal static ConfigEntry GlobalSneakDamageMultiplier = null; internal static ConfigEntry SneakDamageScalesWithSneakLevel = null; internal static ConfigEntry SneakDamageScaleBaseMultiplier = null; internal static ConfigEntry SneakDamageMultiplierPerSneakLevel = null; internal static ConfigEntry SneakXpOnSuccessfulSneakAttack = null; internal static ConfigEntry SneakXpOnSuccessfulSneakAttackBaseAmount = null; internal static ConfigEntry SneakXpScalesWithSneakDamageMultiplier = null; internal static ConfigEntry SneakXpRangedAttackMultiplier = null; internal static ConfigEntry GlobalAttackMovementMultiplier = null; internal static ConfigEntry GlobalLungeDistanceMultiplier = null; internal static ConfigEntry GlobalRunningAttackLungeDistanceMultiplier = null; internal static ConfigEntry GlobalJumpAttackLungeDistanceMultiplier = null; internal static ConfigEntry GlobalAttackRotationMultiplier = null; internal static ConfigEntry GlobalPvpHitStopDurationMultiplier = null; internal static ConfigEntry GlobalAttackAnimationSpeedMultiplier = null; internal static ConfigEntry GlobalRecoveryAnimationSpeedMultiplier = null; internal static ConfigEntry BetterLightningEffects = null; internal static ConfigEntry HimminAflLightningChainChance = null; internal static ConfigEntry DundrLightningChainChancePerPellet = null; internal static ConfigEntry StaffLightningRecoilStrengthMultiplier = null; internal static ConfigEntry GlobalAdrenalineRate = null; internal static ConfigEntry MustEatUniqueFood = null; internal static ConfigEntry FoodHealthMultiplier = null; internal static ConfigEntry FoodStaminaMultiplier = null; internal static ConfigEntry FoodEitrMultiplier = null; internal static ConfigEntry FoodHealingRateMultiplier = null; internal static ConfigEntry PermanentFoodBuffs = null; internal static ConfigEntry PermanentMeadBuffs = null; internal static ConfigEntry PermanentRestedBuff = null; internal static ConfigEntry NoConsumption = null; internal static ConfigEntry MeadStaminaRecoveryBonusMultiplier = null; internal static ConfigEntry MeadEitrRecoveryBonusMultiplier = null; internal static ConfigEntry MeadHealthRegenBonusMultiplier = null; internal static ConfigEntry GlobalUnbreakableGear = null; internal static ConfigEntry GlobalDurabilityConsumptionMultiplier = null; internal static ConfigEntry GlobalBaseDurabilityMultiplier = null; internal static ConfigEntry GlobalDurabilityMultiplier = null; internal static ConfigEntry SpawnPvpTestDummy = null; internal static ConfigEntry RemovePvpTestDummies = null; internal static ConfigEntry PvpTestDummyHealth = null; internal static ConfigEntry PvpTestDummyArmor = null; internal static ConfigEntry PvpTestDummyStaggerThresholdMultiplier = null; internal static ConfigEntry DebugPvpTestDummyOutput = null; internal static ConfigEntry HitStopDurationMultiplier = null; internal static ConfigEntry HitStopDurationOverrideSeconds = null; internal static ConfigEntry AttackMovementGroundingModeConfig = null; internal static ConfigEntry BetterFreeAim = null; private const float VanillaPlayerBaseHealth = 25f; private const float VanillaPlayerBaseStamina = 75f; private const float VanillaPlayerBaseArmor = 0f; private const float VanillaPlayerBaseCarryWeight = 300f; private const float VanillaFallDamageStartHeight = 4f; private const float VanillaFallDamagePerMeter = 6.25f; private const float VanillaFallDamageCap = 100f; internal static ConfigEntry EnableNegativeStamina = null; internal static ConfigEntry NegativeStaminaFloor = null; internal static ConfigEntry NegativeStaminaActionGate = null; internal static ConfigEntry PlayerBaseHealth = null; internal static ConfigEntry PlayerBaseStamina = null; internal static ConfigEntry PlayerBaseEitr = null; internal static ConfigEntry PlayerBaseEitrRegenRate = null; internal static ConfigEntry PlayerBaseArmor = null; internal static ConfigEntry PlayerBaseCarryWeight = null; internal static ConfigEntry PlayerJumpForceMultiplier = null; internal static ConfigEntry PlayerRunSpeedMultiplier = null; internal static ConfigEntry PlayerJogSpeedMultiplier = null; internal static ConfigEntry PlayerWalkSpeedMultiplier = null; internal static ConfigEntry PlayerCrouchSpeedMultiplier = null; internal static ConfigEntry PlayerBaseRotationFactor = null; internal static ConfigEntry PlayerSizeMultiplier = null; internal static ConfigEntry PlayerPoiseMultiplier = null; internal static ConfigEntry PlayerStaggerDecaySpeedMultiplier = null; internal static ConfigEntry PlayerStaggerDecayCooldownSeconds = null; internal static ConfigEntry AirborneAttackMinimumAirTimeSeconds = null; internal static ConfigEntry JumpStaminaConsumptionRate = null; internal static ConfigEntry RunStaminaConsumptionRate = null; internal static ConfigEntry DrainRunStaminaDuringAttacksAndAirborne = null; internal static ConfigEntry RollStaminaConsumptionRate = null; internal static ConfigEntry CrouchStaminaConsumptionRate = null; internal static ConfigEntry RollAnimationSpeed = null; internal static ConfigEntry RollDistanceMultiplier = null; internal static ConfigEntry StaminaRecoveryRate = null; internal static ConfigEntry StaminaRegenCurve = null; internal static ConfigEntry PlayerHealingRate = null; internal static ConfigEntry FallDamageStartHeight = null; internal static ConfigEntry FallDamagePerMeter = null; internal static ConfigEntry FallDamageCap = null; internal static ConfigEntry BowDrawSpeedMultiplier = null; internal static ConfigEntry BowDrawRotationFactor = null; internal static ConfigEntry BowSpreadMultiplier = null; internal static ConfigEntry BowProjectileSpeedMultiplier = null; internal static ConfigEntry BowBaseSpreadMultiplier = null; internal static ConfigEntry CrossbowReloadTimeMultiplier = null; internal static ConfigEntry CrossbowProjectileSpeedMultiplier = null; internal static ConfigEntry CrossbowRecoilStrengthMultiplier = null; internal static ConfigEntry SpearProjectileSpeedMultiplier = null; internal static ConfigEntry SpearLoyaltyMode = null; internal static ConfigEntry ThrowableStaminaConsumptionRate = null; internal static ConfigEntry ThrowableDamageMultiplier = null; internal static ConfigEntry ThrowableProjectileSpeedMultiplier = null; internal static ConfigEntry ThrowableThrowAnimationSpeedMultiplier = null; internal static ConfigEntry SledgeUseAlternateMoveset = null; internal static ConfigEntry SledgeSlamAoeRadiusMultiplier = null; internal static ConfigEntry SledgePrimary1DirectHitRayStartHeightMeters = null; internal static ConfigEntry SledgePrimary1DirectHitRayRangeMeters = null; internal static ConfigEntry SledgePrimary1DirectHitRayAngleDegrees = null; internal static ConfigEntry SledgePrimary1DirectHitRayThicknessMeters = null; internal static ConfigEntry SledgePrimary2DirectHitRayStartHeightMeters = null; internal static ConfigEntry SledgePrimary2DirectHitRayRangeMeters = null; internal static ConfigEntry SledgePrimary2DirectHitRayAngleDegrees = null; internal static ConfigEntry SledgePrimary2DirectHitRayThicknessMeters = null; internal static ConfigEntry SledgeRunningDirectHitRayStartHeightMeters = null; internal static ConfigEntry SledgeRunningDirectHitRayRangeMeters = null; internal static ConfigEntry SledgeRunningDirectHitRayAngleDegrees = null; internal static ConfigEntry SledgeRunningDirectHitRayThicknessMeters = null; internal static ConfigEntry HarpoonRopeStrengthMultiplier = null; internal static ConfigEntry CrossbowBaseSpreadMultiplier = null; internal static ConfigEntry LightArmorBaseArmorMultiplier = null; internal static ConfigEntry MediumArmorBaseArmorMultiplier = null; internal static ConfigEntry HeavyArmorBaseArmorMultiplier = null; internal static ConfigEntry DebugPlayerSystems = null; internal static ConfigEntry DebugNegativeStamina = null; internal static ConfigEntry DebugRanged = null; internal static ConfigEntry DebugBlocking = null; internal static ConfigEntry GooCombatDebugMode = null; internal static ConfigEntry EnableDebugFlyToggle = null; internal static ConfigEntry EnableNoPlacementCostToggle = null; internal static ConfigEntry EnableGhostModeToggle = null; internal static ConfigEntry ToggleNoPlacementCost = null; internal static ConfigEntry ToggleDebugFly = null; internal static ConfigEntry ToggleGhostMode = null; internal static ConfigEntry DebugModeHeal = null; internal static ConfigEntry DebugModeAddRested = null; internal static ConfigEntry ToggleNoPlacementCostKeybind = null; internal static ConfigEntry ToggleDebugFlyKeybind = null; internal static ConfigEntry ToggleGhostModeKeybind = null; internal static ConfigEntry DebugModeHealKeybind = null; internal static ConfigEntry DebugModeAddRestedKeybind = null; internal static ConfigEntry EnemyStaggerDamageDealtMultiplier = null; internal static ConfigEntry EnemyKnockbackDealtMultiplier = null; internal static ConfigEntry EnemySpeedMultiplier = null; internal static ConfigEntry EnemyAttackAnimationSpeedMultiplier = null; internal static ConfigEntry EnemyMovementAnimationSpeedMultiplier = null; internal static ConfigEntry EnemySizeMultiplier = null; internal static ConfigEntry EnemyAttackHitRayRangeMultiplier = null; internal static ConfigEntry EnemyAttackHitRaySizeMultiplier = null; internal static ConfigEntry EnemyAttackHitRayHeightMultiplier = null; internal static ConfigEntry EnemyAttackHitRayOffsetMultiplier = null; internal static ConfigEntry EnemyBaseRotationFactor = null; internal static ConfigEntry EnemyAttackRotationFactor = null; internal static ConfigEntry EnemyLockRotationAfterHitRay = null; internal static ConfigEntry EnemyPoiseMultiplier = null; internal static ConfigEntry EnemyStaggerDecaySpeedMultiplier = null; internal static ConfigEntry EnemyStaggerDecayCooldownSeconds = null; internal static ConfigEntry UseCustomLightArmorMovementModifier = null; internal static ConfigEntry UseCustomMediumArmorMovementModifier = null; internal static ConfigEntry UseCustomHeavyArmorMovementModifier = null; internal static ConfigEntry UseCustomOneHandedSwordMovementModifier = null; internal static ConfigEntry UseCustomOneHandedAxeMovementModifier = null; internal static ConfigEntry UseCustomClubMovementModifier = null; internal static ConfigEntry UseCustomKnifeMovementModifier = null; internal static ConfigEntry UseCustomDualKnivesMovementModifier = null; internal static ConfigEntry UseCustomSpearMovementModifier = null; internal static ConfigEntry UseCustomAtgeirMovementModifier = null; internal static ConfigEntry UseCustomTwoHandedSwordMovementModifier = null; internal static ConfigEntry UseCustomTwoHandedAxeMovementModifier = null; internal static ConfigEntry UseCustomSledgeMovementModifier = null; internal static ConfigEntry UseCustomDualAxesMovementModifier = null; internal static ConfigEntry UseCustomBowMovementModifier = null; internal static ConfigEntry UseCustomCrossbowMovementModifier = null; internal static ConfigEntry UseCustomToolMovementModifier = null; internal static ConfigEntry UseCustomOtherEquipmentMovementModifier = null; internal static ConfigEntry LightArmorMovementModifierPercent = null; internal static ConfigEntry MediumArmorMovementModifierPercent = null; internal static ConfigEntry HeavyArmorMovementModifierPercent = null; internal static ConfigEntry OneHandedSwordMovementModifierPercent = null; internal static ConfigEntry OneHandedAxeMovementModifierPercent = null; internal static ConfigEntry ClubMovementModifierPercent = null; internal static ConfigEntry KnifeMovementModifierPercent = null; internal static ConfigEntry DualKnivesMovementModifierPercent = null; internal static ConfigEntry SpearMovementModifierPercent = null; internal static ConfigEntry AtgeirMovementModifierPercent = null; internal static ConfigEntry TwoHandedSwordMovementModifierPercent = null; internal static ConfigEntry TwoHandedAxeMovementModifierPercent = null; internal static ConfigEntry SledgeMovementModifierPercent = null; internal static ConfigEntry DualAxesMovementModifierPercent = null; internal static ConfigEntry BowMovementModifierPercent = null; internal static ConfigEntry CrossbowMovementModifierPercent = null; internal static ConfigEntry ToolMovementModifierPercent = null; internal static ConfigEntry OtherEquipmentMovementModifierPercent = null; internal static readonly Dictionary CategoryConfigs = new Dictionary(); internal static readonly Dictionary> AttackStartNoiseMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackHitNoiseMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackRangeMultipliers = new Dictionary>(); internal static readonly Dictionary> OneHandedSwordPrefabHitRayRangeMultipliers = new Dictionary>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary> SpearPrefabHitRayRangeMultipliers = new Dictionary>(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary> AttackRayWidthMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackHitboxHeightMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackOffsetMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackAngleMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackHitboxTypes = new Dictionary>(); internal static readonly Dictionary> AttackMultiTargetDamagePenaltyModes = new Dictionary>(); internal static readonly Dictionary> AttackMultiHitEnabled = new Dictionary>(); internal static readonly Dictionary> AttackHitThroughTargetsEnabled = new Dictionary>(); internal static readonly Dictionary> AttackHitThroughWallsEnabled = new Dictionary>(); internal static readonly Dictionary> RunningAttackMultiHitEnabled = new Dictionary>(); internal static readonly Dictionary> RunningAttackHitThroughTargetsEnabled = new Dictionary>(); internal static readonly Dictionary> RunningAttackHitThroughWallsEnabled = new Dictionary>(); internal static readonly Dictionary> JumpAttackMultiHitEnabled = new Dictionary>(); internal static readonly Dictionary> JumpAttackHitThroughTargetsEnabled = new Dictionary>(); internal static readonly Dictionary> JumpAttackHitThroughWallsEnabled = new Dictionary>(); internal static readonly Dictionary> AttackBuildingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackStaggeredEnemyDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> BackstabDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackFlankingDamageEnabled = new Dictionary>(); internal static readonly Dictionary> AttackFlankingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackPvpFlankingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackFlankingDingEnabled = new Dictionary>(); internal static readonly Dictionary> WeaponEffectiveBlockAngleMultipliers = new Dictionary>(); internal static readonly HashSet EnemyRotationLockedAfterHitRayAttacks = new HashSet(); internal static readonly Dictionary> RunningAttackBuildingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> JumpAttackBuildingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> RunningAttackStaggeredEnemyDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> JumpAttackStaggeredEnemyDamageMultipliers = new Dictionary>(); internal static readonly Dictionary RunningAttackConfigs = new Dictionary(); internal static readonly Dictionary JumpAttackConfigs = new Dictionary(); internal static readonly Dictionary> BlockCancelModes = new Dictionary>(); internal static readonly Dictionary WeaponBlockExtras = new Dictionary(); internal static readonly Dictionary StaffConfigs = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary StaffBlockConfigs = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static ShieldTypeBlockConfig ShieldBucklerBlockConfig = null; internal static ShieldTypeBlockConfig ShieldMediumBlockConfig = null; internal static ShieldTypeBlockConfig ShieldTowerBlockConfig = null; internal static ConfigEntry ShieldBucklerEffectiveBlockAngleMultiplier = null; internal static ConfigEntry ShieldMediumEffectiveBlockAngleMultiplier = null; internal static ConfigEntry ShieldTowerEffectiveBlockAngleMultiplier = null; internal static readonly Dictionary> StaffEffectiveBlockAngleMultipliers = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary EffectiveBlockAngleOriginalHitDirs = new Dictionary(); internal static StaffShieldActiveConfig StaffShieldActiveSettings = null; internal static ConfigEntry StaffGreenRootsAttackPlayersInPvp = null; internal static ConfigEntry StaffSkeletonAttackPlayersInPvp = null; internal static ConfigEntry StaffLightningReloadSpeedMultiplier = null; internal static ConfigEntry StaffLightningProjectileCountMultiplier = null; internal static ConfigEntry StaffGreenRootsVineDamageMultiplier = null; internal static ConfigEntry StaffGreenRootsVineSizeMultiplier = null; internal static ConfigEntry StaffGreenRootsVineAttackSpeedMultiplier = null; internal static ConfigEntry StaffGreenRootsVineLevel = null; internal static ConfigEntry StaffGreenRootsMaxVinesSpawned = null; internal static ConfigEntry StaffSkeletonSizeMultiplier = null; internal static ConfigEntry StaffSkeletonAttackSpeedMultiplier = null; internal static ConfigEntry StaffSkeletonLevel = null; internal static ConfigEntry StaffSkeletonBaseSpawnCapLevel1 = null; internal static ConfigEntry StaffSkeletonBaseSpawnCapLevel2 = null; internal static ConfigEntry StaffSkeletonBaseSpawnCapLevel3 = null; internal static ConfigEntry StaffSkeletonBaseSpawnCapLevel4 = null; internal static ConfigEntry StaffSkeletonHealthConsumptionRate = null; private static readonly Dictionary StaffSpawnAbilityOrigins = new Dictionary(); private static readonly Dictionary StaffSpawnCharacterOrigins = new Dictionary(); private static readonly HashSet StaffGreenRootsRuntimeTweakedCharacters = new HashSet(); private static readonly HashSet StaffSkeletonRuntimeTweakedCharacters = new HashSet(); private static readonly Dictionary> StaffGreenRootsByOwner = new Dictionary>(); private static readonly Dictionary StaffGreenRootOwnerByCharacter = new Dictionary(); private static readonly Dictionary StaffSpawnProjectileOrigins = new Dictionary(); private static readonly Dictionary StaffSpawnAoeOrigins = new Dictionary(); private static readonly Dictionary ProjectileAttackContexts = new Dictionary(); private static readonly Dictionary SpearLoyaltyConsumeItemOriginals = new Dictionary(); private static readonly HashSet SledgeAlternateMovesetAttacks = new HashSet(); private static readonly HashSet AlternateSledgeSecondaryRequests = new HashSet(); private static readonly Dictionary> AlternateSledgeHitRayContexts = new Dictionary>(); [ThreadStatic] private static EffectList? CurrentRpcDamageCritEffectListToSuppress; [ThreadStatic] private static int CurrentRpcDamageCritEffectSuppressionDepth; private static readonly ConditionalWeakTable SneakXpAwardedHits = new ConditionalWeakTable(); private static readonly Dictionary BetterLightningOriginalAttackSpawnStates = new Dictionary(); private static GameObject? CachedLightningChainSpawnOnHitTemplate; private static bool LightningChainSpawnOnHitLookupAttempted; private static object? CachedHimminAflHitEffectTemplate; private static bool HimminAflHitEffectLookupAttempted; private static EffectList? CachedAlternateSledgeTwoHandedAxeHitEffect; private static bool AlternateSledgeTwoHandedAxeHitEffectLookupAttempted; private static readonly ConditionalWeakTable LightningVisualEffectHits = new ConditionalWeakTable(); private static readonly Dictionary AoeAttackContexts = new Dictionary(); private static readonly Dictionary AoeParryStaggerSources = new Dictionary(); [ThreadStatic] private static Stack? CurrentAttackHitContexts; [ThreadStatic] private static object? CurrentMeleeRayAttackInstance; [ThreadStatic] private static GameObject? CurrentMeleeRayHitObject; [ThreadStatic] private static StaffSpawnOrigin? CurrentStaffSpawnDamageOrigin; [ThreadStatic] private static StaffConfig? CurrentStaffFireballAoeSource; [ThreadStatic] private static StaffSpawnOrigin? CurrentSpawnAbilityAoeSetupOrigin; [ThreadStatic] private static StaffSpawnOrigin? CurrentSpawnAbilityRuntimeOrigin; [ThreadStatic] private static Stack? CurrentIncomingParryStaggerSources; [ThreadStatic] private static Stack? CurrentParryStaggerSuppressionFrames; private static readonly Dictionary AttackStates = new Dictionary(); private static readonly Dictionary AttackInstanceOwners = new Dictionary(); private static readonly Dictionary AttackInstanceWeapons = new Dictionary(); private static readonly Dictionary AttackInstanceRoles = new Dictionary(); private static Attack? CachedTwoHandedAxePrimaryAttackTemplate; private static bool TwoHandedAxePrimaryAttackTemplateLookupAttempted; private static Attack? CachedTwoHandedAxeSecondaryAttackTemplate; private static bool TwoHandedAxeSecondaryAttackTemplateLookupAttempted; private static readonly Dictionary AttackInstanceStartedAirborne = new Dictionary(); private static readonly Dictionary AttackInstanceStartedWhileAlreadyInAttack = new Dictionary(); private static readonly HashSet BalancedHyperArmorCompletedAttacks = new HashSet(); private static readonly Dictionary MeleeHitStopContexts = new Dictionary(); private static readonly Dictionary HyperArmorStates = new Dictionary(); private static readonly Dictionary PendingAttackRoles = new Dictionary(); private static readonly Dictionary CounterVulnerableStates = new Dictionary(); private static readonly HashSet RunningAttackInstances = new HashSet(); private static readonly Dictionary RunningAttackForcedChainLevels = new Dictionary(); private static readonly HashSet JumpAttackInstances = new HashSet(); private static readonly Dictionary JumpAttackForcedChainLevels = new Dictionary(); private static readonly HashSet BetterFreeAimPreAppliedAttacks = new HashSet(); private static readonly HashSet BetterFreeAimActiveBlocks = new HashSet(); private static readonly HashSet LungeHitboxTriggeredAttacks = new HashSet(); private static readonly HashSet RecoveryAnimationSpeedAppliedAttacks = new HashSet(); private static readonly HashSet BlockCanceledAttacks = new HashSet(); private static readonly HashSet BlockCancelRequestedAttacks = new HashSet(); private static readonly Dictionary BlockCancelAnimationSpeedByAttack = new Dictionary(); private static readonly Dictionary> AttackMovementSpeedContexts = new Dictionary>(); private static readonly Dictionary> AttackRotationContexts = new Dictionary>(); private static readonly Dictionary> AttackRangedFieldContexts = new Dictionary>(); private static readonly Dictionary> AttackAnimationSpeedContexts = new Dictionary>(); private static readonly Dictionary> DodgeAnimationSpeedContexts = new Dictionary>(); private static readonly Dictionary PlayerEitrBeforeMaxClamp = new Dictionary(); private static readonly Dictionary CharacterAnimationSpeedByCharacter = new Dictionary(); private static readonly Dictionary PlayerRuntimeDefaultsByPlayer = new Dictionary(); private static readonly Dictionary CharacterRuntimeDefaultsByCharacter = new Dictionary(); private static readonly Dictionary StaggerDecayCooldownUntil = new Dictionary(); private static readonly Dictionary NegativeStaminaBeforeMaxClamp = new Dictionary(); private static readonly Dictionary DebugLogThrottle = new Dictionary(); private static readonly Dictionary FieldCache = new Dictionary(); private static readonly Dictionary MethodCache = new Dictionary(); private static readonly ConditionalWeakTable OutgoingDamageCalibratedHits = new ConditionalWeakTable(); private static readonly ConditionalWeakTable HitSkillDamageFactors = new ConditionalWeakTable(); private static readonly ConditionalWeakTable PvpTestDummyArmorAppliedHits = new ConditionalWeakTable(); private static readonly ConditionalWeakTable PvpTestDummyDebugHits = new ConditionalWeakTable(); private static readonly ConditionalWeakTable DamageBonusStackStates = new ConditionalWeakTable(); private static readonly ConditionalWeakTable CombatDingStates = new ConditionalWeakTable(); private static readonly ConditionalWeakTable StaffFireballAoeRadiusApplied = new ConditionalWeakTable(); private static readonly ConditionalWeakTable StaffFireballProjectileAoeRadiusApplied = new ConditionalWeakTable(); private static readonly ConditionalWeakTable StaffFireballProjectileSources = new ConditionalWeakTable(); private static ItemData? CurrentPvpBlockArmorContextBlocker; private static int CurrentPvpBlockArmorContextDepth; private static float CurrentPvpBlockArmorContextMultiplier = 1f; private static float CurrentPvpBlockForceContextMultiplier = 1f; private static float CurrentPvpParryBonusContextMultiplier = 1f; private static Player? CurrentBlockAdrenalineContextPlayer; private static ItemData? CurrentBlockAdrenalineContextBlocker; private static int CurrentBlockAdrenalineContextDepth; private static readonly Dictionary ParryWindowBlockTimerOriginals = new Dictionary(); private static readonly Dictionary PendingPvpStaggerDurations = new Dictionary(); private static readonly ConditionalWeakTable PvpStaggerDurationsByHit = new ConditionalWeakTable(); private static readonly Dictionary SoulsLikeBlockBreakStates = new Dictionary(); private static readonly Dictionary SoulsLikeBlockStaminaStates = new Dictionary(); private static readonly Dictionary PvpStaggerSpeedCoroutines = new Dictionary(); private const string PvpTestDummyPrefabName = "TrainingDummy"; private const string PvpTestDummyZdoFlag = "GCO_PvPTestDummy"; private const string PvpTestDummyZdoHealth = "GCO_TestDummyHealth"; private const string PvpTestDummyZdoArmor = "GCO_TestDummyArmor"; private const string PvpTestDummyZdoStaggerThresholdMultiplier = "GCO_TestDummyStaggerThresholdMultiplier"; private const string PvpTestDummyZdoStaggerFactor = "GCO_TestDummyStaggerFactor"; private const string StaffGreenRootOwnerIdZdo = "GCO_StaffGreenRootOwnerId"; private const string StaffGreenRootSpawnTicksZdo = "GCO_StaffGreenRootSpawnTicks"; private static readonly (string Section, string Label, string KeySuffix)[] WeaponPresetSections = new(string, string, string)[16] { ("Two-Handed Swords", "two-handed swords", "TwoHandedSwords"), ("Two-Handed Axes", "two-handed axes", "TwoHandedAxes"), ("Sledges", "sledges", "Sledges"), ("Pickaxes", "pickaxes", "Pickaxes"), ("Dual Axes", "dual axes", "DualAxes"), ("Atgeirs", "atgeirs", "Atgeirs"), ("One-Handed Swords", "one-handed swords", "OneHandedSwords"), ("One-Handed Axes", "one-handed axes", "OneHandedAxes"), ("Clubs and Maces", "clubs and maces", "ClubsAndMaces"), ("Knives", "knives", "Knives"), ("Dual Knives", "dual knives", "DualKnives"), ("Spears", "spears", "Spears"), ("Bows", "bows", "Bows"), ("Crossbows", "crossbows", "Crossbows"), ("Unarmed", "unarmed", "Unarmed"), ("Other", "other weapons", "Other") }; private static readonly (WeaponCategory Category, string Label, string KeySuffix)[] FeatureToggleWeapons = new(WeaponCategory, string, string)[16] { (WeaponCategory.TwoHandedSword, "2H sword", "TwoHandedSword"), (WeaponCategory.TwoHandedAxe, "2H axe", "TwoHandedAxe"), (WeaponCategory.Sledge, "sledge", "Sledge"), (WeaponCategory.Pickaxe, "pickaxe", "Pickaxe"), (WeaponCategory.DualAxes, "dual axes", "DualAxes"), (WeaponCategory.Atgeir, "atgeir", "Atgeir"), (WeaponCategory.OneHandedSword, "1H sword", "OneHandedSword"), (WeaponCategory.OneHandedAxe, "1H axe", "OneHandedAxe"), (WeaponCategory.Club, "club/mace", "ClubMace"), (WeaponCategory.Knife, "knife", "Knife"), (WeaponCategory.DualKnives, "dual knives", "DualKnives"), (WeaponCategory.Spear, "spear", "Spear"), (WeaponCategory.Bow, "bow", "Bow"), (WeaponCategory.Crossbow, "crossbow", "Crossbow"), (WeaponCategory.Unarmed, "unarmed", "Unarmed"), (WeaponCategory.Other, "other weapons", "Other") }; private static readonly WeaponCategory[] AllFeatureWeaponCategories = new WeaponCategory[16] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.Sledge, WeaponCategory.Pickaxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Bow, WeaponCategory.Crossbow, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly WeaponCategory[] StaggerFeatureWeaponCategories = new WeaponCategory[16] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.Sledge, WeaponCategory.Pickaxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Bow, WeaponCategory.Crossbow, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly WeaponCategory[] MeleeCombatFeatureWeaponCategories = new WeaponCategory[14] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.Sledge, WeaponCategory.Pickaxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly WeaponCategory[] DirectHitRayWeaponCategories = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly WeaponCategory[] HitRayFeatureWeaponCategories = new WeaponCategory[13] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.Sledge, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly WeaponCategory[] StealthAttributeWeaponCategories = new WeaponCategory[16] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.Sledge, WeaponCategory.Pickaxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Bow, WeaponCategory.Crossbow, WeaponCategory.Unarmed, WeaponCategory.Other }; private static readonly Dictionary> FeatureGlobalToggles = new Dictionary>(); private static readonly Dictionary> FeatureWeaponToggles = new Dictionary>(); private static MethodInfo? _hitDataGetAttacker; private static MethodInfo? CharacterInAttackMethod; private static readonly Dictionary PrettyConfigKeyCache = new Dictionary(StringComparer.Ordinal); private static readonly string[] ConfigWeaponSectionOrder = new string[18] { "Unarmed", "Knives", "Dual Knives", "One-Handed Swords", "One-Handed Axes", "Clubs and Maces", "Spears", "Atgeirs", "Dual Axes", "Two-Handed Swords", "Two-Handed Axes", "Sledges", "Pickaxes", "Bows", "Crossbows", "Throwables", "Magic", "Other" }; private static readonly string[] ConfigStaffSectionOrder = new string[8] { "Magic - Staff of Embers", "Magic - Staff of Frost", "Magic - Dundr", "Magic - Staff of the Wild", "Magic - Dead Raiser", "Magic - Staff of Protection", "Magic - Staff of Fracturing", "Magic - Trollstav" }; private static readonly Dictionary LegacyTitleConfigKeyCache = new Dictionary(StringComparer.Ordinal); private static Type? CachedZInputType; private static MethodInfo? CachedZInputGetKeyDownMethod; private static MethodInfo? CachedZInputGetButtonMethod; private static bool CachedZInputLookedUp; private const float GcoEnabledParryBaseBonus = 1.5f; private const float VanillaStaggeredEnemyDamageMultiplier = 2f; private static readonly AttackRole[] ConfigurableAttackRoles = new AttackRole[5] { AttackRole.Primary1, AttackRole.Primary2, AttackRole.Primary3, AttackRole.Primary4, AttackRole.Secondary }; internal static GooCombatOverhaulPlugin Instance { get; private set; } = null; internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; private static bool ModActive { get { if (Enabled != null) { return Enabled.Value; } return false; } } private void Awake() { Instance = this; BindConfig(); AuditFeatureToggleCoverage(); _hitDataGetAttacker = AccessTools.Method(typeof(HitData), "GetAttacker", (Type[])null, (Type[])null); PatchCoreDamageAndStagger(); PatchAttackLifecycle(); PatchPlayerAndSystemTuning(); PatchStaffSpawnAndProjectileSystems(); PatchMultiplayerConfigSync(); EnsureConsoleCommandCoverage(); RegisterGcoFlatConsoleCommands(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate(object _, SettingChangedEventArgs eventArgs) { bool flag = HandleGcoConfigSettingChanged(eventArgs); if (_configRuntimeRefreshBatchActive) { _configRuntimeRefreshBatchPending = true; } else if (!flag) { ResetRuntimeStateAfterConfigChange(); } }; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Goo's Combat Overhaul 1.4.0 loaded."); } private static void BeginConfigRuntimeRefreshBatch() { _configRuntimeRefreshBatchActive = true; } private static void EndConfigRuntimeRefreshBatch() { _configRuntimeRefreshBatchActive = false; if (_configRuntimeRefreshBatchPending) { _configRuntimeRefreshBatchPending = false; ResetRuntimeStateAfterConfigChange(); } } private static void ResetRuntimeStateAfterConfigChange() { AttackStates.Clear(); AttackInstanceOwners.Clear(); AttackInstanceWeapons.Clear(); AttackInstanceRoles.Clear(); AttackInstanceStartedAirborne.Clear(); AttackInstanceStartedWhileAlreadyInAttack.Clear(); BalancedHyperArmorCompletedAttacks.Clear(); MeleeHitStopContexts.Clear(); HyperArmorStates.Clear(); PendingAttackRoles.Clear(); CounterVulnerableStates.Clear(); RunningAttackInstances.Clear(); RunningAttackForcedChainLevels.Clear(); JumpAttackInstances.Clear(); JumpAttackForcedChainLevels.Clear(); BetterFreeAimPreAppliedAttacks.Clear(); BetterFreeAimActiveBlocks.Clear(); LungeHitboxTriggeredAttacks.Clear(); BlockCanceledAttacks.Clear(); BlockCancelRequestedAttacks.Clear(); BlockCancelAnimationSpeedByAttack.Clear(); RestoreAllAttackMovementSpeedTweaks(); RestoreAllAttackRotationTweaks(); RestoreAllAttackRangedFieldTweaks(); RestoreAllAttackAnimationSpeedTweaks(); RestoreAllDodgeAnimationSpeedTweaks(); NegativeStaminaBeforeMaxClamp.Clear(); PendingPvpStaggerDurations.Clear(); SoulsLikeBlockBreakStates.Clear(); SoulsLikeBlockStaminaStates.Clear(); RestoreParryWindowBlockTimers(); StaffSpawnAbilityOrigins.Clear(); StaffSpawnCharacterOrigins.Clear(); StaffGreenRootsRuntimeTweakedCharacters.Clear(); StaffSkeletonRuntimeTweakedCharacters.Clear(); StaffSpawnProjectileOrigins.Clear(); StaffSpawnAoeOrigins.Clear(); ProjectileAttackContexts.Clear(); SpearLoyaltyConsumeItemOriginals.Clear(); SledgeAlternateMovesetAttacks.Clear(); AlternateSledgeSecondaryRequests.Clear(); RestoreAllAlternateSledgeHitRayTweaks(); AoeAttackContexts.Clear(); AoeParryStaggerSources.Clear(); CurrentStaffFireballAoeSource = null; StopAllPvpStaggerSpeedCoroutines(); } private void OnDestroy() { RestoreClientLocalConfigAfterSync("plugin shutdown"); SetClientGuestDebugAuthority(limited: false, "plugin shutdown"); _harmony.UnpatchSelf(); AttackStates.Clear(); AttackInstanceOwners.Clear(); AttackInstanceWeapons.Clear(); AttackInstanceRoles.Clear(); AttackInstanceStartedAirborne.Clear(); AttackInstanceStartedWhileAlreadyInAttack.Clear(); BalancedHyperArmorCompletedAttacks.Clear(); MeleeHitStopContexts.Clear(); HyperArmorStates.Clear(); PendingAttackRoles.Clear(); CounterVulnerableStates.Clear(); RunningAttackInstances.Clear(); RunningAttackForcedChainLevels.Clear(); JumpAttackInstances.Clear(); JumpAttackForcedChainLevels.Clear(); BetterFreeAimActiveBlocks.Clear(); LungeHitboxTriggeredAttacks.Clear(); BlockCanceledAttacks.Clear(); BlockCancelRequestedAttacks.Clear(); BlockCancelAnimationSpeedByAttack.Clear(); RestoreAllAttackMovementSpeedTweaks(); RestoreAllAttackRotationTweaks(); RestoreAllAttackRangedFieldTweaks(); RestoreAllAttackAnimationSpeedTweaks(); RestoreAllDodgeAnimationSpeedTweaks(); RestoreAllCharacterRuntimeTweaks(); PlayerRuntimeDefaultsByPlayer.Clear(); CharacterRuntimeDefaultsByCharacter.Clear(); StaggerDecayCooldownUntil.Clear(); NegativeStaminaBeforeMaxClamp.Clear(); PendingPvpStaggerDurations.Clear(); SoulsLikeBlockBreakStates.Clear(); SoulsLikeBlockStaminaStates.Clear(); RestoreParryWindowBlockTimers(); StaffSpawnAbilityOrigins.Clear(); StaffSpawnCharacterOrigins.Clear(); StaffGreenRootsRuntimeTweakedCharacters.Clear(); StaffSkeletonRuntimeTweakedCharacters.Clear(); StaffGreenRootsByOwner.Clear(); StaffGreenRootOwnerByCharacter.Clear(); StaffSpawnProjectileOrigins.Clear(); StaffSpawnAoeOrigins.Clear(); ProjectileAttackContexts.Clear(); SpearLoyaltyConsumeItemOriginals.Clear(); SledgeAlternateMovesetAttacks.Clear(); AlternateSledgeSecondaryRequests.Clear(); RestoreAllAlternateSledgeHitRayTweaks(); AoeAttackContexts.Clear(); AoeParryStaggerSources.Clear(); CurrentStaffFireballAoeSource = null; StopAllPvpStaggerSpeedCoroutines(); ResetPvpBlockArmorContext(); } private void Update() { if (!ModActive) { if (_lastGooCombatDebugModeActive) { ForceDisableGooDebugPlayerToggles(Player.m_localPlayer); _lastGooCombatDebugModeActive = false; } AttackStates.Clear(); AttackInstanceOwners.Clear(); AttackInstanceWeapons.Clear(); AttackInstanceRoles.Clear(); AttackInstanceStartedAirborne.Clear(); AttackInstanceStartedWhileAlreadyInAttack.Clear(); BalancedHyperArmorCompletedAttacks.Clear(); MeleeHitStopContexts.Clear(); HyperArmorStates.Clear(); PendingAttackRoles.Clear(); CounterVulnerableStates.Clear(); RunningAttackInstances.Clear(); RunningAttackForcedChainLevels.Clear(); JumpAttackInstances.Clear(); JumpAttackForcedChainLevels.Clear(); BetterFreeAimPreAppliedAttacks.Clear(); BetterFreeAimActiveBlocks.Clear(); LungeHitboxTriggeredAttacks.Clear(); BlockCanceledAttacks.Clear(); BlockCancelRequestedAttacks.Clear(); BlockCancelAnimationSpeedByAttack.Clear(); RestoreAllAttackMovementSpeedTweaks(); RestoreAllAttackRotationTweaks(); RestoreAllAttackRangedFieldTweaks(); RestoreAllAttackAnimationSpeedTweaks(); RestoreAllDodgeAnimationSpeedTweaks(); RestoreAllCharacterRuntimeTweaks(); StaggerDecayCooldownUntil.Clear(); NegativeStaminaBeforeMaxClamp.Clear(); PendingPvpStaggerDurations.Clear(); SoulsLikeBlockBreakStates.Clear(); SoulsLikeBlockStaminaStates.Clear(); RestoreParryWindowBlockTimers(); StaffSpawnAbilityOrigins.Clear(); StaffSpawnCharacterOrigins.Clear(); StaffGreenRootsRuntimeTweakedCharacters.Clear(); StaffSkeletonRuntimeTweakedCharacters.Clear(); StaffSpawnProjectileOrigins.Clear(); StaffSpawnAoeOrigins.Clear(); ProjectileAttackContexts.Clear(); SpearLoyaltyConsumeItemOriginals.Clear(); SledgeAlternateMovesetAttacks.Clear(); AlternateSledgeSecondaryRequests.Clear(); RestoreAllAlternateSledgeHitRayTweaks(); AoeAttackContexts.Clear(); AoeParryStaggerSources.Clear(); CurrentStaffFireballAoeSource = null; StopAllPvpStaggerSpeedCoroutines(); ResetPvpBlockArmorContext(); } else { ProcessDebugTestDummyToggles(); ProcessGooDebugKeybinds(); ProcessGooCombatDebugMode(); ProcessGcoGeneralDebugLogging(); ProcessGcoVerboseDebugLogging(); } } private static void ProcessGcoGeneralDebugLogging() { if (!Debug()) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } try { string text = "none"; ItemData currentWeapon = GetCurrentWeapon((Character)(object)localPlayer); if (currentWeapon != null && currentWeapon.m_shared != null) { text = LocalizeTokenForDebug(currentWeapon.m_shared.m_name); } bool flag = false; bool flag2 = false; bool flag3 = false; try { flag = ((Character)localPlayer).InAttack(); } catch { } try { flag2 = ((Character)localPlayer).IsBlocking(); } catch { } try { flag3 = ((Character)localPlayer).InDodge(); } catch { } float num = 0f; float num2 = 0f; float num3 = 0f; float num4 = 0f; float num5 = 0f; float num6 = 0f; try { num = ((Character)localPlayer).GetHealth(); num2 = ((Character)localPlayer).GetMaxHealth(); } catch { } try { num3 = localPlayer.GetStamina(); num4 = ((Character)localPlayer).GetMaxStamina(); } catch { } try { num5 = localPlayer.GetEitr(); num6 = ((Character)localPlayer).GetMaxEitr(); } catch { } DebugLogThrottled("general-player-status", 1f, $"[GCO Debug] Player hp={num:0.#}/{num2:0.#} stam={num3:0.#}/{num4:0.#} eitr={num5:0.#}/{num6:0.#} attack={flag} block={flag2} dodge={flag3} weapon={text}"); } catch { } } private static void ProcessGcoVerboseDebugLogging() { //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) if (VerboseDebugLogging == null || !VerboseDebugLogging.Value || DebugLogging == null || !DebugLogging.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } try { ItemData currentWeapon = GetCurrentWeapon((Character)(object)localPlayer); string text = ((currentWeapon != null && currentWeapon.m_shared != null) ? LocalizeTokenForDebug(currentWeapon.m_shared.m_name) : "none"); WeaponCategory weaponCategory = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Other); AttackRuntimeState value; AttackRuntimeState attackRuntimeState = (AttackStates.TryGetValue((Character)(object)localPlayer, out value) ? value : default(AttackRuntimeState)); bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; try { flag = ((Character)localPlayer).InAttack(); } catch { } try { flag2 = ((Character)localPlayer).IsBlocking(); } catch { } try { flag3 = ((Character)localPlayer).InDodge(); } catch { } try { flag4 = ((Character)localPlayer).IsOnGround(); } catch { } try { flag5 = GetPrivateBool(localPlayer, "m_debugFly", fallback: false); } catch { } try { flag6 = GetPrivateBool(localPlayer, "m_ghostMode", fallback: false); } catch { } try { flag7 = GetPrivateBool(localPlayer, "m_noPlacementCost", fallback: false); } catch { } try { flag8 = ((Character)localPlayer).InGodMode(); } catch { } float num = 0f; float num2 = 0f; float num3 = 0f; float num4 = 0f; float num5 = 0f; float num6 = 0f; try { num = ((Character)localPlayer).GetHealth(); num2 = ((Character)localPlayer).GetMaxHealth(); } catch { } try { num3 = localPlayer.GetStamina(); num4 = ((Character)localPlayer).GetMaxStamina(); } catch { } try { num5 = localPlayer.GetEitr(); num6 = ((Character)localPlayer).GetMaxEitr(); } catch { } Vector3 val = Vector3.zero; Vector3 val2 = Vector3.zero; try { val = ((Component)localPlayer).transform.position; } catch { } try { val2 = ((Character)localPlayer).GetVelocity(); } catch { } string text2 = "none"; try { List list = new List(); if (TryZInputGetButton("Forward")) { list.Add("W"); } if (TryZInputGetButton("Backward")) { list.Add("S"); } if (TryZInputGetButton("Left")) { list.Add("A"); } if (TryZInputGetButton("Right")) { list.Add("D"); } if (TryZInputGetButton("Block")) { list.Add("Block"); } if (TryZInputGetButton("Attack")) { list.Add("Attack"); } if (TryZInputGetButton("AltKeys")) { list.Add("AltKeys"); } text2 = ((list.Count == 0) ? "none" : string.Join("+", list)); } catch { } string text3 = ((attackRuntimeState.AttackInstance == null) ? "none" : $"{attackRuntimeState.Category}/{attackRuntimeState.Role}/hash={RuntimeHelpers.GetHashCode(attackRuntimeState.AttackInstance)}"); DebugLogThrottled("verbose-debug-status:" + ((Object)localPlayer).GetInstanceID(), 1f, $"[GCO Verbose] pos=({val.x:0.0},{val.y:0.0},{val.z:0.0}) vel={((Vector3)(ref val2)).magnitude:0.00} grounded={flag4} hp={num:0.#}/{num2:0.#} stam={num3:0.#}/{num4:0.#} eitr={num5:0.#}/{num6:0.#} weapon={text} category={weaponCategory} input={text2} attack={flag} block={flag2} dodge={flag3} runtime={text3} god={flag8} fly={flag5}/{_gcoDebugFlyDesired} nocost={flag7}/{_gcoNoPlacementCostDesired} ghost={flag6}/{_gcoGhostModeDesired} toggles(fly/noCost/ghost)={IsDebugFlyToggleAllowed()}/{IsDebugNoCostToggleAllowed()}/{IsDebugGhostToggleAllowed()}"); } catch (Exception ex) { DebugLogThrottled("verbose-debug-error", 5f, "[GCO Verbose] failed to collect verbose debug state: " + ex.Message); } } private static string LocalizeTokenForDebug(string token) { if (string.IsNullOrEmpty(token)) { return token; } try { Type type = AccessTools.TypeByName("Localization"); if (type == null) { return token; } object obj = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) ?? type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (obj == null) { return token; } string text = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null)?.Invoke(obj, new object[1] { token }) as string; return string.IsNullOrEmpty(text) ? token : text; } catch { return token; } } private static void ProcessDebugTestDummyToggles() { if (IsGuestDebugAuthorityLimited()) { ResetGuestRestrictedOneShotButtons(); return; } if (SpawnPvpTestDummy != null && SpawnPvpTestDummy.Value) { SpawnPvpTestDummy.Value = false; try { SpawnMarkedPvpTestDummy(); } catch (Exception ex) { Log.LogWarning((object)("Failed to spawn PvP test dummy: " + ex.Message)); } try { ((BaseUnityPlugin)Instance).Config.Save(); } catch { } } if (RemovePvpTestDummies == null || !RemovePvpTestDummies.Value) { return; } RemovePvpTestDummies.Value = false; try { RemoveMarkedPvpTestDummies(); } catch (Exception ex2) { Log.LogWarning((object)("Failed to remove PvP test dummies: " + ex2.Message)); } try { ((BaseUnityPlugin)Instance).Config.Save(); } catch { } } private static void SpawnMarkedPvpTestDummy() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Log.LogWarning((object)"Cannot spawn PvP test dummy: no local player."); return; } if ((Object)(object)ZNetScene.instance == (Object)null) { Log.LogWarning((object)"Cannot spawn PvP test dummy: ZNetScene is not available."); return; } GameObject prefab = ZNetScene.instance.GetPrefab("TrainingDummy"); if ((Object)(object)prefab == (Object)null) { Log.LogWarning((object)"Cannot spawn PvP test dummy: prefab 'TrainingDummy' was not found."); return; } Vector3 forward = ((Component)localPlayer).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.001f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = ((Component)localPlayer).transform.position + forward * 2.5f; val.y = ((Component)localPlayer).transform.position.y + 0.05f; Quaternion val2 = Quaternion.LookRotation(-forward, Vector3.up); GameObject obj = Object.Instantiate(prefab, val, val2); Character component = obj.GetComponent(); ZNetView component2 = obj.GetComponent(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { ZDO zDO = component2.GetZDO(); if (zDO != null) { zDO.Persistent = true; zDO.Set("GCO_PvPTestDummy", true); zDO.Set("GCO_TestDummyHealth", Mathf.Max(1f, PvpTestDummyHealth.Value)); zDO.Set("GCO_TestDummyArmor", Mathf.Max(0f, PvpTestDummyArmor.Value)); zDO.Set("GCO_TestDummyStaggerThresholdMultiplier", Mathf.Max(0.05f, PvpTestDummyStaggerThresholdMultiplier.Value)); zDO.Set("GCO_TestDummyStaggerFactor", Mathf.Max(0.001f, GetPrivateFloat(localPlayer, "m_staggerDamageFactor", 0.4f))); } } if ((Object)(object)component != (Object)null) { EnsurePvpTestDummyBehavior(component); float num = Mathf.Max(1f, PvpTestDummyHealth.Value); component.SetMaxHealth(num); component.SetHealth(num); SetPrivateFloat(component, "m_staggerDamage", 0f); } Log.LogInfo((object)"Spawned GCO PvP test dummy based on vanilla TrainingDummy prefab."); } private static void RemoveMarkedPvpTestDummies() { int num = 0; Character[] array = Object.FindObjectsOfType(); foreach (Character val in array) { if ((Object)(object)val == (Object)null || !IsPvpTestDummy(val)) { continue; } ZNetView component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { if (!component.IsOwner()) { component.ClaimOwnership(); } component.Destroy(); } else { Object.Destroy((Object)(object)((Component)val).gameObject); } num++; } Log.LogInfo((object)$"Removed {num} GCO PvP test dummy instance(s)."); } private static bool TryGetZdo(Character character, out ZDO zdo) { zdo = null; if ((Object)(object)character == (Object)null) { return false; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } zdo = component.GetZDO(); return zdo != null; } private static bool IsPvpTestDummy(Character? character) { if ((Object)(object)character == (Object)null) { return false; } if (!LooksLikeTrainingDummy(character)) { return false; } if (TryGetZdo(character, out ZDO zdo)) { return zdo.GetBool("GCO_PvPTestDummy", false); } return false; } private static bool LooksLikeTrainingDummy(Character character) { if ((Object)(object)character == (Object)null) { return false; } string prefabLikeName = GetPrefabLikeName(((Component)character).gameObject); if (!string.Equals(prefabLikeName, "TrainingDummy", StringComparison.OrdinalIgnoreCase)) { return prefabLikeName.IndexOf("TrainingDummy", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static string GetPrefabLikeName(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return string.Empty; } string text = ((Object)gameObject).name ?? string.Empty; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { text = text.Substring(0, num); } return text.Trim(); } private static bool IsPvpLikeTarget(Character? character) { if (!(character is Player)) { return IsPvpTestDummy(character); } return true; } private static void EnsurePvpTestDummyBehavior(Character character) { if (!((Object)(object)character == (Object)null) && LooksLikeTrainingDummy(character) && IsPvpTestDummy(character)) { if ((Object)(object)((Component)character).GetComponent() == (Object)null) { ((Component)character).gameObject.AddComponent(); } DisableDummyAI(character); ApplyPvpTestDummyConfigToInstance(character, restoreHealth: false); } } private static void DisableDummyAI(Character character) { if ((Object)(object)character == (Object)null) { return; } BaseAI[] components = ((Component)character).GetComponents(); foreach (BaseAI val in components) { if ((Object)(object)val != (Object)null && ((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; } } } private static float GetPvpTestDummyMaxHealth(Character character) { if (TryGetZdo(character, out ZDO zdo)) { return Mathf.Max(1f, zdo.GetFloat("GCO_TestDummyHealth", PvpTestDummyHealth.Value)); } return Mathf.Max(1f, PvpTestDummyHealth.Value); } private static float GetPvpTestDummyArmor(Character character) { if (TryGetZdo(character, out ZDO zdo)) { return Mathf.Max(0f, zdo.GetFloat("GCO_TestDummyArmor", PvpTestDummyArmor.Value)); } return Mathf.Max(0f, PvpTestDummyArmor.Value); } private static float GetPvpTestDummyStaggerThresholdMultiplier(Character character) { if (TryGetZdo(character, out ZDO zdo)) { return Mathf.Max(0.05f, zdo.GetFloat("GCO_TestDummyStaggerThresholdMultiplier", PvpTestDummyStaggerThresholdMultiplier.Value)); } return Mathf.Max(0.05f, PvpTestDummyStaggerThresholdMultiplier.Value); } private static float GetPvpTestDummyStaggerFactor(Character character) { float num = Mathf.Max(0.001f, GetPrivateFloat(character, "m_staggerDamageFactor", 0.4f)); if (TryGetZdo(character, out ZDO zdo)) { return Mathf.Max(0.001f, zdo.GetFloat("GCO_TestDummyStaggerFactor", num)); } return num; } private static void ApplyPvpTestDummyConfigToInstance(Character character, bool restoreHealth) { if (!IsPvpTestDummy(character)) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { ZDO zDO = component.GetZDO(); if (zDO != null) { zDO.Persistent = true; } } if (!((Object)(object)component != (Object)null) || !component.IsValid() || component.IsOwner()) { float pvpTestDummyMaxHealth = GetPvpTestDummyMaxHealth(character); SetPrivateFloat(character, "m_staggerDamageFactor", GetPvpTestDummyStaggerFactor(character)); character.SetMaxHealth(pvpTestDummyMaxHealth); if (restoreHealth || character.GetHealth() <= 1f || character.GetHealth() > pvpTestDummyMaxHealth) { character.SetHealth(pvpTestDummyMaxHealth); } } } private static void ApplyPvpTestDummyArmorIfNeeded(Character victim, Character? attacker, HitData hit) { if (!((Object)(object)victim == (Object)null) && attacker is Player && hit != null && IsPvpTestDummy(victim) && !PvpTestDummyArmorAppliedHits.TryGetValue(hit, out ProcessedHitMarker _)) { try { PvpTestDummyArmorAppliedHits.Add(hit, ProcessedHitMarker.Instance); } catch { } float pvpTestDummyArmor = GetPvpTestDummyArmor(victim); if (pvpTestDummyArmor > 0f) { hit.ApplyArmor(pvpTestDummyArmor); } } } private static string BuildConfigSyncPath(ConfigEntryBase entry) { return (entry.Definition.Section ?? string.Empty) + "\u001f" + (entry.Definition.Key ?? string.Empty); } private static string BuildConfigSyncPath(string section, string key) { return (section ?? string.Empty) + "\u001f" + (key ?? string.Empty); } private static void RegisterConfigEntryForMultiplayerSync(ConfigEntryBase entry) { if (entry != null) { GcoConfigSyncEntries[BuildConfigSyncPath(entry)] = entry; } } private static bool IsMultiplayerSyncEntry(ConfigEntryBase entry) { if (entry == null || entry.SettingType == typeof(KeyCode)) { return false; } string text = entry.Definition.Section ?? string.Empty; string text2 = NormalizeCommandKey(entry.Definition.Key ?? string.Empty); if (text.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { return false; } if (IsRunnableCommandEntry(entry)) { return false; } if (text.Equals("General", StringComparison.OrdinalIgnoreCase)) { switch (text2) { case "configversion": case "settogooruleset": case "settogoosruleset": case "resettovanilla": case "settovanillaruleset": case "broadcastconfigchanges": case "syncconfiginmultiplayer": case "limitguestsauthority": case "roll": return false; } } return true; } private void PatchMultiplayerConfigSync() { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown //IL_01e3: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "OnNewConnection", new Type[1] { typeof(ZNetPeer) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "RPC_PeerInfo", new Type[2] { typeof(ZRpc), typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ZNet), "OnDestroy", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(typeof(ConfigFile), "Save", Type.EmptyTypes, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ZNetOnNewConnectionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ZNet.OnNewConnection(ZNetPeer); multiplayer config sync RPC registration is unavailable."); } if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ZNetRpcPeerInfoPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ZNet.RPC_PeerInfo(ZRpc,ZPackage); initial multiplayer config sync is unavailable."); } if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ZNetDisconnectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ZNetOnDestroyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ConfigFileSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ConfigFileSaveFinalizer", (Type[])null), (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ConfigFile.Save(); synchronized clients may save effective host values instead of their local values."); } PatchConfigEntryEffectiveValueGetters(); PatchConfigurationManagerLocalDisplayScope(); } private void PatchConfigEntryEffectiveValueGetters() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(GooCombatOverhaulPlugin), "ConfigEntryValueGetterPostfix", (Type[])null, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find the GCO effective-config getter postfix; host config read-through is unavailable."); return; } foreach (Type item in (from entry in GcoConfigSyncEntries.Values.Distinct() select entry.SettingType).Distinct()) { try { MethodInfo methodInfo2 = typeof(ConfigEntry<>).MakeGenericType(item).GetProperty("Value", BindingFlags.Instance | BindingFlags.Public)?.GetGetMethod(); if (!(methodInfo2 == null) && PatchedConfigEntryValueGetters.Add(methodInfo2)) { MethodInfo methodInfo3 = methodInfo.MakeGenericMethod(item); _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch effective GCO config reads for " + item.FullName + ": " + ex.Message)); } } } public static void ConfigEntryValueGetterPostfix(ConfigEntry __instance, ref T __result) { if (_clientMultiplayerConfigSyncActive && _readClientLocalConfigDepth <= 0 && _configurationManagerLocalDisplayDepth <= 0 && __instance != null && ClientSyncedTypedValues.TryGetValue((ConfigEntryBase)(object)__instance, out object value) && value is T val) { __result = val; } } private void PatchConfigurationManagerLocalDisplayScope() { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_010d: Expected O, but got Unknown int num = 0; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if ((assembly.GetName().Name ?? string.Empty).IndexOf("ConfigurationManager", StringComparison.OrdinalIgnoreCase) < 0) { continue; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type type) => type != null).Cast().ToArray(); } catch { continue; } Type[] array2 = array; for (int num2 = 0; num2 < array2.Length; num2++) { MethodInfo[] methods = array2[num2].GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, "OnGUI", StringComparison.Ordinal) && methodInfo.GetParameters().Length == 0 && !methodInfo.IsAbstract) { try { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ConfigurationManagerLocalDisplayPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ConfigurationManagerLocalDisplayFinalizer", (Type[])null), (HarmonyMethod)null); num++; } catch { } } } } } if (num == 0) { ((BaseUnityPlugin)this).Logger.LogDebug((object)"Configuration Manager OnGUI was not found. This is harmless when Configuration Manager is not installed; otherwise its value fields may show effective host values instead of client-local values while sync is active."); } } public static void ConfigurationManagerLocalDisplayPrefix() { _configurationManagerLocalDisplayDepth++; } public static Exception? ConfigurationManagerLocalDisplayFinalizer(Exception? __exception) { if (_configurationManagerLocalDisplayDepth > 0) { _configurationManagerLocalDisplayDepth--; } return __exception; } public static void ZNetOnNewConnectionPostfix(ZNetPeer peer) { if (peer?.m_rpc == null) { return; } try { peer.m_rpc.Register("GCO_ConfigSync", (Action)ReceiveConfigSyncRpc); peer.m_rpc.Register("GCO_ConfigSyncRequest", (Action)ReceiveConfigSyncRequestRpc); } catch (Exception ex) { try { Log.LogWarning((object)("Could not register GCO config-sync RPCs for a peer: " + ex.Message)); } catch { } } } public static void ZNetRpcPeerInfoPostfix(ZNet __instance, ZRpc rpc) { if (!((Object)(object)__instance == (Object)null) && rpc != null) { if (__instance.IsServer()) { SendConfigSyncToRpc(rpc); } else if (__instance.GetServerRPC() == rpc) { SendConfigSyncRequest(rpc); } } } private static void SendConfigSyncRequest(ZRpc rpc) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown if (rpc == null || !rpc.IsConnected()) { return; } try { ZPackage val = new ZPackage(); val.Write(3); val.Write("1.4.0"); val.Write(GetRawLocalSerializedValue((ConfigEntryBase?)(object)ConfigVersion)); rpc.Invoke("GCO_ConfigSyncRequest", new object[1] { val }); } catch (Exception ex) { try { Log.LogWarning((object)("Could not request the initial GCO host configuration: " + ex.Message)); } catch { } } } private static void ReceiveConfigSyncRequestRpc(ZRpc rpc, ZPackage request) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || rpc == null || request == null) { return; } try { int num = request.ReadInt(); string text = request.ReadString(); string text2 = request.ReadString(); string rawLocalSerializedValue = GetRawLocalSerializedValue((ConfigEntryBase?)(object)ConfigVersion); if (num != 3 || !string.Equals(text, "1.4.0", StringComparison.Ordinal) || !string.Equals(text2, rawLocalSerializedValue, StringComparison.Ordinal)) { string peerDisplayNameForRpc = GetPeerDisplayNameForRpc(rpc); string text3 = string.Format("GCO config sync failed for {0} because of a version mismatch. Host mod/config version: {1} / {2}. Guest mod/config version: {3} / {4}. Host/guest sync protocol: {5} / {6}. The guest will use client-local configuration.", peerDisplayNameForRpc, "1.4.0", rawLocalSerializedValue, text, text2, 3, num); try { Log.LogWarning((object)text3); } catch { } BroadcastGcoChatMessage(text3, force: true); } SendConfigSyncToRpc(rpc); } catch (Exception ex) { try { Log.LogWarning((object)("Could not read a GCO config-sync request: " + ex.Message)); } catch { } SendConfigSyncToRpc(rpc); } } private static string GetPeerDisplayNameForRpc(ZRpc rpc) { try { ZNet instance = ZNet.instance; if ((Object)(object)instance != (Object)null) { foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { if (connectedPeer != null && connectedPeer.m_rpc == rpc) { return string.IsNullOrWhiteSpace(connectedPeer.m_playerName) ? connectedPeer.m_socket.GetEndPointString() : connectedPeer.m_playerName; } } } } catch { } return "a guest"; } public static void ZNetDisconnectPostfix(ZNet __instance, ZNetPeer peer) { if ((Object)(object)__instance != (Object)null && !__instance.IsServer() && peer != null && peer.m_server) { RestoreClientLocalConfigAfterSync("server disconnect"); SetClientGuestDebugAuthority(limited: false, "server disconnect"); } } public static void ZNetOnDestroyPrefix(ZNet __instance) { if ((Object)(object)__instance != (Object)null && !__instance.IsServer()) { RestoreClientLocalConfigAfterSync("network shutdown"); SetClientGuestDebugAuthority(limited: false, "network shutdown"); } } private static string GetRawLocalSerializedValue(ConfigEntryBase? entry) { if (entry == null) { return string.Empty; } _readClientLocalConfigDepth++; try { return entry.GetSerializedValue() ?? string.Empty; } finally { _readClientLocalConfigDepth--; } } private static ZPackage BuildConfigSyncEnvelope() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(3); val.Write("1.4.0"); val.Write(GetRawLocalSerializedValue((ConfigEntryBase?)(object)ConfigVersion)); bool flag = SyncConfigInMultiplayer != null && SyncConfigInMultiplayer.Value; bool flag2 = LimitGuestsAuthority != null && LimitGuestsAuthority.Value; val.Write(flag); val.Write(flag2); if (!flag) { return val; } List list = GcoConfigSyncEntries.Values.Distinct().Where(IsMultiplayerSyncEntry).OrderBy((ConfigEntryBase entry) => entry.Definition.Section, StringComparer.OrdinalIgnoreCase) .ThenBy((ConfigEntryBase entry) => entry.Definition.Key, StringComparer.OrdinalIgnoreCase) .ToList(); ZPackage val2 = new ZPackage(); val2.Write(list.Count); foreach (ConfigEntryBase item in list) { val2.Write(item.Definition.Section ?? string.Empty); val2.Write(item.Definition.Key ?? string.Empty); val2.Write(GetRawLocalSerializedValue(item)); } val.WriteCompressed(val2); return val; } private static void SendConfigSyncToRpc(ZRpc rpc) { if (rpc == null || !rpc.IsConnected()) { return; } try { rpc.Invoke("GCO_ConfigSync", new object[1] { BuildConfigSyncEnvelope() }); } catch (Exception ex) { try { Log.LogWarning((object)("Could not send GCO multiplayer config sync: " + ex.Message)); } catch { } } } private static void BroadcastMultiplayerConfigFromHost() { _configSyncBroadcastPending = false; ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return; } foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { if (connectedPeer != null && connectedPeer.IsReady() && connectedPeer.m_rpc != null) { SendConfigSyncToRpc(connectedPeer.m_rpc); } } } private static void QueueMultiplayerConfigBroadcast(ConfigEntryBase changed) { ZNet instance = ZNet.instance; if (changed != null && !((Object)(object)instance == (Object)null) && instance.IsServer() && ((object)changed == SyncConfigInMultiplayer || (object)changed == LimitGuestsAuthority || IsMultiplayerSyncEntry(changed))) { if (_suppressGcoChatBroadcast || ((Object)(object)Instance != (Object)null && Instance._processingGlobalRulesetButton)) { _configSyncBroadcastPending = true; } else { BroadcastMultiplayerConfigFromHost(); } } } private static void FlushPendingHostConfigSync(bool force = false) { if (force || _configSyncBroadcastPending) { BroadcastMultiplayerConfigFromHost(); } } private static void ReceiveConfigSyncRpc(ZRpc rpc, ZPackage envelope) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer() || rpc == null || envelope == null || instance.GetServerRPC() != rpc) { return; } try { int num = envelope.ReadInt(); string text = envelope.ReadString(); string text2 = envelope.ReadString(); bool num2 = envelope.ReadBool(); bool limited = envelope.ReadBool(); string rawLocalSerializedValue = GetRawLocalSerializedValue((ConfigEntryBase?)(object)ConfigVersion); SetClientGuestDebugAuthority(limited, "host policy update"); if (!num2) { RestoreClientLocalConfigAfterSync("host disabled config sync"); return; } if (num != 3 || !string.Equals(text, "1.4.0", StringComparison.Ordinal) || !string.Equals(text2, rawLocalSerializedValue, StringComparison.Ordinal)) { RestoreClientLocalConfigAfterSync("config sync version mismatch"); ShowConfigSyncVersionMismatchWarning(text, text2, num, rawLocalSerializedValue); return; } ZPackage val = envelope.ReadCompressedPackage(); int num3 = val.ReadInt(); if (num3 < 0 || num3 > 100000) { throw new InvalidDataException("Invalid GCO config-sync entry count: " + num3.ToString(CultureInfo.InvariantCulture)); } Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); for (int i = 0; i < num3; i++) { string text3 = val.ReadString(); string text4 = val.ReadString(); string text5 = val.ReadString(); if (GcoConfigSyncEntries.TryGetValue(BuildConfigSyncPath(text3, text4), out ConfigEntryBase value) && IsMultiplayerSyncEntry(value)) { if (!TryParseSyncedConfigValue(value.SettingType, text5, out object value2) || value2 == null) { throw new InvalidDataException("Could not parse synchronized value for [" + text3 + "] " + text4 + ": " + text5); } dictionary[value] = text5; dictionary2[value] = value2; } } int num4 = GcoConfigSyncEntries.Values.Distinct().Count(IsMultiplayerSyncEntry); if (dictionary.Count != num4) { throw new InvalidDataException("Incomplete GCO config-sync payload: received " + dictionary.Count.ToString(CultureInfo.InvariantCulture) + " of " + num4.ToString(CultureInfo.InvariantCulture) + " expected entries."); } ActivateClientConfigSync(dictionary, dictionary2, text, text2); } catch (Exception ex) { RestoreClientLocalConfigAfterSync("invalid config-sync payload"); string text6 = "GCO config sync failed because the host payload could not be read. Reverting to client-local configuration. " + ex.GetType().Name + ": " + ex.Message; try { Log.LogWarning((object)text6); } catch { } ShowLocalConfigSyncMessage(text6); } } private static bool TryParseSyncedConfigValue(Type type, string serialized, out object? value) { value = null; try { if (type == typeof(string)) { value = serialized; } else if (type == typeof(bool)) { value = bool.Parse(serialized); } else if (type == typeof(int)) { value = int.Parse(serialized, NumberStyles.Integer, CultureInfo.InvariantCulture); } else if (type == typeof(float)) { value = float.Parse(serialized, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); } else { if (!type.IsEnum) { return false; } value = Enum.Parse(type, serialized, ignoreCase: true); } return true; } catch { return false; } } private static void ActivateClientConfigSync(Dictionary hostValues, Dictionary typedValues, string hostModVersion, string hostConfigVersion) { if ((Object)(object)Instance == (Object)null) { return; } bool clientMultiplayerConfigSyncActive = _clientMultiplayerConfigSyncActive; Dictionary previousValues = new Dictionary(ClientSyncedConfigValues); ClientSyncedConfigValues.Clear(); ClientSyncedTypedValues.Clear(); foreach (KeyValuePair hostValue in hostValues) { ClientSyncedConfigValues[hostValue.Key] = hostValue.Value; } foreach (KeyValuePair typedValue in typedValues) { ClientSyncedTypedValues[typedValue.Key] = typedValue.Value; } _clientMultiplayerConfigSyncActive = true; ResetRuntimeStateAfterConfigChange(); if (!clientMultiplayerConfigSyncActive) { string text = $"GCO is reading the host's configuration at runtime ({ClientSyncedConfigValues.Count} settings). Configuration Manager continues to show and edit your client-local values; your local config file is unchanged."; try { Log.LogInfo((object)(text + " Host mod/config version: " + hostModVersion + " / " + hostConfigVersion + ".")); } catch { } ShowLocalConfigSyncMessage(text); return; } string value3; List> list = hostValues.Where>((KeyValuePair pair) => !previousValues.TryGetValue(pair.Key, out value3) || !string.Equals(value3, pair.Value, StringComparison.Ordinal)).ToList(); if (list.Count == 0) { return; } foreach (KeyValuePair item in list) { string value; string serialized = (previousValues.TryGetValue(item.Key, out value) ? value : ""); string text2 = "[GCO Sync] Host changed " + FormatGcoConfigChangeLabel(item.Key) + ": " + FormatSyncedConfigValue(item.Key, serialized) + " -> " + FormatSyncedConfigValue(item.Key, item.Value); try { Log.LogInfo((object)text2); } catch { } } if (list.Count <= 8) { foreach (KeyValuePair item2 in list) { string value2; string serialized2 = (previousValues.TryGetValue(item2.Key, out value2) ? value2 : ""); ShowLocalConfigSyncMessage("[GCO Sync] " + FormatGcoConfigChangeLabel(item2.Key) + ": " + FormatSyncedConfigValue(item2.Key, serialized2) + " -> " + FormatSyncedConfigValue(item2.Key, item2.Value)); } return; } ShowLocalConfigSyncMessage("[GCO Sync] The host changed " + list.Count.ToString(CultureInfo.InvariantCulture) + " settings. Details were written to the log."); } private static string FormatSyncedConfigValue(ConfigEntryBase entry, string serialized) { if (serialized == "") { return serialized; } if (!TryParseSyncedConfigValue(entry.SettingType, serialized, out object value) || value == null) { return serialized; } string text = entry.Definition.Key ?? string.Empty; bool flag = text.IndexOf("Multiplier", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Rate", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Speed", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Damage", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Stagger", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Knockback", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Lunge", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Distance", StringComparison.OrdinalIgnoreCase) >= 0; if (value is float num) { return num.ToString("0.###", CultureInfo.InvariantCulture) + (flag ? "x" : string.Empty); } return serialized; } private static void RestoreClientLocalConfigAfterSync(string reason) { if (_clientMultiplayerConfigSyncActive) { _clientMultiplayerConfigSyncActive = false; ClientSyncedConfigValues.Clear(); ClientSyncedTypedValues.Clear(); ResetRuntimeStateAfterConfigChange(); try { Log.LogInfo((object)("Stopped reading host GCO configuration after " + reason + "; runtime reads now use client-local values.")); } catch { } ShowLocalConfigSyncMessage("GCO host configuration is no longer active. Runtime has returned to your client-local settings."); } } private static bool IsGuestDebugAuthorityLimited() { ZNet instance = ZNet.instance; if (_clientGuestDebugAuthorityLimited && (Object)(object)instance != (Object)null) { return !instance.IsServer(); } return false; } private static void SetClientGuestDebugAuthority(bool limited, string reason) { bool num = _clientGuestDebugAuthorityLimited != limited; _clientGuestDebugAuthorityLimited = limited; if (limited) { ForceDisableGooDebugPlayerToggles(Player.m_localPlayer); ResetGuestRestrictedOneShotButtons(); } if (num) { string text = (limited ? "The host has disabled GCO cheat-capable debug functions for guests. Diagnostic logging remains available." : "The host's GCO guest-debug restriction is no longer active."); try { Log.LogInfo((object)(text + " Reason: " + reason + ".")); } catch { } ShowLocalConfigSyncMessage(text); } } private static bool IsGuestRestrictedDebugEntry(ConfigEntryBase? entry) { if (entry == null) { return false; } if ((object)entry == GooCombatDebugMode || (object)entry == ToggleNoPlacementCost || (object)entry == ToggleDebugFly || (object)entry == ToggleGhostMode || (object)entry == DebugModeHeal || (object)entry == DebugModeAddRested || (object)entry == SpawnPvpTestDummy || (object)entry == RemovePvpTestDummies) { return true; } string obj = entry.Definition.Section ?? string.Empty; string text = NormalizeCommandKey(entry.Definition.Key ?? string.Empty); if (obj.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { if (!text.Contains("pvptestdummy")) { return text.Contains("trainingdummy"); } return true; } return false; } private static bool RejectGuestRestrictedDebugCommand(ConfigEntryBase? entry, ConsoleEventArgs args) { if (!IsGuestDebugAuthorityLimited() || !IsGuestRestrictedDebugEntry(entry)) { return false; } args.Context.AddString("The host has disabled GCO cheat-capable debug functions for guests."); return true; } private static void ResetGuestRestrictedOneShotButtons() { if (_applyingGuestDebugAuthorityRestriction || (Object)(object)Instance == (Object)null) { return; } bool saveOnConfigSet = ((BaseUnityPlugin)Instance).Config.SaveOnConfigSet; _applyingGuestDebugAuthorityRestriction = true; ((BaseUnityPlugin)Instance).Config.SaveOnConfigSet = false; try { if (ToggleNoPlacementCost != null) { ToggleNoPlacementCost.Value = false; } if (ToggleDebugFly != null) { ToggleDebugFly.Value = false; } if (ToggleGhostMode != null) { ToggleGhostMode.Value = false; } if (DebugModeHeal != null) { DebugModeHeal.Value = false; } if (DebugModeAddRested != null) { DebugModeAddRested.Value = false; } if (SpawnPvpTestDummy != null) { SpawnPvpTestDummy.Value = false; } if (RemovePvpTestDummies != null) { RemovePvpTestDummies.Value = false; } } finally { ((BaseUnityPlugin)Instance).Config.SaveOnConfigSet = saveOnConfigSet; _applyingGuestDebugAuthorityRestriction = false; } } private static void ShowConfigSyncVersionMismatchWarning(string hostModVersion, string hostConfigVersion, int hostProtocol, string clientConfigVersion) { string text = string.Format("GCO config sync failed because of a version mismatch. Reverting to client-local configuration. Host mod/config version: {0} / {1}. Client mod/config version: {2} / {3}. Host/client sync protocol: {4} / {5}.", hostModVersion, hostConfigVersion, "1.4.0", clientConfigVersion, hostProtocol, 3); try { Log.LogWarning((object)text); } catch { } ShowLocalConfigSyncMessage(text); } private static void ShowLocalConfigSyncMessage(string message) { if (string.IsNullOrWhiteSpace(message)) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)MessageHud.instance != (Object)null) { ((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null); } else if ((Object)(object)Instance != (Object)null && ((Behaviour)Instance).isActiveAndEnabled) { ((MonoBehaviour)Instance).StartCoroutine(ShowLocalConfigSyncMessageWhenReady(message)); } } catch { } } private static IEnumerator ShowLocalConfigSyncMessageWhenReady(string message) { float timeout = Time.realtimeSinceStartup + 30f; while (Time.realtimeSinceStartup < timeout) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)MessageHud.instance != (Object)null) { ((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null); break; } yield return null; } } public static void ConfigFileSavePrefix(ConfigFile __instance, ref ConfigSyncSaveState __state) { __state = null; if (_clientMultiplayerConfigSyncActive && !((Object)(object)Instance == (Object)null) && __instance == ((BaseUnityPlugin)Instance).Config) { _readClientLocalConfigDepth++; __state = new ConfigSyncSaveState(enteredLocalReadScope: true); } } public static Exception? ConfigFileSaveFinalizer(ConfigFile __instance, Exception? __exception, ConfigSyncSaveState __state) { if (__state != null && __state.EnteredLocalReadScope && !__state.ExitedLocalReadScope) { __state.ExitedLocalReadScope = true; if (_readClientLocalConfigDepth > 0) { _readClientLocalConfigDepth--; } } return __exception; } private static string StartupConfigKey(string section, string key) { return (section ?? string.Empty).Trim() + "\n" + (key ?? string.Empty).Trim(); } private void LoadStartupRawConfigSnapshot() { if (_startupRawConfigSnapshotLoaded) { return; } _startupRawConfigSnapshotLoaded = true; string configFilePath = GetConfigFilePath(); if (string.IsNullOrWhiteSpace(configFilePath) || !File.Exists(configFilePath)) { return; } _startupHadExistingConfigFile = true; try { string text = string.Empty; foreach (string item in File.ReadLines(configFilePath)) { string text2 = item.Trim(); if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal) || text2.StartsWith(";", StringComparison.Ordinal)) { continue; } if (text2.Length >= 2 && text2[0] == '[' && text2[text2.Length - 1] == ']') { text = text2.Substring(1, text2.Length - 2).Trim(); continue; } int num = text2.IndexOf('='); if (num > 0 && !string.IsNullOrWhiteSpace(text)) { string text3 = text2.Substring(0, num).Trim(); string value = text2.Substring(num + 1).Trim(); if (text3.Length > 0) { _startupRawConfigValues[StartupConfigKey(text, text3)] = value; } } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not read the existing config for inheritance-safe migration: " + ex.Message)); } } private bool TryGetStartupRawConfigValue(string section, IEnumerable candidateKeys, out string rawValue, out string matchedKey) { rawValue = string.Empty; matchedKey = string.Empty; foreach (string item in candidateKeys.Where((string k) => !string.IsNullOrWhiteSpace(k)).Distinct(StringComparer.OrdinalIgnoreCase)) { if (_startupRawConfigValues.TryGetValue(StartupConfigKey(section, item), out string value)) { rawValue = value; matchedKey = item; return true; } } return false; } private void CaptureStartupInheritedConfigValue(ConfigEntry entry, string section, params string[] candidateKeys) { if (entry == null || !_startupRawConfigSnapshotLoaded || !TryGetStartupRawConfigValue(section, candidateKeys, out string rawValue, out string matchedKey)) { return; } try { ((ConfigEntryBase)entry).SetSerializedValue(rawValue); _startupInheritedConfigValues[(ConfigEntryBase)(object)entry] = ((ConfigEntryBase)entry).GetSerializedValue(); } catch (Exception ex) { _startupInheritedConfigValues.Remove((ConfigEntryBase)(object)entry); _startupConfigInheritanceFailures.Add("[" + section + "] " + matchedKey + ": " + ex.Message); } } private void ApplyVanillaFallbackThenRestoreInheritedValues() { Dictionary dictionary = new Dictionary(_startupInheritedConfigValues); ApplyVanillaPresetValues(); int num = 0; int num2 = 0; foreach (KeyValuePair item in dictionary) { if ((object)item.Key != ConfigVersion) { try { item.Key.SetSerializedValue(item.Value); num++; } catch (Exception ex) { num2++; _startupConfigInheritanceFailures.Add("[" + item.Key.Definition.Section + "] " + item.Key.Definition.Key + ": " + ex.Message); } } } int num3 = Math.Max(0, GcoConfigSyncEntries.Values.Distinct().Count() - num); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Inherited {num} compatible config entries. Applied Reset to Vanilla values to {num3} newly added or non-inherited entries."); if (_startupConfigInheritanceFailures.Count > 0 || num2 > 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)string.Format("{0} config entr{1} could not be inherited and remained at Reset to Vanilla values. First failure: {2}", _startupConfigInheritanceFailures.Count, (_startupConfigInheritanceFailures.Count == 1) ? "y" : "ies", _startupConfigInheritanceFailures[0])); } } private ConfigEntry BindConfig(string section, string key, T defaultValue, string description) { return BindConfig(section, key, defaultValue, description, null); } private ConfigEntry BindConfig(string section, string key, T defaultValue, string description, AcceptableValueBase? acceptableValues) { string text = PrettyConfigKey(key); ConfigEntry val = BindConfigEntry(section, text, defaultValue, description, acceptableValues); RegisterCommandConfigEntry(section, text, (ConfigEntryBase)(object)val); string text2 = LegacyTitleConfigKey(key); if (ShouldRunConfigMigration()) { MigrateCompactConfigValue(section, key, text, val); if (!string.Equals(text2, key, StringComparison.Ordinal) && !string.Equals(text2, text, StringComparison.Ordinal)) { MigrateCompactConfigValue(section, text2, text, val); } } CaptureStartupInheritedConfigValue(val, section, new string[3] { text, key, text2 }); return val; } private ConfigEntry BindBootstrapConfig(string section, string key, T defaultValue, string description, AcceptableValueBase? acceptableValues = null) { string text = PrettyConfigKey(key); ConfigEntry val = BindConfigEntry(section, text, defaultValue, description, acceptableValues); RegisterCommandConfigEntry(section, text, (ConfigEntryBase)(object)val); CaptureStartupInheritedConfigValue(val, section, new string[3] { text, key, LegacyTitleConfigKey(key) }); return val; } private ConfigEntry BindConfigEntry(string section, string prettyKey, T defaultValue, string description, AcceptableValueBase? acceptableValues) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description, acceptableValues, new object[1] { new ConfigurationManagerAttributes { Order = GetConfigDisplayOrder(section, prettyKey), DispName = GetConfigDisplayName(section, prettyKey), Category = GetConfigDisplayCategory(section) } }); ConfigEntry obj = ((BaseUnityPlugin)this).Config.Bind(section, prettyKey, defaultValue, val); RegisterConfigEntryForMultiplayerSync((ConfigEntryBase)(object)obj); return obj; } private static string? GetConfigDisplayName(string section, string prettyKey) { string text = NormalizeCommandKey(prettyKey); string text2 = prettyKey; if (IsPersistedFlankSection(section)) { text2 = ReplaceOrdinalIgnoreCase(text2, "Backstab", "Flank"); } else if (IsPersistedBackstabSection(section)) { text2 = GetBackstabDisplayName(text2, text); } if (section.Equals("Sledges - Animation", StringComparison.OrdinalIgnoreCase) && text == "usealternatemoveset") { text2 = "[Beta] " + text2; } if (text.Contains("pvpstaggerdurationseconds")) { text2 = "[Beta] " + text2; } if (!string.Equals(text2, prettyKey, StringComparison.Ordinal)) { return text2; } return null; } private static string? GetConfigDisplayCategory(string section) { if (section.StartsWith("Sledge - Alt Moveset", StringComparison.OrdinalIgnoreCase)) { return section + " [Beta]"; } if (section.Equals("Backstab", StringComparison.OrdinalIgnoreCase)) { return "Flank"; } if (section.EndsWith(" - Backstab", StringComparison.OrdinalIgnoreCase)) { return section.Substring(0, section.Length - "Backstab".Length) + "Flank"; } return null; } private static bool IsPersistedFlankSection(string section) { if (!section.Equals("Backstab", StringComparison.OrdinalIgnoreCase)) { return section.EndsWith(" - Backstab", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsPersistedBackstabSection(string section) { if (!section.Equals("Sneak", StringComparison.OrdinalIgnoreCase)) { return section.EndsWith(" - Sneak", StringComparison.OrdinalIgnoreCase); } return true; } private static string GetBackstabDisplayName(string prettyKey, string normalizedKey) { return prettyKey; } private static string ReplaceOrdinalIgnoreCase(string input, string oldValue, string newValue) { if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(oldValue)) { return input; } return Regex.Replace(input, Regex.Escape(oldValue), newValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } private static int GetConfigDisplayOrder(string section, string prettyKey) { int configSectionDisplayOrder = GetConfigSectionDisplayOrder(section); int configKeyDisplayOrder = GetConfigKeyDisplayOrder(section, prettyKey); return configSectionDisplayOrder + configKeyDisplayOrder; } private static int GetConfigSectionDisplayOrder(string section) { if (string.IsNullOrWhiteSpace(section)) { return 0; } if (section.Equals("General", StringComparison.OrdinalIgnoreCase)) { return 1000000; } if (section.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { return 990000; } if (section.Equals("Resources", StringComparison.OrdinalIgnoreCase)) { return 980000; } if (section.Equals("Player", StringComparison.OrdinalIgnoreCase)) { return 970000; } if (section.Equals("Enemies", StringComparison.OrdinalIgnoreCase)) { return 960000; } if (section.Equals("Damage", StringComparison.OrdinalIgnoreCase)) { return 950000; } if (section.Equals("PvP Damage", StringComparison.OrdinalIgnoreCase)) { return 940000; } if (section.Equals("Staggering", StringComparison.OrdinalIgnoreCase)) { return 930000; } if (section.Equals("Counter Damage", StringComparison.OrdinalIgnoreCase)) { return 920000; } if (section.Equals("Sneak", StringComparison.OrdinalIgnoreCase)) { return 910000; } if (section.Equals("Backstab", StringComparison.OrdinalIgnoreCase)) { return 909000; } if (section.Equals("Flanking", StringComparison.OrdinalIgnoreCase)) { return 908000; } if (section.Equals("Stealth", StringComparison.OrdinalIgnoreCase)) { return 899000; } if (section.Equals("Hyperarmor", StringComparison.OrdinalIgnoreCase)) { return 890000; } if (section.Equals("Hit-Stop / Hitlag", StringComparison.OrdinalIgnoreCase)) { return 880000; } if (section.Equals("Knockback", StringComparison.OrdinalIgnoreCase)) { return 870000; } if (section.Equals("Blocking and Parry", StringComparison.OrdinalIgnoreCase)) { return 860000; } if (section.Equals("Attack movement and lunge", StringComparison.OrdinalIgnoreCase)) { return 850000; } if (section.Equals("Attack rotation", StringComparison.OrdinalIgnoreCase)) { return 840000; } if (section.Equals("Animation", StringComparison.OrdinalIgnoreCase)) { return 830000; } if (section.Equals("Hit Ray", StringComparison.OrdinalIgnoreCase)) { return 820000; } if (section.Equals("Multi target penalty", StringComparison.OrdinalIgnoreCase)) { return 815000; } if (section.Equals("Armor", StringComparison.OrdinalIgnoreCase)) { return 810000; } if (section.Equals("Shields - Bucklers", StringComparison.OrdinalIgnoreCase)) { return 800000; } if (section.Equals("Shields - Medium Shields", StringComparison.OrdinalIgnoreCase)) { return 799000; } if (section.Equals("Shields - Tower Shields", StringComparison.OrdinalIgnoreCase)) { return 798000; } for (int i = 0; i < ConfigStaffSectionOrder.Length; i++) { if (section.Equals(ConfigStaffSectionOrder[i], StringComparison.OrdinalIgnoreCase)) { return 780000 - i * 1000; } } int configWeaponSectionIndex = GetConfigWeaponSectionIndex(section); if (configWeaponSectionIndex >= 0) { return 700000 - configWeaponSectionIndex * 10000 + GetConfigWeaponFeatureOrder(section); } if (section.IndexOf("Legacy", StringComparison.OrdinalIgnoreCase) >= 0 || section.IndexOf("Deprecated", StringComparison.OrdinalIgnoreCase) >= 0 || section.IndexOf("Migration", StringComparison.OrdinalIgnoreCase) >= 0) { return 10000; } return 100000; } private static int GetConfigWeaponSectionIndex(string section) { if (section.StartsWith("Sledge - Alt Moveset", StringComparison.OrdinalIgnoreCase)) { return Array.FindIndex(ConfigWeaponSectionOrder, (string value) => value.Equals("Sledges", StringComparison.OrdinalIgnoreCase)); } for (int num = 0; num < ConfigWeaponSectionOrder.Length; num++) { string text = ConfigWeaponSectionOrder[num]; if (section.Equals(text, StringComparison.OrdinalIgnoreCase) || section.StartsWith(text + " - ", StringComparison.OrdinalIgnoreCase)) { return num; } } return -1; } private static int GetConfigWeaponFeatureOrder(string section) { if (section.StartsWith("Sledge - Alt Moveset", StringComparison.OrdinalIgnoreCase)) { if (section.EndsWith(" Damage", StringComparison.OrdinalIgnoreCase)) { return 9000; } if (section.EndsWith(" PvP", StringComparison.OrdinalIgnoreCase)) { return 8500; } if (section.EndsWith(" Stagger", StringComparison.OrdinalIgnoreCase)) { return 8000; } if (section.EndsWith(" Counter Damage", StringComparison.OrdinalIgnoreCase)) { return 7500; } if (section.EndsWith(" Sneak", StringComparison.OrdinalIgnoreCase)) { return 7000; } if (section.EndsWith(" Backstab", StringComparison.OrdinalIgnoreCase)) { return 6900; } if (section.EndsWith(" Hyperarmor", StringComparison.OrdinalIgnoreCase)) { return 6000; } if (section.EndsWith(" Hit Stop", StringComparison.OrdinalIgnoreCase)) { return 5500; } if (section.EndsWith(" Blocking", StringComparison.OrdinalIgnoreCase)) { return 5000; } if (section.EndsWith(" Attack Movement", StringComparison.OrdinalIgnoreCase)) { return 4500; } if (section.EndsWith(" Attack Rotation", StringComparison.OrdinalIgnoreCase)) { return 4000; } if (section.EndsWith(" Animation", StringComparison.OrdinalIgnoreCase)) { return 3500; } if (section.EndsWith(" Hit Ray", StringComparison.OrdinalIgnoreCase)) { return 3000; } if (section.EndsWith(" Multi Target Penalty", StringComparison.OrdinalIgnoreCase)) { return 2750; } if (section.EndsWith(" Running Attack", StringComparison.OrdinalIgnoreCase)) { return 2000; } if (section.EndsWith(" Resources", StringComparison.OrdinalIgnoreCase)) { return 1900; } if (section.EndsWith(" Knockback", StringComparison.OrdinalIgnoreCase)) { return 1800; } if (section.EndsWith(" Block Cancel", StringComparison.OrdinalIgnoreCase)) { return 1700; } if (section.EndsWith(" Destruction", StringComparison.OrdinalIgnoreCase)) { return 1600; } } if (section.EndsWith(" - Damage", StringComparison.OrdinalIgnoreCase)) { return 9000; } if (section.EndsWith(" - PvP Damage", StringComparison.OrdinalIgnoreCase)) { return 8500; } if (section.EndsWith(" - Staggering", StringComparison.OrdinalIgnoreCase)) { return 8000; } if (section.EndsWith(" - Counter Damage", StringComparison.OrdinalIgnoreCase)) { return 7500; } if (section.EndsWith(" - Sneak", StringComparison.OrdinalIgnoreCase)) { return 7000; } if (section.EndsWith(" - Backstab", StringComparison.OrdinalIgnoreCase)) { return 6900; } if (section.EndsWith(" - Flanking", StringComparison.OrdinalIgnoreCase)) { return 6800; } if (section.EndsWith(" - Stealth", StringComparison.OrdinalIgnoreCase)) { return 6400; } if (section.EndsWith(" - Hyperarmor", StringComparison.OrdinalIgnoreCase)) { return 6000; } if (section.EndsWith(" - Hit-Stop / Hitlag", StringComparison.OrdinalIgnoreCase)) { return 5500; } if (section.EndsWith(" - Blocking", StringComparison.OrdinalIgnoreCase)) { return 5000; } if (section.EndsWith(" - Attack movement and lunge", StringComparison.OrdinalIgnoreCase)) { return 4500; } if (section.EndsWith(" - Attack rotation", StringComparison.OrdinalIgnoreCase)) { return 4000; } if (section.EndsWith(" - Animation", StringComparison.OrdinalIgnoreCase)) { return 3500; } if (section.EndsWith(" - Hit Ray", StringComparison.OrdinalIgnoreCase)) { return 3000; } if (section.EndsWith(" - Multi target penalty", StringComparison.OrdinalIgnoreCase)) { return 2750; } if (section.EndsWith(" - Equipment Movement", StringComparison.OrdinalIgnoreCase)) { return 2500; } if (section.EndsWith(" - Running Attack", StringComparison.OrdinalIgnoreCase)) { return 2000; } if (section.EndsWith(" - Jump Attack", StringComparison.OrdinalIgnoreCase)) { return 1500; } if (section.EndsWith(" - Ranged", StringComparison.OrdinalIgnoreCase)) { return 1000; } return 0; } private static int GetConfigKeyDisplayOrder(string section, string prettyKey) { string text = NormalizeCommandKey(prettyKey); string text2 = NormalizeCommandKey(section); if (section.Equals("General", StringComparison.OrdinalIgnoreCase)) { return text switch { "applymodoptions" => 900, "settogooruleset" => 850, "resettovanilla" => 800, "configversion" => 750, "syncconfiginmultiplayer" => 725, "limitguestsauthority" => 715, "broadcastconfigchanges" => 700, "roll" => 650, _ => 100, }; } if (section.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { switch (text) { case "debuglogging": return 950; case "verbosedebuglogging": return 940; case "debugattacklifecycle": return 930; case "debugcounterdamage": return 920; case "debughitstop": return 910; case "debugmovement": return 900; case "debughyperarmorlifecycle": return 890; case "debughyperarmordamagemitigation": return 880; case "debugplayersystems": return 870; case "debugnegativestamina": return 860; case "debugranged": return 850; case "debugblocking": return 840; case "goocombatdebugmode": return 800; case "enabledebugflytoggle": return 790; case "enablenoplacementcosttoggle": return 780; case "enableghostmodetoggle": return 770; case "toggledebugfly": return 760; case "toggledebugflykeybind": return 750; case "togglenoplacementcost": return 740; case "togglenoplacementcostkeybind": return 730; case "toggleghostmode": return 720; case "toggleghostmodekeybind": return 710; case "debugmodeheal": return 700; case "debugmodehealkeybind": return 690; case "debugmodeaddrested": return 680; case "debugmodeaddrestedkeybind": return 670; case "debugmodesetspawnpoint": return 660; case "debugmodesetspawnpointkeybind": return 655; case "spawnpvptestdummy": return 650; case "removepvptestdummies": return 640; default: if (text.Contains("pvptestdummy")) { return 600; } return 100; } } if (text2.Contains("blockingandparry") || section.StartsWith("Shields - ", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Blocking", StringComparison.OrdinalIgnoreCase) || section.StartsWith("Magic - ", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("blockarmormultiplier") && !text.Contains("pvp")) { return 950; } if (text.Contains("blockforcemultiplier") && !text.Contains("pvp")) { return 940; } if (text.Contains("blockstaminaconsumptionrate")) { return 930; } if (text.Contains("blockbreakthresholdmultiplier") && !text.Contains("pvp")) { return 920; } if (text.Contains("pvpblockarmormultiplier")) { return 900; } if (text.Contains("pvpblockforcemultiplier")) { return 890; } if (text.Contains("pvpblockbreakthresholdmultiplier")) { return 880; } if (text.Contains("parrybonusmultiplier") && !text.Contains("pvp")) { return 850; } if (text.Contains("pvpparrybonusmultiplier")) { return 840; } if (text.Contains("parrywindowtimemultiplier")) { return 830; } if (text.Contains("parryadrenalinemultiplier")) { return 820; } if (text.Contains("canstaggerrangedattackswithparry") && text.Contains("pve")) { return 818; } if (text.Contains("canstaggerrangedattackswithparry") && text.Contains("pvp")) { return 817; } if (text.Contains("canstaggeraoeattackswithparry") && text.Contains("pve")) { return 816; } if (text.Contains("canstaggeraoeattackswithparry") && text.Contains("pvp")) { return 815; } if (text.Contains("effectiveblockanglemultiplier")) { return 800; } if (text.Contains("blockingstaminaregenmultiplier")) { return 780; } if (text.Contains("blockingmovementspeedmultiplier") && !text.Contains("running")) { return 770; } if (text.Contains("enablerunningwhileblocking")) { return 760; } if (text.Contains("runningwhileblockingmovementspeedmultiplier")) { return 750; } if (text.Contains("soulslikeblockbreak")) { return 730; } if (text.Contains("soulslikeblockstaminamode")) { return 720; } if (text.Contains("soulslikeblockstaminabaserate")) { return 710; } if (text.Contains("damagetakenonblockedhitmultiplier")) { if (text.Contains("generic")) { return 690; } if (text.Contains("blunt")) { return 680; } if (text.Contains("slash")) { return 670; } if (text.Contains("pierce")) { return 660; } if (text.Contains("chop")) { return 650; } if (text.Contains("pickaxe")) { return 640; } if (text.Contains("fire")) { return 630; } if (text.Contains("frost")) { return 620; } if (text.Contains("lightning")) { return 610; } if (text.Contains("poison")) { return 600; } if (text.Contains("spirit")) { return 590; } return 580; } if (text.Contains("damagetakenwhileblockingmultiplier")) { return 100; } } if (section.Equals("Hit Ray", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Hit Ray", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("hitrayrange")) { return 900; } if (text.Contains("hitraythickness") && !text.Contains("added")) { return 880; } if (text.Contains("hitrayaddedthicknessmeters")) { return 870; } if (text.Contains("hitraystartheight")) { return 860; } if (text.Contains("hitrayoffset")) { return 850; } if (text.Contains("attackhitboxtype")) { return 840; } if (text.Contains("hitrayangle")) { return 830; } if (text.Contains("prefab") && text.Contains("hitrayrange")) { return 790; } } if (section.Equals("Sneak", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Sneak", StringComparison.OrdinalIgnoreCase) || section.Equals("Stealth", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Stealth", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("sneakdamagemultiplier") || text.Contains("backstabdamagemultiplier")) { return 900; } if (text.Contains("sneakdamagestackable") || text.Contains("backstabdamagestackable")) { return 880; } if (text.Contains("attackstartnoise")) { return 850; } if (text.Contains("attackhitnoise")) { return 840; } } if (section.Equals("Backstab", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Backstab", StringComparison.OrdinalIgnoreCase) || section.Equals("Flanking", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Flanking", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("enabled")) { return 950; } if (text.Contains("angle")) { return 930; } if (text.Contains("pvp") && text.Contains("damagemultiplier")) { return 900; } if (text.Contains("damagemultiplier")) { return 910; } if (text.Contains("ding")) { return 880; } if (text.Contains("stackable")) { return 860; } } if (section.EndsWith(" - Running Attack", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Jump Attack", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("enabled")) { return 950; } if (text.Contains("usedattack")) { return 940; } if (text.Contains("minimumvelocity")) { return 930; } if (text.Contains("damage")) { return 900; } if (text.Contains("stagger")) { return 880; } if (text.Contains("staminarate")) { return 860; } if (text.Contains("animationspeed")) { return 840; } if (text.Contains("recoveryanimationspeed")) { return 830; } if (text.Contains("blockcancel")) { return 810; } if (text.Contains("hyperarmor")) { return 790; } if (text.Contains("hitstop")) { return 760; } if (text.Contains("movement")) { return 730; } if (text.Contains("rotation")) { return 710; } if (text.Contains("lunge")) { return 690; } if (text.Contains("noise")) { return 650; } if (text.Contains("hitray") || text.Contains("hitbox")) { return 630; } } return 100; } private void BindBootstrapGeneralConfig() { Enabled = BindBootstrapConfig("General", "ApplyModOptions", defaultValue: true, "Master switch. If false, disables every gameplay feature, runtime state check, and live modifier this mod offers without unloading the Harmony patches."); ConfigVersion = BindBootstrapConfig("General", "ConfigVersion", "0.0.0", "Internal config schema version. Fresh configs use Goo defaults. Older-version configs preserve every compatible value; newly added or failed-to-inherit entries use Reset to Vanilla values. Same-version configs are not rewritten."); SetToGoosRuleset = BindBootstrapConfig("General", "SetToGooRuleset", defaultValue: false, "One-shot ruleset button. Fresh configs already default to Goo's ruleset; set true to rewrite combat/balance entries back to Goo's ruleset after manual edits or vanilla reset. This automatically returns to false. ApplyModOptions controls whether the mod is active. Because GCO now contains a very large config, applying this ruleset may take several minutes; the game may appear temporarily unresponsive, so do not close it while the operation is running."); SetToVanillaRuleset = BindBootstrapConfig("General", "ResetToVanilla", defaultValue: false, "One-shot reset button. Set true to restore ordinary vanilla attack slots and gameplay systems to vanilla/no-op Valheim behavior, then this value automatically returns to false. Values for mod-added attack slots, running attacks, jump attacks, and alternate movesets are preserved; those attacks/movesets are disabled instead. Debug settings and keybinds are preserved. Because GCO now contains a very large config, resetting may take several minutes; the game may appear temporarily unresponsive, so do not close it while the operation is running."); } private void EvaluateStartupConfigVersionDecision() { if (ConfigVersion != null) { string value = ConfigVersion.Value; _startupConfigVersionNeedsBump = IsVersionOlderThan(value, "1.4.0"); _startupConfigVersionDecisionInitialized = true; } } private bool ShouldRunConfigMigration() { if (_startupConfigVersionDecisionInitialized) { return _startupConfigVersionNeedsBump; } return false; } private void MigrateCompactConfigValue(string section, string compactKey, string prettyKey, ConfigEntry entry) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if (compactKey == prettyKey) { return; } try { if (!(typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary)) { return; } ConfigDefinition key = new ConfigDefinition(section, compactKey); if (dictionary.Contains(key)) { string text = dictionary[key]?.ToString(); if (!string.IsNullOrWhiteSpace(text)) { ((ConfigEntryBase)entry).SetSerializedValue(text); _startupInheritedConfigValues[(ConfigEntryBase)(object)entry] = ((ConfigEntryBase)entry).GetSerializedValue(); } else { _startupInheritedConfigValues.Remove((ConfigEntryBase)(object)entry); _startupConfigInheritanceFailures.Add("[" + section + "] " + compactKey + ": empty serialized value"); } dictionary.Remove(key); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Migrated config entry [" + section + "] " + compactKey + " -> " + prettyKey + ".")); } } catch (Exception ex) { _startupInheritedConfigValues.Remove((ConfigEntryBase)(object)entry); _startupConfigInheritanceFailures.Add("[" + section + "] " + compactKey + ": " + ex.Message); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not migrate compact config entry [" + section + "] " + compactKey + ": " + ex.Message)); } } private static string LegacyTitleConfigKey(string key) { if (string.IsNullOrWhiteSpace(key)) { return key; } if (LegacyTitleConfigKeyCache.TryGetValue(key, out string value)) { return value; } string input = key.Trim(); input = Regex.Replace(input, "(?<=[A-Za-z])(?=\\d)|(?<=\\d)(?=[A-Za-z])", " "); input = Regex.Replace(input, "(?<=[a-z])(?=[A-Z])", " "); input = Regex.Replace(input, "(?<=[A-Z])(?=[A-Z][a-z])", " "); input = Regex.Replace(input, "\\s+", " "); input = input.Replace("Pvp", "PvP").Replace("Pve", "PvE").Replace("Aoe", "AOE"); LegacyTitleConfigKeyCache[key] = input; return input; } private static string PrettyConfigKey(string key) { if (string.IsNullOrWhiteSpace(key)) { return key; } if (PrettyConfigKeyCache.TryGetValue(key, out string value)) { return value; } string[] array = Regex.Replace(Regex.Replace(Regex.Replace(Regex.Replace(key.Trim(), "(?<=[A-Za-z])(?=\\d)|(?<=\\d)(?=[A-Za-z])", " "), "(?<=[a-z])(?=[A-Z])", " "), "(?<=[A-Z])(?=[A-Z][a-z])", " "), "\\s+", " ").Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return key; } for (int i = 0; i < array.Length; i++) { array[i] = NormalizeConfigWord(array[i], i == 0); } string text = string.Join(" ", array); PrettyConfigKeyCache[key] = text; return text; } private static string NormalizeConfigWord(string word, bool firstWord) { if (string.IsNullOrEmpty(word)) { return word; } string text = word.ToLowerInvariant(); string text2 = text switch { "pvp" => "PvP", "pve" => "PvE", "aoe" => "AOE", "ai" => "AI", "dps" => "DPS", "id" => "ID", "zdo" => "ZDO", "rpc" => "RPC", "hp" => "HP", "goo" => "Goo", "goo's" => "Goo's", _ => text, }; if (firstWord && text2.Length > 0 && text2 == text) { text2 = char.ToUpperInvariant(text2[0]) + text2.Substring(1); } return text2; } private void BindGeneralApplicationToggles() { BroadcastConfigChanges = BindConfig("General", "BroadcastConfigChanges", defaultValue: false, "If true, GCO sends chat/shout notifications for config changes, preset applications, and debug-mode toggle feedback. Goo default is false to avoid chat spam. Console/log output is still available where relevant."); SyncConfigInMultiplayer = BindConfig("General", "SyncConfigInMultiplayer", defaultValue: true, "If enabled on the host or dedicated server, connected guests read the host's complete gameplay-config snapshot at runtime until disconnect. Guest ConfigEntry values and config files are never overwritten; Configuration Manager continues to display and edit client-local values, and GCO shows a notice while host values are active. Keybinds, debug settings, one-shot buttons, broadcast preferences, config version, this toggle, and Limit Guests Authority remain local. Host changes synchronize live and are reported to guests. Exact GCO mod/config versions are required; a mismatch only shows host/client warnings and falls back to client-local gameplay config."); LimitGuestsAuthority = BindConfig("General", "LimitGuestsAuthority", defaultValue: true, "Host/dedicated-server authority switch. If enabled, non-host guests cannot use GCO's cheat-capable debug functions: combat debug mode/god mode, debug fly, no-placement-cost, ghost mode, debug healing, debug Rested, or PvP test-dummy creation/manipulation. Diagnostic logging remains available. This restriction is sent independently of gameplay-config sync and does not rewrite guest config files. Goo and vanilla-reset defaults are enabled."); Roll = BindConfig("General", "Roll", (KeyCode)120, "One-button roll key. Press this key to queue Valheim's native dodge/roll without holding block or crouch. Direction follows current WASD movement input; if no movement input is held, the roll goes backward relative to the player's look direction. Set to None to disable. Goo default is X; vanilla/no-op reset sets this to None."); FeatureGlobalToggles.Clear(); FeatureWeaponToggles.Clear(); BindFeatureApplicationToggles("Damage", "damage", "Damage", ModFeature.Damage, AllFeatureWeaponCategories); BindStaggeringApplicationToggles(); BindFeatureApplicationToggles("Hyperarmor", "hyperarmor", "Hyperarmor", ModFeature.HyperArmor, MeleeCombatFeatureWeaponCategories); BindFeatureApplicationToggles("Counter Damage", "counter damage", "CounterDamage", ModFeature.CounterDamage, MeleeCombatFeatureWeaponCategories); BindFeatureApplicationToggles("Hit-Stop / Hitlag", "hit-stop", "HitStop", ModFeature.HitStop, MeleeCombatFeatureWeaponCategories); BindFeatureApplicationToggles("Attack movement and lunge", "attack movement and lunge", "AttackMovementAndLunge", ModFeature.AttackMovement, MeleeCombatFeatureWeaponCategories); BindFeatureApplicationToggles("Attack rotation", "attack rotation", "AttackRotation", ModFeature.AttackRotation, MeleeCombatFeatureWeaponCategories); BindFeatureApplicationToggles("Hit Ray", "hit ray", "HitRay", ModFeature.HitRay, HitRayFeatureWeaponCategories); BindFeatureApplicationToggles("Sneak", "sneak", "Sneak", ModFeature.StealthAttributes, StealthAttributeWeaponCategories); BindFeatureApplicationToggles("Multi target penalty", "multi-target damage penalty", "MultiTargetPenalty", ModFeature.MultiTargetPenalty, HitRayFeatureWeaponCategories); BindFeatureApplicationToggles("PvP Damage", "PvP damage", "Pvp", ModFeature.Pvp, AllFeatureWeaponCategories); BindFeatureApplicationToggles("Resources", "resource tuning", "Resources", ModFeature.Resources, AllFeatureWeaponCategories); BindFeatureApplicationToggles("Blocking and Parry", "blocking and parry", "BlockingAndParry", ModFeature.Blocking, AllFeatureWeaponCategories); } private static void AuditFeatureToggleCoverage() { List missing = new List(); Require(ModFeature.Damage, AllFeatureWeaponCategories); Require(ModFeature.Staggering, StaggerFeatureWeaponCategories); Require(ModFeature.HyperArmor, MeleeCombatFeatureWeaponCategories); Require(ModFeature.CounterDamage, MeleeCombatFeatureWeaponCategories); Require(ModFeature.HitStop, MeleeCombatFeatureWeaponCategories); Require(ModFeature.AttackMovement, MeleeCombatFeatureWeaponCategories); Require(ModFeature.AttackRotation, MeleeCombatFeatureWeaponCategories); Require(ModFeature.HitRay, HitRayFeatureWeaponCategories); Require(ModFeature.StealthAttributes, StealthAttributeWeaponCategories); Require(ModFeature.MultiTargetPenalty, HitRayFeatureWeaponCategories); Require(ModFeature.Pvp, AllFeatureWeaponCategories); Require(ModFeature.Resources, AllFeatureWeaponCategories); Require(ModFeature.Blocking, AllFeatureWeaponCategories); if (missing.Count == 0) { Log.LogInfo((object)"GCO gameplay-feature toggle audit passed, including Sledge/SledgeAlt movement and Bow/Crossbow stagger coverage."); } else { Log.LogError((object)("GCO gameplay-feature toggle audit found missing application toggles: " + string.Join("; ", missing.Distinct()))); } void Require(ModFeature feature, IEnumerable categories) { if (!FeatureGlobalToggles.ContainsKey(feature)) { missing.Add(feature.ToString() + " global"); } foreach (WeaponCategory item in categories.Distinct()) { WeaponCategory category = PublicFeatureCategory(item); if (!FeatureWeaponToggles.ContainsKey(FeatureToggleKey(feature, category))) { missing.Add(feature.ToString() + " / " + category); } } } } private void BindConfig() { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { LoadStartupRawConfigSnapshot(); BindBootstrapGeneralConfig(); EvaluateStartupConfigVersionDecision(); BindGeneralApplicationToggles(); AlwaysDoMaxDamage = BindConfig("Damage", "AlwaysDoMaxDamage", defaultValue: false, "If true, player attacks use the maximum skill damage factor instead of Valheim's random damage roll against all targets. Goo default is false. Tooltips show base damage with max damage in parentheses instead of a min-max damage range."); AlwaysDoMaxDamageInPvp = BindConfig("Damage", "AlwaysDoMaxDamageInPvp", defaultValue: true, "If true, player attacks use the maximum skill damage factor only when damaging players or GCO PvP test dummies. Goo default is true. This does not alter item tooltips."); GlobalDamageMultiplier = BindConfig("Damage", "GlobalDamageMultiplier", 1f, "Global outgoing player damage multiplier after weapon/category damage tuning. 1 is vanilla/no-op."); GlobalHyperArmorDamageTakenMultiplier = BindConfig("Hyperarmor", "GlobalHyperArmorDamageTakenMultiplier", 1f, "Global incoming damage multiplier while player hyperarmor is active. Multiplies per-attack hyperarmor damage-taken settings. 1 is vanilla/no-op."); GlobalHyperArmorKnockbackTakenMultiplier = BindConfig("Knockback", "GlobalHyperArmorKnockbackTakenMultiplier", 1f, "Global incoming knockback multiplier while player hyperarmor is active. Multiplies per-attack hyperarmor knockback-taken settings. 1 is vanilla/no-op."); GlobalPvpDamageMultiplier = BindConfig("PvP Damage", "GlobalPvpDamageMultiplier", 1f, "Global outgoing PvP damage multiplier for player attacks against players or GCO PvP test dummies. Multiplies per-weapon PvP damage settings. 1 is vanilla/no-op."); GlobalPvpDamageTakenMultiplier = BindConfig("PvP Damage", "GlobalPvpDamageTakenMultiplier", 1f, "Global incoming PvP damage-taken multiplier for player/PvP-test-dummy victims. Does not alter tooltips. 1 is vanilla/no-op."); UseDamageCurveInPve = BindConfig("Damage", "UseDamageCurveInPve", defaultValue: true, "If true, incoming non-player damage dealt to players is multiplied by the configurable alternate damage curve. This includes mobs/NPCs/environment-style hits where the attacker is not a player. Goo default is true; vanilla/no-op reset disables it."); PveAlternateDamageCurveBaseRawDamage = BindConfig("Damage", "AlternateDamageCurveBaseRawDamage", 100f, "PvE incoming-damage alternate curve anchor raw damage. With the default equation, this incoming raw damage receives a 1x curve multiplier before clamping."); PveAlternateDamageCurveExponent = BindConfig("Damage", "AlternateDamageCurveExponent", 0.5f, "PvE incoming-damage alternate curve exponent. Goo default 0.5 means sqrt-style normalization: (BaseRawDamage / rawDamage)^0.5."); PveAlternateDamageCurveMinimumMultiplier = BindConfig("Damage", "AlternateDamageCurveMinimumMultiplier", 0f, "PvE incoming-damage alternate curve floor. Goo default is 0, so the equation is not forced upward by a minimum multiplier."); PveAlternateDamageCurveMaximumMultiplier = BindConfig("Damage", "AlternateDamageCurveMaximumMultiplier", 1f, "PvE incoming-damage alternate curve ceiling. Goo default is 1x, so low incoming damage is not boosted above vanilla/GCO-scaled incoming damage."); UseDamageCurveInPvp = BindConfig("PvP Damage", "UseDamageCurveInPvp", defaultValue: true, "If true, player attacks against players or GCO PvP test dummies are multiplied by the configurable alternate damage curve after attack, PvP, sneak, flank, counter, and other applicable damage multipliers have been included in the curve input. The curve is the final GCO outgoing-damage multiplier before Valheim armor/resistance processing. Goo default is true; vanilla/no-op reset disables it."); PvpAlternateDamageCurveBaseRawDamage = BindConfig("PvP Damage", "AlternateDamageCurveBaseRawDamage", 100f, "PvP alternate damage-curve anchor raw damage. With the default equation, this raw damage receives a 1x curve multiplier."); PvpAlternateDamageCurveExponent = BindConfig("PvP Damage", "AlternateDamageCurveExponent", 0.5f, "PvP alternate damage-curve exponent. Goo default 0.5 means sqrt-style normalization: (BaseRawDamage / rawDamage)^0.5."); PvpAlternateDamageCurveMinimumMultiplier = BindConfig("PvP Damage", "AlternateDamageCurveMinimumMultiplier", 0f, "PvP alternate damage-curve floor. Goo default is 0, so the equation is not forced upward by a minimum multiplier."); PvpAlternateDamageCurveMaximumMultiplier = BindConfig("PvP Damage", "AlternateDamageCurveMaximumMultiplier", 2f, "PvP alternate damage-curve ceiling. The curve multiplier is clamped no higher than this value."); GlobalPlayerKnockbackMultiplier = BindConfig("Knockback", "GlobalPlayerKnockbackMultiplier", 1f, "Global outgoing player knockback/push-force multiplier after weapon/category knockback tuning. 1 is vanilla/no-op."); GlobalPvpKnockbackMultiplier = BindConfig("Knockback", "GlobalPvpKnockbackMultiplier", 1f, "Global PvP-only outgoing knockback/push-force multiplier for player attacks against players or PvP test dummies. Stacks with per-attack PvP knockback settings. 1 is vanilla/no-op."); GlobalPlayerStaggerPowerMultiplier = BindConfig("Staggering", "GlobalPlayerStaggerPowerMultiplier", 1f, "Global outgoing player stagger/poise power multiplier after weapon/category stagger tuning. 1 is vanilla/no-op."); GlobalDamageToStaggeredEnemiesMultiplier = BindConfig("Staggering", "GlobalDamageToStaggeredEnemiesMultiplier", 2f, "Final global damage multiplier for player attacks against staggered PvE enemies. This replaces Valheim's normal staggered-enemy execution damage result instead of multiplying it. Final result is global x per-weapon/per-attack local multiplier. 2 is vanilla/Goo default."); StaggeredEnemyDamageStackableWithOtherBonuses = BindConfig("Staggering", "StaggeredEnemyDamageStackableWithOtherBonuses", defaultValue: true, "If true, the staggered-PvE-enemy damage bonus can stack with other conditional damage bonuses such as sneak, counter damage, and flank. If false, it only applies when no other tracked conditional bonus is active."); BackstabDamageStackableWithOtherBonuses = BindConfig("Sneak", "SneakDamageStackableWithOtherBonuses", defaultValue: true, "If true, sneak damage can stack with other conditional damage bonuses such as staggered-enemy, counter damage, and flank bonuses. If false, it only applies when no other tracked conditional bonus is active."); CounterDamageStackableWithOtherBonuses = BindConfig("Counter Damage", "CounterDamageStackableWithOtherBonuses", defaultValue: true, "If true, counter damage can stack with other conditional damage bonuses such as sneak, staggered-enemy, and flank bonuses. If false, it only applies when no other tracked conditional bonus is active."); CounterDamageDing = BindConfig("Counter Damage", "CounterDamageDing", defaultValue: true, "If true, successful counter-damage hits play Valheim's existing critical-hit effect once per qualifying hit. Uses the game's normal EffectList cleanup path and does not spawn persistent custom audio objects."); EnableFlankingDamage = BindConfig("Backstab", "EnableBackstabDamage", defaultValue: true, "If true, player attacks can receive configurable flank bonus damage when they hit the configured horizontal rear sector of a PvE enemy or PvP target."); FlankingDamageStackableWithOtherBonuses = BindConfig("Backstab", "BackstabDamageStackableWithOtherBonuses", defaultValue: true, "If true, flank damage can stack with other conditional damage bonuses such as sneak, counter damage, and staggered-enemy bonuses. If false, it only applies when no other tracked conditional bonus is active."); SuccessfulFlankDing = BindConfig("Backstab", "SuccessfulBackstabDing", defaultValue: true, "If true, successful flank attacks play Valheim's existing critical-hit effect once per qualifying hit. Uses the game's normal EffectList cleanup path and does not spawn persistent custom audio objects."); FlankingPveRearAngleDegrees = BindConfig("Backstab", "BackstabPveRearAngleDegrees", 120f, "Total horizontal rear-sector angle used for PvE flank checks. 120 means a 120-degree slice centered on the target's back; 0 disables PvE flank by angle; 360 makes every horizontal direction count."); FlankingPvpRearAngleDegrees = BindConfig("Backstab", "BackstabPvpRearAngleDegrees", 90f, "Total horizontal rear-sector angle used for PvP flank checks. 90 means a 90-degree slice centered on the target's back; 0 disables PvP flank by angle; 360 makes every horizontal direction count."); GlobalBackstabDamageMultiplier = BindConfig("Backstab", "GlobalBackstabDamageMultiplier", 1f, "Global PvE flank damage multiplier for rear-sector hits. Stacks with per-weapon flank damage. 1 leaves per-weapon values unchanged."); GlobalPvpBackstabDamageMultiplier = BindConfig("Backstab", "GlobalPvpBackstabDamageMultiplier", 1f, "Global PvP flank damage multiplier for rear-sector hits. Stacks with per-weapon PvP flank damage. 1 leaves per-weapon values unchanged."); GlobalPlayerDamageTakenMultiplier = BindConfig("Player", "GlobalPlayerDamageTakenMultiplier", 1f, "Global incoming player damage-taken multiplier after outgoing/PvP tuning and before final hyperarmor correction. 1 is vanilla/no-op."); PlayerDamageToBuildingsMultiplier = BindConfig("Player", "PlayerDamageToBuildingsMultiplier", 1f, "Global multiplier for damage that player-owned hits deal to WearNTear buildings/pieces. Stacks after ordinary outgoing damage tuning and per-weapon Destruction settings. 1 is vanilla/no-op."); GlobalCounterDamageMultiplier = BindConfig("Counter Damage", "GlobalCounterDamageMultiplier", 1f, "Global counter-damage multiplier. Multiplies each weapon slot's counter-damage multiplier. 1 leaves counter damage unchanged."); GlobalHitStopEnabled = BindConfig("Hit-Stop / Hitlag", "GlobalHitStopEnabled", defaultValue: true, "Global hit-stop enable switch. If false, GCO suppresses player melee hit-stop for supported attacks. If true, hit-stop duration is controlled by the global and per-attack duration multipliers."); GlobalBlockingStaminaRegenMultiplier = BindConfig("Blocking and Parry", "GlobalBlockingStaminaRegenMultiplier", 1f, "Global stamina regeneration multiplier while a player is actively blocking. Stacks with shield/weapon/staff block stamina regen settings. 1 is vanilla/no-op."); GlobalBlockArmorMultiplier = BindConfig("Blocking and Parry", "GlobalBlockArmorMultiplier", 1f, "Global Block Armor multiplier for all blockers. Stacks with weapon/shield/staff Block Armor settings. 1 is vanilla/no-op."); GlobalBlockForceMultiplier = BindConfig("Blocking and Parry", "GlobalBlockForceMultiplier", 1f, "Global Block Force multiplier for all blockers. Stacks with weapon/shield/staff Block Force settings. 1 is vanilla/no-op."); GlobalParryBonusMultiplier = BindConfig("Blocking and Parry", "GlobalParryBonusMultiplier", 1f, "Global parry/timed-block bonus multiplier for all blockers. Stacks with weapon/shield/staff parry settings. 1 is vanilla/no-op."); GlobalParryWindowTimeMultiplier = BindConfig("Blocking and Parry", "GlobalParryWindowTimeMultiplier", 1f, "Global parry/timed-block window duration multiplier for all blockers. Stacks with weapon/shield/staff parry-window settings. 1 keeps Valheim's 0.25 second window; 2 doubles it; 0 disables timed parries."); GlobalEffectiveBlockAngleMultiplier = BindConfig("Blocking and Parry", "GlobalEffectiveBlockAngleMultiplier", 1f, "Global effective block angle multiplier. Valheim's default block sector is 180 degrees; 1 keeps vanilla, 1.5 makes the effective sector 270 degrees, and 0 disables blocking by angle."); GlobalParryAdrenalineMultiplier = BindConfig("Blocking and Parry", "GlobalParryAdrenalineMultiplier", 1f, "Global parry adrenaline multiplier for all blockers. Stacks with shield/staff parry adrenaline settings. 1 is vanilla/no-op."); CanStaggerRangedAttacksWithParryPve = BindConfig("Blocking and Parry", "CanStaggerRangedAttacksWithParryPve", defaultValue: false, "If true, a player perfect-block/parry can stagger a PvE attacker when the incoming attack is a direct ranged/projectile hit. Goo default is false; vanilla/no-op is true."); CanStaggerRangedAttacksWithParryPvp = BindConfig("Blocking and Parry", "CanStaggerRangedAttacksWithParryPvp", defaultValue: true, "If true, a player perfect-block/parry can stagger a PvP attacker when the incoming attack is a direct ranged/projectile hit. Goo and vanilla/no-op default is true."); CanStaggerAoeAttacksWithParryPve = BindConfig("Blocking and Parry", "CanStaggerAoeAttacksWithParryPve", defaultValue: false, "If true, a player perfect-block/parry can stagger a PvE attacker when the incoming attack is a standalone AOE hit. Projectile splash is treated as ranged/projectile. Goo default is false; vanilla/no-op is true."); CanStaggerAoeAttacksWithParryPvp = BindConfig("Blocking and Parry", "CanStaggerAoeAttacksWithParryPvp", defaultValue: false, "If true, a player perfect-block/parry can stagger a PvP attacker when the incoming attack is a standalone AOE hit. Projectile splash is treated as ranged/projectile. Goo default is false; vanilla/no-op is true."); GlobalBlockStaminaConsumptionRate = BindConfig("Blocking and Parry", "GlobalBlockStaminaConsumptionRate", 1f, "Global block stamina consumption multiplier for all blockers. Stacks with weapon/shield/staff block stamina settings. 1 is vanilla/no-op."); GlobalBlockBreakThresholdMultiplier = BindConfig("Blocking and Parry", "GlobalBlockBreakThresholdMultiplier", 1.1f, "Global block-break threshold multiplier for all blockers. Higher values make blocked stagger contribute less to block break. Goo default is 1.1; vanilla/no-op is 1."); GlobalPvpBlockArmorMultiplier = BindConfig("Blocking and Parry", "GlobalPvpBlockArmorMultiplier", 1f, "Global PvP-only Block Armor multiplier for all blockers. Stacks on normal global and item-specific block armor. 1 is vanilla/no-op."); GlobalPvpBlockForceMultiplier = BindConfig("Blocking and Parry", "GlobalPvpBlockForceMultiplier", 1f, "Global PvP-only Block Force multiplier for all blockers. Stacks on normal global and item-specific block force. 1 is vanilla/no-op."); GlobalPvpParryBonusMultiplier = BindConfig("Blocking and Parry", "GlobalPvpParryBonusMultiplier", 1f, "Global PvP-only parry/timed-block bonus multiplier for all blockers. Stacks on normal global and item-specific parry bonus. 1 is vanilla/no-op."); GlobalPvpBlockBreakThresholdMultiplier = BindConfig("Blocking and Parry", "GlobalPvpBlockBreakThresholdMultiplier", 1f, "Global PvP-only block-break threshold multiplier for all blockers. Stacks on normal global and item-specific PvP block-break settings. 1 is vanilla/no-op."); GlobalBlockingMovementSpeedMultiplier = BindConfig("Blocking and Parry", "GlobalBlockingMovementSpeedMultiplier", 1f, "Global movement-speed multiplier while actively blocking. Stacks with shield-type/per-weapon blocking movement settings. Goo default is 1x; 1 is vanilla/no-op."); GlobalKnockbackTakenWhileBlockingMultiplier = BindConfig("Knockback", "GlobalKnockbackTakenWhileBlockingMultiplier", 1f, "Global knockback/push-force taken multiplier while a block succeeds. Stacks with weapon/shield/staff blocking knockback-taken settings. Does not apply when block breaks. 1 is vanilla/no-op."); GlobalSoulsLikeBlockStaminaBaseRateMultiplier = BindConfig("Blocking and Parry", "GlobalSoulsLikeBlockStaminaBaseRateMultiplier", 1f, "Global multiplier applied to each blocker category's SoulsLikeBlockStaminaBaseRate. 1 leaves per-blocker base rates unchanged."); GlobalDamageTakenOnBlockedHit = BindBlockedHitDamageTakenConfig("Blocking and Parry", globalPrefix: true); GlobalHitRayRangeMultiplier = BindConfig("Hit Ray", "GlobalHitRayRangeMultiplier", 1f, "Global multiplier for melee hit-ray range. Stacks with per-weapon/per-attack HitRayRange. 1 is vanilla/no-op."); GlobalHitRayThicknessMultiplier = BindConfig("Hit Ray", "GlobalHitRayThicknessMultiplier", 1f, "Global multiplier for melee hit-ray thickness. Stacks with per-weapon/per-attack HitRayThickness. 1 is vanilla/no-op."); GlobalHitRayStartHeightMultiplier = BindConfig("Hit Ray", "GlobalHitRayStartHeightMultiplier", 1f, "Global multiplier for melee hit-ray start height. Stacks with per-weapon/per-attack HitRayStartHeight. 1 is vanilla/no-op."); GlobalHitRayOffsetMultiplier = BindConfig("Hit Ray", "GlobalHitRayOffsetMultiplier", 1f, "Global multiplier for melee hit-ray sideways offset. Stacks with per-weapon/per-attack HitRayOffset. 1 is vanilla/no-op."); GlobalHitRayAngleMultiplier = BindConfig("Hit Ray", "GlobalHitRayAngleMultiplier", 1f, "Global multiplier for melee hit-ray fan angle. Stacks with per-weapon/per-attack HitRayAngle. 1 is vanilla/no-op."); GlobalAttackStartNoiseMultiplier = BindConfig("Stealth", "GlobalAttackStartNoiseMultiplier", 1f, "Global multiplier for attack-start AI noise radius. Stacks with per-weapon/per-attack AttackStartNoiseMultiplier. 1 is vanilla/no-op."); GlobalAttackHitNoiseMultiplier = BindConfig("Stealth", "GlobalAttackHitNoiseMultiplier", 1f, "Global multiplier for attack-hit AI noise radius. Stacks with per-weapon/per-attack AttackHitNoiseMultiplier. 1 is vanilla/no-op."); GlobalSneakDamageMultiplier = BindConfig("Sneak", "GlobalSneakDamageMultiplier", 2f, "Global sneak damage multiplier. Final tooltip/runtime sneak damage equals this global value multiplied by the weapon/attack local Sneak Damage Multiplier and optional Sneak skill scaling. Goo preset default is 2."); SneakDamageScalesWithSneakLevel = BindConfig("Sneak", "SneakDamageScalesWithSneakLevel", defaultValue: true, "If true, player sneak damage gains an extra multiplier from the player's Sneak skill. Final sneak damage = global x local x (Sneak Damage Scale Base Multiplier + Sneak Damage Multiplier Per Sneak Level x Sneak skill level). Goo default is true; Reset to Vanilla disables this."); SneakDamageScaleBaseMultiplier = BindConfig("Sneak", "SneakDamageScaleBaseMultiplier", 1f, "Base multiplier for Sneak-skill sneak-damage scaling. With Goo defaults, level 0 Sneak keeps normal configured sneak damage at 1x skill scale."); SneakDamageMultiplierPerSneakLevel = BindConfig("Sneak", "SneakDamageMultiplierPerSneakLevel", 0.01f, "Additional sneak damage multiplier added per Sneak skill level when Sneak Damage Scales With Sneak Level is enabled. Goo default is 0.01x per level, so level 100 doubles the configured sneak-damage value."); SneakXpOnSuccessfulSneakAttack = BindConfig("Sneak", "SneakXpOnSuccessfulSneakAttack", defaultValue: true, "If true, successful player sneak attacks raise the Sneak skill. This is event-based on real hit damage calls and does not scan enemies. Goo default is true; Reset to Vanilla disables this."); SneakXpOnSuccessfulSneakAttackBaseAmount = BindConfig("Sneak", "SneakXpOnSuccessfulSneakAttackBaseAmount", 1f, "Base Sneak XP awarded for a successful sneak attack before optional scaling. 1 matches a normal vanilla skill-raise unit."); SneakXpScalesWithSneakDamageMultiplier = BindConfig("Sneak", "SneakXpScalesWithSneakDamageMultiplier", defaultValue: true, "If true, Sneak XP from a successful sneak attack is multiplied by the final configured sneak damage multiplier for the attack. If false, only the base amount and ranged multiplier apply."); SneakXpRangedAttackMultiplier = BindConfig("Sneak", "SneakXpRangedAttackMultiplier", 0.5f, "Multiplier for Sneak XP awarded by ranged/projectile sneak attacks. Goo default is 0.5 so ranged sneak attacks still progress Sneak but are less farmable than melee sneak attacks."); GlobalAttackMovementMultiplier = BindConfig("Attack movement and lunge", "GlobalAttackMovementMultiplier", 1f, "Global attack movement-speed multiplier. Stacks with per-weapon/per-attack attack movement settings. 1 is vanilla/no-op."); GlobalLungeDistanceMultiplier = BindConfig("Attack movement and lunge", "GlobalLungeDistanceMultiplier", 1f, "Global root-motion/lunge-distance multiplier for normal attacks. Stacks with per-weapon/per-attack lunge settings. 1 is vanilla/no-op."); GlobalRunningAttackLungeDistanceMultiplier = BindConfig("Attack movement and lunge", "GlobalRunningAttackLungeDistanceMultiplier", 1f, "Global root-motion/lunge-distance multiplier for running attacks. Stacks with running attack lunge settings. 1 is vanilla/no-op."); GlobalJumpAttackLungeDistanceMultiplier = BindConfig("Attack movement and lunge", "GlobalJumpAttackLungeDistanceMultiplier", 1f, "Global root-motion/lunge-distance multiplier for jump attacks. Stacks with jump attack lunge settings. 1 is vanilla/no-op."); GlobalAttackRotationMultiplier = BindConfig("Attack rotation", "GlobalAttackRotationMultiplier", 1f, "Global multiplier for attack rotation factors. Stacks with weapon/jump/running rotation settings when rotation is enabled. 1 is vanilla/no-op."); GlobalPvpHitStopDurationMultiplier = BindConfig("Hit-Stop / Hitlag", "GlobalPvpHitStopDurationMultiplier", 1f, "Global PvP-only hit-stop duration multiplier. PvP hit-stop is vanilla hit-stop multiplied by this and the per-attack PvP hit-stop multiplier; it does not stack with PvE hit-stop duration. 1 is vanilla/no-op."); GlobalAttackAnimationSpeedMultiplier = BindConfig("Animation", "GlobalAttackAnimationSpeedMultiplier", 1f, "Global attack animation-speed multiplier. Stacks with per-weapon/per-attack attack animation speed. 1 is vanilla/no-op."); GlobalRecoveryAnimationSpeedMultiplier = BindConfig("Animation", "GlobalRecoveryAnimationSpeedMultiplier", 1f, "Global recovery animation-speed multiplier after the attack hitbox event. Stacks with per-weapon/per-attack recovery animation speed. 1 is vanilla/no-op."); BetterLightningEffects = BindConfig("Animation", "MoreLightningVisualEffectsOnHit", defaultValue: true, "If true, lightning-damage player hits play an extra Himmin Afl-style lightning visual effect on impact. This is visual-only and does not control chain-lightning gameplay chances. Goo default is true; Reset to Vanilla disables this."); GlobalAdrenalineRate = BindConfig("Resources", "GlobalAdrenalineRate", 1f, "Global multiplier for player adrenaline gained from attacks, parries, and other adrenaline gain calls. 1 is vanilla/no-op."); MustEatUniqueFood = BindConfig("Resources", "MustEatUniqueFood", defaultValue: true, "If true, preserves vanilla Valheim behavior: each food slot must be a distinct food unless an existing matching food is depleted enough to refresh. If false, players may fill multiple food slots with the same food. Goo preset default is true."); FoodHealthMultiplier = BindConfig("Resources", "FoodHealthMultiplier", 1f, "Multiplier for health gained from food. Scales the food portion only, not base player health. 1 is vanilla/no-op."); FoodStaminaMultiplier = BindConfig("Resources", "FoodStaminaMultiplier", 1f, "Multiplier for stamina gained from food. Scales the food portion only, not base player stamina. 1 is vanilla/no-op."); FoodEitrMultiplier = BindConfig("Resources", "FoodEitrMultiplier", 1f, "Multiplier for eitr gained from food. 1 is vanilla/no-op."); FoodHealingRateMultiplier = BindConfig("Resources", "FoodHealingRateMultiplier", 1f, "Multiplier for healing ticks from eaten food. 1 is vanilla/no-op. Works even when PermanentFoodBuffs freezes food timers."); PermanentFoodBuffs = BindConfig("Resources", "PermanentFoodBuffs", defaultValue: false, "If true, player food timers are frozen while the mod is active, so eaten food does not tick down. Food healing ticks still run."); PermanentMeadBuffs = BindConfig("Resources", "PermanentMeadBuffs", defaultValue: false, "If true, mead/potion-like timed status effects are kept from expiring while the mod is active. Mead over-time effects still run."); PermanentRestedBuff = BindConfig("Resources", "PermanentRestedBuff", defaultValue: false, "If true, the Rested status effect is kept from expiring while the mod is active."); NoConsumption = BindConfig("Resources", "NoConsumption", defaultValue: false, "If true, local-player consumable actions do not consume their item resources. Covers food, meads/potions, arrows/bolts, and ammo/bait-style attack resources without blocking normal inventory dropping or trading."); MeadStaminaRecoveryBonusMultiplier = BindConfig("Resources", "MeadStaminaRecoveryBonusMultiplier", 1f, "Multiplier for stamina-regeneration bonuses provided by mead/potion-like status effects. 1 is vanilla/no-op."); MeadEitrRecoveryBonusMultiplier = BindConfig("Resources", "MeadEitrRecoveryBonusMultiplier", 1f, "Multiplier for eitr-regeneration bonuses provided by mead/potion-like status effects. 1 is vanilla/no-op."); MeadHealthRegenBonusMultiplier = BindConfig("Resources", "MeadHealthRegenBonusMultiplier", 1f, "Multiplier for health-regeneration bonuses provided by mead/potion-like status effects. 1 is vanilla/no-op."); GlobalUnbreakableGear = BindConfig("Resources", "GlobalUnbreakableGear", defaultValue: false, "If true, all gear durability loss is restored after use. Applies to weapons, shields, armor, tools, staffs, bows, and other durability-using equipment."); GlobalDurabilityConsumptionMultiplier = BindConfig("Resources", "GlobalDurabilityConsumptionMultiplier", 1f, "Global gear durability consumption multiplier. 1 is vanilla/no-op, 0 prevents durability loss, 2 doubles durability loss. Stacks with per-gear durability consumption multipliers."); GlobalBaseDurabilityMultiplier = BindConfig("Resources", "GlobalBaseDurabilityMultiplier", 1f, "Global base gear durability multiplier. 1 is vanilla/no-op. Multiplies the item's base durability before quality-level durability is added, and stacks with per-gear base durability and maximum durability multipliers."); GlobalDurabilityMultiplier = BindConfig("Resources", "GlobalDurabilityMultiplier", 1f, "Global maximum gear durability multiplier. 1 is vanilla/no-op, 2 doubles max durability, 0.5 halves max durability. Stacks with per-gear durability multipliers."); EnableCounterDamage = BindConfig("Counter Damage", "EnableCounterDamage", defaultValue: true, "Master switch for counter-damage. Category settings decide which weapon types and which attack buttons can use it."); OnlyPlayersCanDealCounterDamage = BindConfig("Counter Damage", "OnlyPlayersCanDealCounterDamage", defaultValue: true, "If true, only player attacks can trigger counter-damage. If false, mobs using eligible melee attacks can also trigger it."); HyperArmorMitigationMode = BindConfig("Hyperarmor", "HyperArmorDamageMitigationMode", HyperArmorDamageMitigationMode.RawHitData, "How hyperarmor damage reduction is applied. RawHitData is reliable but interacts with armor nonlinearly; FinalHealthRestore tries to correct final HP loss; RawHitDataAndFinalHealthRestore does both for reliability."); MitigateUnknownAttackerDamageDuringHyperArmor = BindConfig("Hyperarmor", "MitigateUnknownAttackerDamageDuringHyperArmor", defaultValue: true, "If true, hyperarmor can mitigate incoming damage even when HitData.GetAttacker() fails. This fixes some enemy hits but may also affect non-environmental unknown-source damage."); EnablePvpDamageModifiers = BindConfig("PvP Damage", "EnablePvpDamageModifiers", defaultValue: true, "Master switch for per-category PvP damage, PvP stagger duration, PvP stagger power, PvP knockback, and PvP-only per-weapon Block Armor controls."); ClearStaggerMeterWhenStaggered = BindConfig("Staggering", "ClearStaggerMeterWhenStaggered", defaultValue: true, "If true, clears a player/PvP-test-dummy stagger meter when an actual stagger break occurs. This prevents leftover meter from carrying through the stagger animation."); ClearEnemyStaggerMeterWhenStaggered = BindConfig("Staggering", "ClearEnemyStaggerMeterWhenStaggered", defaultValue: true, "If true, clears enemy stagger/poise meters when an actual enemy stagger occurs. Goo default is true."); MobMaintainDirectionWhenStaggered = BindConfig("Staggering", "MobMaintainDirectionWhenStaggered", defaultValue: true, "If true, non-player characters keep their current facing direction when staggered instead of snapping to face opposite the incoming stagger force. Goo default is true; Reset to Vanilla disables it."); PlayerMaintainDirectionWhenStaggered = BindConfig("Staggering", "PlayerMaintainDirectionWhenStaggered", defaultValue: true, "If true, players keep their current facing direction when staggered instead of snapping to face opposite the incoming stagger force. Goo default is true; Reset to Vanilla disables it."); SpawnPvpTestDummy = BindConfig("Debug", "SpawnPvpTestDummy", defaultValue: false, "One-shot debug button. Set true to spawn a persistent GCO-marked TrainingDummy in front of the local player for PvP damage/stagger testing, then this automatically returns to false."); RemovePvpTestDummies = BindConfig("Debug", "RemovePvpTestDummies", defaultValue: false, "One-shot debug button. Set true to remove all GCO-marked PvP test dummies in the loaded area, then this automatically returns to false."); PvpTestDummyHealth = BindConfig("Debug", "PvpTestDummyHealth", 250f, "Max health for GCO PvP test dummies. Goo testing default is 250. A dummy immediately restores to full health only when its health bar is completely depleted, allowing repeat testing without a timed heal."); PvpTestDummyArmor = BindConfig("Debug", "PvpTestDummyArmor", 150f, "Armor points applied to incoming player attacks on GCO PvP test dummies using Valheim's normal armor formula. Goo testing default is 150."); if (Mathf.Approximately(PvpTestDummyArmor.Value, 118f)) { PvpTestDummyArmor.Value = 150f; } PvpTestDummyStaggerThresholdMultiplier = BindConfig("Debug", "PvpTestDummyStaggerThresholdMultiplier", 1f, "Multiplier over the vanilla player stagger break point for GCO PvP test dummies. Effective threshold is max health × player stagger factor × this value."); EnemyStaggerDamageDealtMultiplier = BindConfig("Staggering", "EnemyStaggerDamageDealtMultiplier", 1f, "Multiplier for stagger/poise damage dealt by enemies. 1 is vanilla."); EnemyKnockbackDealtMultiplier = BindConfig("Knockback", "EnemyKnockbackDealtMultiplier", 0.7f, "Multiplier for knockback/push force dealt by enemies to players. Goo default is 0.7; 1 is vanilla/no-op; 0 disables enemy knockback."); EnemyDamageToBuildingsMultiplier = BindConfig("Enemies", "EnemyDamageToBuildingsMultiplier", 0.2f, "Global multiplier for damage that enemy-owned hits deal to WearNTear buildings/pieces. Goo default is 0.2x so enemies deal reduced structure damage; vanilla/no-op is 1."); EnemySpeedMultiplier = BindConfig("Enemies", "EnemySpeedMultiplier", 1f, "Generic enemy speed multiplier. Scales enemy movement speed and stacks with enemy attack/movement animation-speed multipliers. 1 is vanilla/no-op."); EnemyAttackAnimationSpeedMultiplier = BindConfig("Enemies", "EnemyAttackAnimationSpeedMultiplier", 1f, "Multiplier for enemy attack animation speed. Stacks with EnemySpeedMultiplier and vanilla/world-difficulty behavior. 1 is vanilla."); EnemyMovementAnimationSpeedMultiplier = BindConfig("Enemies", "EnemyMovementAnimationSpeedMultiplier", 1f, "Experimental multiplier for enemy non-attack movement animation speed. Stacks with EnemySpeedMultiplier. 1 is vanilla."); EnemySizeMultiplier = BindConfig("Enemies", "EnemySizeMultiplier", 1f, "Multiplier for enemy transform scale after vanilla/world-difficulty size scaling. 1 is vanilla. Enemy melee hit-ray range, size/thickness, height, and offset scale proportionally with this size multiplier."); EnemyAttackHitRayRangeMultiplier = BindConfig("Enemies", "EnemyAttackHitRayRangeMultiplier", 1f, "Enemy melee attack hit-ray range multiplier. Final enemy melee range is vanilla range x this value x EnemySizeMultiplier. 1 is vanilla/no-op."); EnemyAttackHitRaySizeMultiplier = BindConfig("Enemies", "EnemyAttackHitRaySizeMultiplier", 1f, "Enemy melee attack hit-ray size/thickness multiplier. Final enemy ray width is vanilla width x this value x EnemySizeMultiplier. 1 is vanilla/no-op."); EnemyAttackHitRayHeightMultiplier = BindConfig("Enemies", "EnemyAttackHitRayHeightMultiplier", 1f, "Enemy melee attack hit-ray start-height multiplier. Final enemy hit-ray height is vanilla height x this value x EnemySizeMultiplier. 1 is vanilla/no-op."); EnemyAttackHitRayOffsetMultiplier = BindConfig("Enemies", "EnemyAttackHitRayOffsetMultiplier", 1f, "Enemy melee attack hit-ray offset multiplier. Final enemy offset is vanilla offset x this value x EnemySizeMultiplier. 1 is vanilla/no-op."); EnemyBaseRotationFactor = BindConfig("Enemies", "EnemyBaseRotationFactor", 1f, "Multiplier for enemy generic movement rotation speed. This stacks on top of vanilla/world-difficulty behavior. 1 is vanilla."); EnemyAttackRotationFactor = BindConfig("Enemies", "EnemyAttackRotationFactor", 1f, "Multiplier for enemy attack-rotation speed. This stacks on top of vanilla/world-difficulty behavior. 1 is vanilla."); EnemyLockRotationAfterHitRay = BindConfig("Enemies", "EnemyLockRotationAfterHitRay", defaultValue: true, "If true, enemy/mob attacks set their current Attack.m_speedFactorRotation to 0 after the attack hit ray/hitbox event has fired, then restore normal attack rotation when the attack animation ends. Goo default is true; vanilla/no-op is false."); EnemyPoiseMultiplier = BindConfig("Staggering", "EnemyPoiseMultiplier", 1f, "Multiplier for enemy stagger threshold/poise. 2 means enemies require twice as much accumulated stagger damage to stagger. 1 is vanilla."); EnemyStaggerDecaySpeedMultiplier = BindConfig("Staggering", "EnemyStaggerDecaySpeedMultiplier", 1f, "Multiplier for enemy stagger/poise decay speed after cooldown. Vanilla full-bar decay time is 5 seconds; 2 decays twice as fast. 1 is vanilla."); EnemyStaggerDecayCooldownSeconds = BindConfig("Staggering", "EnemyStaggerDecayCooldownSeconds", 1f, "Seconds after non-staggering enemy stagger damage before accumulated stagger starts decaying. Repeated non-staggering stagger damage refreshes the cooldown; hits that actually stagger do not start cooldown, so decay can resume immediately during stagger. Vanilla has no cooldown. Goo ruleset default is 1."); HitStopDurationMultiplier = BindConfig("Hit-Stop / Hitlag", "HitStopDurationMultiplier", 1f, "Global multiplier for Valheim's hardcoded melee hit FreezeFrame duration on categories where hit-stop is supported. 1 keeps vanilla 0.15s; 0 disables it; 0.5 halves it. Per-attack duration multipliers stack with this value."); HitStopDurationOverrideSeconds = BindConfig("Hit-Stop / Hitlag", "HitStopDurationOverrideSeconds", -1f, "Optional absolute hit-stop duration in seconds for supported hit-stop attacks. -1 uses HitStopDurationMultiplier and the per-attack duration multiplier. 0 disables hit-stop. This only applies to player melee hit FreezeFrame calls during tracked attacks."); AttackMovementGroundingModeConfig = BindConfig("Player", "AttackMovementGroundingMode", AttackMovementGroundingMode.GroundOnly, "Controls whether per-weapon AttackMovementModifier applies while airborne. Always applies the multiplier during the configured attack timing even in air. GroundOnly applies it only while the player is grounded, so 0x attack movement does not remove air control during jumps/airborne attacks."); BetterFreeAim = BindConfig("Player", "BetterFreeAim", defaultValue: false, "Alternative unlocked-melee facing mode. When enabled while Valheim's Attack Towards Look Direction setting is disabled, starting a melee attack or block with movement input faces the player toward movement-input direction without changing the camera/crosshair. It does nothing while the vanilla setting is enabled. Goo/fresh default is off."); EnableNegativeStamina = BindConfig("Resources", "EnableNegativeStamina", defaultValue: true, "Goo preset: true. If true, stamina-consuming actions may spend below 0 down to NegativeStaminaFloor."); NegativeStaminaFloor = BindConfig("Resources", "NegativeStaminaFloor", -50f, "Fixed lower stamina floor used by EnableNegativeStamina. Goo preset default is -50. Spending cannot push stamina below this value."); NegativeStaminaActionGate = BindConfig("Resources", "NegativeStaminaActionGate", NegativeStaminaActionGateMode.RequireCostWithinFloor, "Controls when negative stamina allows a stamina-consuming action. RequireCostWithinFloor allows an action only if current stamina is above 0 and current - cost stays at or above NegativeStaminaFloor. PositiveOnlyLegacy reproduces the old behavior where any positive stamina allowed the action, then spending clamped to the floor."); StaminaRecoveryRate = BindConfig("Resources", "StaminaRecoveryRate", 1.5f, "Multiplier for player stamina regeneration. Goo preset default is 1.5; vanilla is 1."); StaminaRegenCurve = BindConfig("Resources", "StaminaRegenCurve", StaminaRegenCurveMode.Vanilla, "Controls the stamina regeneration curve. Vanilla is faster at low stamina. Constant is a flat 9 stamina/sec before StaminaRecoveryRate and status/global multipliers. Reverted is the vanilla curve inverted so regeneration is slower at low stamina and faster at high stamina."); RunStaminaConsumptionRate = BindConfig("Resources", "RunStaminaConsumptionRate", 1f, "Multiplier for the player's base run stamina drain. 1 is vanilla."); DrainRunStaminaDuringAttacksAndAirborne = BindConfig("Resources", "DrainRunStaminaDuringAttacksAndAirborne", defaultValue: false, "If false, suppresses extra Shift/run stamina drain while the player is in an attack animation or airborne. Attack, jump, dodge, and other direct stamina costs still apply normally. Goo preset default is false; vanilla is true."); JumpStaminaConsumptionRate = BindConfig("Resources", "JumpStaminaConsumptionRate", 0.5f, "Multiplier for the player's base jump stamina cost. Goo preset default is 0.5; vanilla is 1."); RollStaminaConsumptionRate = BindConfig("Resources", "RollStaminaConsumptionRate", 1f, "Multiplier for the player's base roll/dodge stamina cost. 1 is vanilla."); CrouchStaminaConsumptionRate = BindConfig("Resources", "CrouchStaminaConsumptionRate", 0f, "Multiplier for the player's base sneak/crouch stamina drain while moving crouched. Goo preset default is 0; vanilla is 1."); PlayerBaseHealth = BindConfig("Resources", "PlayerBaseHealth", 25f, "Direct player base health value before food. Source-current vanilla in this Valheim build is 25."); PlayerBaseStamina = BindConfig("Resources", "PlayerBaseStamina", 75f, "Direct player base stamina value before food. Source-current vanilla in the uploaded Player.txt is 75."); PlayerBaseEitr = BindConfig("Resources", "PlayerBaseEitr", 0f, "Direct bonus max eitr added after food/status max-eitr calculation. Goo preset default is 0, matching vanilla/no free base eitr."); PlayerBaseEitrRegenRate = BindConfig("Resources", "PlayerBaseEitrRegenRate", 1f, "Multiplier for the player's base eitr regeneration value. Goo preset default is 1; vanilla is 1."); PlayerBaseArmor = BindConfig("Player", "PlayerBaseArmor", 0f, "Direct player base armor added before status-effect armor modifiers. Vanilla is 0."); PlayerBaseCarryWeight = BindConfig("Player", "PlayerBaseCarryWeight", 300f, "Direct player base carry weight before status-effect carry modifiers. Goo preset default is 300; vanilla is 300."); MigrateCompactConfigValue("Player", "PlayerMaxCarryWeight", "Player base carry weight", PlayerBaseCarryWeight); MigrateCompactConfigValue("Player", "Player max carry weight", "Player base carry weight", PlayerBaseCarryWeight); PlayerJumpForceMultiplier = BindConfig("Player", "PlayerJumpForceMultiplier", 1f, "Multiplier for the player prefab/runtime jump force. 1 is vanilla. This replaces the old direct PlayerJumpForce override."); PlayerRunSpeedMultiplier = BindConfig("Player", "PlayerRunSpeedMultiplier", 1f, "Multiplier for the player prefab/runtime run speed. 1 is vanilla."); PlayerJogSpeedMultiplier = BindConfig("Player", "PlayerJogSpeedMultiplier", 1f, "Multiplier for the player's normal jog speed. 1 is vanilla."); PlayerWalkSpeedMultiplier = BindConfig("Player", "PlayerWalkSpeedMultiplier", 1f, "Multiplier for the player prefab/runtime walk speed. 1 is vanilla."); PlayerCrouchSpeedMultiplier = BindConfig("Player", "PlayerCrouchSpeedMultiplier", 1f, "Multiplier for the player prefab/runtime crouch speed. 1 is vanilla."); PlayerBaseRotationFactor = BindConfig("Player", "PlayerBaseRotationFactor", 2f, "Multiplier for generic player movement rotation while not attacking. Goo ruleset default is 2; attack rotation remains controlled by per-attack rotation configs."); PlayerSizeMultiplier = BindConfig("Player", "PlayerSizeMultiplier", 1f, "Player size multiplier. 1 is vanilla. Non-1 values scale the player model and GCO player-owned melee attack reach/range alongside it."); PlayerPoiseMultiplier = BindConfig("Staggering", "PlayerPoiseMultiplier", 1f, "Multiplier for player stagger threshold/poise. 2 means the player requires twice as much accumulated stagger damage to stagger. 1 is vanilla."); PlayerStaggerDecaySpeedMultiplier = BindConfig("Staggering", "PlayerStaggerDecaySpeedMultiplier", 1f, "Multiplier for player stagger/poise decay speed after cooldown. Vanilla full-bar decay time is 5 seconds; 2 decays twice as fast. 1 is vanilla."); PlayerStaggerDecayCooldownSeconds = BindConfig("Staggering", "PlayerStaggerDecayCooldownSeconds", 1f, "Seconds after non-staggering player stagger damage before accumulated stagger starts decaying. Repeated non-staggering stagger damage refreshes the cooldown; hits that actually stagger do not start cooldown, so decay can resume immediately during stagger. Vanilla has no cooldown. Goo ruleset default is 1."); AirborneAttackMinimumAirTimeSeconds = BindConfig("Player", "AirborneAttackMinimumAirTimeSeconds", 1f, "Minimum continuous airborne time before player-owned airborne attack bonuses or special jump attacks can trigger. Goo ruleset default is 1 second to avoid tiny ground gaps accidentally triggering jump attacks."); PlayerHealingRate = BindConfig("Resources", "PlayerHealingRate", 1f, "Multiplier for player food/status healing tick rate after vanilla/status calculations. 1 is vanilla."); FallDamageStartHeight = BindConfig("Player", "FallDamageStartHeight", 8f, "Fall height in meters before fall damage begins. Goo preset default is 8; vanilla is 4."); FallDamagePerMeter = BindConfig("Player", "FallDamagePerMeter", 6.25f, "Damage per meter after FallDamageStartHeight. Vanilla effectively equals 6.25 because (height - 4) / 16 * 100."); FallDamageCap = BindConfig("Player", "FallDamageCap", 100f, "Maximum fall damage after modifiers. Vanilla is 100."); RollAnimationSpeed = BindConfig("Player", "RollAnimationSpeed", 1f, "Temporary Animator.speed multiplier while the player is in the dodge/roll animation. 1 is vanilla."); RollDistanceMultiplier = BindConfig("Player", "RollDistanceMultiplier", 1f, "Multiplier for horizontal dodge-roll displacement from root motion. Applies to native rolls and GCO's designated-roll input without changing animation speed, stamina cost, invulnerability timing, or recovery. 1 is vanilla."); ThrowableStaminaConsumptionRate = BindConfig("Throwables", "StaminaConsumptionRate", 1f, "Multiplier for attack stamina consumed by bomb-style thrown consumables/projectiles such as Ooze Bombs. Spears, the Abyssal Harpoon, arrows, bolts, and magic projectiles are excluded. 1 is vanilla/no-op."); ThrowableDamageMultiplier = BindConfig("Throwables", "DamageMultiplier", 1f, "Multiplier for outgoing damage from bomb-style thrown consumables/projectiles, including direct projectile damage and projectile-created lingering AOE damage. Applied once through propagated HitData so direct and lingering damage are not double-multiplied. Spears, the Abyssal Harpoon, arrows, bolts, and magic projectiles are excluded. 1 is vanilla/no-op."); ThrowableProjectileSpeedMultiplier = BindConfig("Throwables", "ProjectileSpeedMultiplier", 1f, "Multiplier for launch velocity of bomb-style thrown consumables/projectiles. Spears, the Abyssal Harpoon, arrows, bolts, and magic projectiles are excluded. 1 is vanilla/no-op."); ThrowableThrowAnimationSpeedMultiplier = BindConfig("Throwables", "ThrowAnimationSpeedMultiplier", 1f, "Multiplier for the throw attack animation speed of bomb-style thrown consumables/projectiles. Stacks with the applicable attack/category and global animation-speed multipliers. Spears, the Abyssal Harpoon, arrows, bolts, and magic projectiles are excluded. 1 is vanilla/no-op."); DebugLogging = BindConfig("Debug", "DebugLogging", defaultValue: false, "General concise GCO diagnostic logging. Logs useful player/enemy combat, movement, resource, status, stagger, and attack events. Intended for troubleshooting only."); VerboseDebugLogging = BindConfig("Debug", "VerboseDebugLogging", defaultValue: false, "Extra-detailed GCO diagnostic logging. Requires DebugLogging. Enables all categorized debug channels and prints a throttled per-second local-player state snapshot covering resources, movement input, active attack role, weapon category, blocking/dodging, and GCO debug toggle states. Intended for short troubleshooting sessions only."); DebugAttackLifecycle = BindConfig("Debug", "DebugAttackLifecycle", defaultValue: false, "If true, logs attack-state lifecycle decisions when DebugLogging is also enabled. Intended for short troubleshooting sessions."); DebugSledgeAlternateMoveset = BindConfig("Debug", "DebugSledgeAlternateMoveset", defaultValue: false, "If true, logs the experimental alternate sledge moveset path in detail when DebugLogging is also enabled: Humanoid secondary requests, temporary template swaps, Attack.Start and hitbox-time template normalization/validation, grounded/airborne/stamina state, authoritative role correction, primary-chain selection, native melee-versus-Area dispatch safeguards, shared sledge trigger-effect suppression for Secondary, mace hit-effect resolution/playback, direct hit-ray settings, blockers, hit counts, damage counts, cleanup, and exception restoration. Intended for short troubleshooting sessions."); DebugCounterDamage = BindConfig("Debug", "DebugCounterDamage", defaultValue: false, "If true, logs counter-damage accept/reject details when DebugLogging is also enabled."); DebugHitStop = BindConfig("Debug", "DebugHitStop", defaultValue: false, "If true, logs hit-stop and hitlag decisions when DebugLogging is also enabled."); DebugMovement = BindConfig("Debug", "DebugMovement", defaultValue: false, "If true, logs movement-related GCO decisions when DebugLogging is also enabled."); DebugHyperArmorLifecycle = BindConfig("Debug", "DebugHyperArmorLifecycle", defaultValue: false, "If true, logs hyperarmor lifecycle transitions when DebugLogging is also enabled."); DebugHyperArmorDamageMitigation = BindConfig("Debug", "DebugHyperArmorDamageMitigation", defaultValue: false, "If true, logs hyperarmor damage-mitigation details when DebugLogging is also enabled."); DebugPlayerSystems = BindConfig("Debug", "DebugPlayerSystems", defaultValue: false, "If true, logs player resource/stat/debug-mode systems when DebugLogging is also enabled."); DebugNegativeStamina = BindConfig("Debug", "DebugNegativeStamina", defaultValue: false, "If true, logs negative-stamina decisions when DebugLogging is also enabled."); DebugRanged = BindConfig("Debug", "DebugRanged", defaultValue: false, "If true, logs ranged/projectile tuning decisions when DebugLogging is also enabled."); DebugBlocking = BindConfig("Debug", "DebugBlocking", defaultValue: false, "If true, logs blocking/parry/Souls-like stamina decisions when DebugLogging is also enabled."); DebugPvpTestDummyOutput = BindConfig("Debug", "DebugPvpTestDummyOutput", defaultValue: false, "If true, logs detailed PvP test-dummy incoming damage, post-hit health, stagger, and depletion-triggered full-heal output when DebugLogging is also enabled."); GooCombatDebugMode = BindConfig("Debug", "GooCombatDebugMode", defaultValue: false, "Master switch for Goo combat testing mode. When enabled, GCO uses Valheim's native god mode for the local player. Debug-only hotkeys/buttons are available only while this is enabled; turning it off forces god mode, debug fly, ghost mode, and no-placement-cost off."); EnableDebugFlyToggle = BindConfig("Debug", "EnableDebugFlyToggle", defaultValue: true, "Allow GooCombatDebugMode to toggle Valheim debug fly through the DebugFly button/keybind. Turning this off also forces the current GCO debug-fly state off."); EnableNoPlacementCostToggle = BindConfig("Debug", "EnableNoPlacementCostToggle", defaultValue: true, "Allow GooCombatDebugMode to toggle Valheim no-placement-cost through the DebugNoCost button/keybind. Turning this off also forces the current GCO no-placement-cost state off."); EnableGhostModeToggle = BindConfig("Debug", "EnableGhostModeToggle", defaultValue: true, "Allow GooCombatDebugMode to toggle Valheim ghost mode through the DebugGhost button/keybind. Turning this off also forces the current GCO ghost-mode state off."); ToggleDebugFly = BindConfig("Debug", "ToggleDebugFly", defaultValue: false, "One-shot button. Set to true to toggle Valheim debug fly while GooCombatDebugMode and EnableDebugFlyToggle are enabled. GCO resets this back to false after processing it."); ToggleDebugFlyKeybind = BindConfig("Debug", "ToggleDebugFlyKeybind", (KeyCode)122, "Physical key that toggles Valheim debug fly while GooCombatDebugMode and EnableDebugFlyToggle are enabled. Uses Valheim's ZInput.GetKeyDown path, so movement keys like W/A/S/D do not cover it up. Set to None to disable."); ToggleNoPlacementCost = BindConfig("Debug", "ToggleNoPlacementCost", defaultValue: false, "One-shot button. Set to true to toggle Valheim's no-placement-cost mode while GooCombatDebugMode and EnableNoPlacementCostToggle are enabled. GCO resets this back to false after processing it."); ToggleNoPlacementCostKeybind = BindConfig("Debug", "ToggleNoPlacementCostKeybind", (KeyCode)110, "Physical key that toggles Valheim's no-placement-cost mode while GooCombatDebugMode and EnableNoPlacementCostToggle are enabled. Uses Valheim's ZInput.GetKeyDown path, so movement keys like W/A/S/D do not cover it up. Set to None to disable."); ToggleGhostMode = BindConfig("Debug", "ToggleGhostMode", defaultValue: false, "One-shot button. Set to true to toggle Valheim ghost mode while GooCombatDebugMode and EnableGhostModeToggle are enabled. GCO resets this back to false after processing it."); ToggleGhostModeKeybind = BindConfig("Debug", "ToggleGhostModeKeybind", (KeyCode)103, "Physical key that toggles Valheim ghost mode while GooCombatDebugMode and EnableGhostModeToggle are enabled. Uses Valheim's ZInput.GetKeyDown path, so movement keys like W/A/S/D do not cover it up. Set to None to disable."); DebugModeHeal = BindConfig("Debug", "DebugModeHeal", defaultValue: false, "One-shot button. Set to true to heal the local player's health, stamina, and eitr while GooCombatDebugMode is enabled. GCO resets this back to false after processing it."); DebugModeHealKeybind = BindConfig("Debug", "DebugModeHealKeybind", (KeyCode)104, "Physical key that heals the local player's health, stamina, and eitr while GooCombatDebugMode is enabled. Uses Valheim's ZInput.GetKeyDown path, so movement keys like W/A/S/D do not cover it up. Set to None to disable."); DebugModeAddRested = BindConfig("Debug", "DebugModeAddRested", defaultValue: false, "One-shot button. Set to true to add or refresh the Rested status effect on the local player while GooCombatDebugMode is enabled. GCO resets this back to false after processing it."); DebugModeAddRestedKeybind = BindConfig("Debug", "DebugModeAddRestedKeybind", (KeyCode)111, "Physical key that adds or refreshes the Rested status effect while GooCombatDebugMode is enabled. Uses Valheim's ZInput.GetKeyDown path, so movement keys like W/A/S/D are intentionally supported."); float? primary2LungeDistanceMultiplier = 2.4f; float? primary3LungeDistanceMultiplier = 2.5f; AddCategory(WeaponCategory.TwoHandedSword, "Two-Handed Swords", 1.5f, 1.5f, HyperArmorMode.Balanced, HyperArmorMode.Balanced, 2.46f, 2.46f, primaryCounter: false, secondaryCounter: true, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 0.7f, 0.7f, 0f, 0f, 0f, 0f, 1.25f, 1f, 1f, null, null, null, 1.5f, 1.5f, 0f, 0f, 2.4f, 2f, primary2LungeDistanceMultiplier, primary3LungeDistanceMultiplier, 1f, 1f, 1f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 6f); BindWeaponSupplementaryConfigs(WeaponCategory.TwoHandedSword, "Two-Handed Swords"); float? primary1DamageRateMultiplier = 2f; float? primary2DamageRateMultiplier = 2f; primary3LungeDistanceMultiplier = 1f; primary2LungeDistanceMultiplier = 1f; AddCategory(WeaponCategory.TwoHandedAxe, "Two-Handed Axes", 1.5f, 1.5f, HyperArmorMode.Balanced, HyperArmorMode.Balanced, 3.44f, 3.44f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 0.7f, 0.7f, 0f, 0f, 0f, 0f, 1.25f, 1f, 1f, primary1DamageRateMultiplier, primary2DamageRateMultiplier, null, 1.5f, 1.5f, 0f, 0f, 2f, 2f, null, null, 1f, 1f, 1f, 1f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 1f, primary3LungeDistanceMultiplier, primary2LungeDistanceMultiplier); BindWeaponSupplementaryConfigs(WeaponCategory.TwoHandedAxe, "Two-Handed Axes"); AddCategory(WeaponCategory.Sledge, "Sledges", 2f, 2f, HyperArmorMode.Balanced, HyperArmorMode.Balanced, 2.23f, 2.23f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 0.7f, 0.7f, 0f, 0f, 0f, 0f, 1.5f, 1f, 1f, null, null, null, 1.5f, 1.5f, 0f, 0f, 2f, 1f, null, null, 1.2f, 1.2f, 1f, 1f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 1f, null, null, 1f, 1f, 1f, -1f, -1f, 1f, 1f, 0.9f, 0.9f); BindWeaponSupplementaryConfigs(WeaponCategory.Sledge, "Sledges"); float? primary1DamageRateMultiplier2 = 2f; float? primary2DamageRateMultiplier2 = 2f; primary2LungeDistanceMultiplier = 1f; primary3LungeDistanceMultiplier = 1f; AddCategory(WeaponCategory.SledgeAlt, "Sledge - Alt Moveset", 1.5f, 1.5f, HyperArmorMode.Balanced, HyperArmorMode.Balanced, 3.44f, 3.44f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 0.7f, 0.7f, 0f, 0f, 0f, 0f, 1.5f, 1f, 1f, primary1DamageRateMultiplier2, primary2DamageRateMultiplier2, null, 1.5f, 1.5f, 0f, 0.2f, 2f, 2f, null, null, 1f, 1f, 1f, 1f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 1f, primary2LungeDistanceMultiplier, primary3LungeDistanceMultiplier); AddCategory(WeaponCategory.Pickaxe, "Pickaxes", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 1f, 1f, 1f, 1f, null, null, 1.2f, 1f, -1f, -1f, primaryLockRotationAfterAttackTrigger: false, secondaryLockRotationAfterAttackTrigger: false); BindWeaponSupplementaryConfigs(WeaponCategory.Pickaxe, "Pickaxes"); AddCategory(WeaponCategory.DualAxes, "Dual Axes", 1f, 2f, HyperArmorMode.Off, HyperArmorMode.Off, 2.4f, 2.4f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 0.8f, null, null, 1f, 1.5f); BindWeaponSupplementaryConfigs(WeaponCategory.DualAxes, "Dual Axes"); AddCategory(WeaponCategory.Atgeir, "Atgeirs", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Balanced, 2.98f, 2.98f, primaryCounter: true, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 0.7f, 1f, 0f, 1f, 0f, 1f, 1f, 1f, null, null, null, 1f, 1f, 1f, 1f, 2f, 1f, null, null, 1f, 1f, 1f, 1f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 6f, 1f, null, null, 1.5f); BindWeaponSupplementaryConfigs(WeaponCategory.Atgeir, "Atgeirs"); AddCategory(WeaponCategory.OneHandedSword, "One-Handed Swords", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Balanced, 2.46f, 2.46f, primaryCounter: false, secondaryCounter: true, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 0f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 1.8f, 2f, null, null, 1f, 1.3f); BindWeaponSupplementaryConfigs(WeaponCategory.OneHandedSword, "One-Handed Swords"); AddCategory(WeaponCategory.OneHandedAxe, "One-Handed Axes", 1.5f, 3f, HyperArmorMode.Off, HyperArmorMode.Balanced, 2.74f, 2.74f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 0f, 1f, 1f, 1f, null, null, null, 1f, 1f, 1f, 1f, 2f, 2f, null, null, 1.4f, 1.5f, 1f, 1f); BindWeaponSupplementaryConfigs(WeaponCategory.OneHandedAxe, "One-Handed Axes"); AddCategory(WeaponCategory.Club, "Clubs and Maces", 1.5f, 1.5f, HyperArmorMode.Off, HyperArmorMode.Balanced, 2.46f, 2.46f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 0f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 1.8f, 2f, null, null, 1f, 1f, 2f, 1f); BindWeaponSupplementaryConfigs(WeaponCategory.Club, "Clubs and Maces"); AddCategory(WeaponCategory.Knife, "Knives", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 2.04f, 2.04f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 1.5f); BindWeaponSupplementaryConfigs(WeaponCategory.Knife, "Knives"); AddCategory(WeaponCategory.DualKnives, "Dual Knives", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 2.04f, 2.04f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 1.5f); BindWeaponSupplementaryConfigs(WeaponCategory.DualKnives, "Dual Knives"); AddCategory(WeaponCategory.Spear, "Spears", 1f, 2f, HyperArmorMode.Off, HyperArmorMode.Off, 0.68f, 0.68f, primaryCounter: true, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 2f); BindWeaponSupplementaryConfigs(WeaponCategory.Spear, "Spears"); AddCategory(WeaponCategory.Bow, "Bows", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 1f, 1f, null, null, 1f, 1f, 2f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 0.85f, null, null, 0.85f); BindWeaponSupplementaryConfigs(WeaponCategory.Bow, "Bows"); AddCategory(WeaponCategory.Crossbow, "Crossbows", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 1f, 1f, null, null, 1f, 1f, 2f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 0.7f, null, null, 0.7f); BindWeaponSupplementaryConfigs(WeaponCategory.Crossbow, "Crossbows"); primary3LungeDistanceMultiplier = 1.5f; primary2LungeDistanceMultiplier = 1f; AddCategory(WeaponCategory.Unarmed, "Unarmed", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 2f, primary3LungeDistanceMultiplier, null, 1.2f, 1.3f, 2f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 1f, primary2LungeDistanceMultiplier, null, 1.3f); BindWeaponSupplementaryConfigs(WeaponCategory.Unarmed, "Unarmed"); AddCategory(WeaponCategory.Other, "Other", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f, primaryCounter: false, secondaryCounter: false, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, null, null, null, 1f, 1f, 0f, 0f, 2f, 2f); BindWeaponSupplementaryConfigs(WeaponCategory.Other, "Other"); AddShieldTypeConfigs(); LightArmorBaseArmorMultiplier = BindConfig("Armor", "LightArmorBaseArmorMultiplier", 1f, "Base armor multiplier for light/no-penalty armor. 1 is vanilla and tooltips update through the same value."); MediumArmorBaseArmorMultiplier = BindConfig("Armor", "MediumArmorBaseArmorMultiplier", 1f, "Base armor multiplier for medium/roughly -2% movement armor. 1 is vanilla and tooltips update through the same value."); HeavyArmorBaseArmorMultiplier = BindConfig("Armor", "HeavyArmorBaseArmorMultiplier", 1f, "Base armor multiplier for heavy/roughly -5% movement armor. 1 is vanilla and tooltips update through the same value."); UseCustomLightArmorMovementModifier = BindConfig("Armor", "UseCustomLightArmorMovementModifier", defaultValue: true, "If true, override the final movement modifier for light/no-penalty armor; if false, use the item's vanilla movement modifier."); LightArmorMovementModifierPercent = BindConfig("Armor", "LightArmorMovementModifierPercent", 0f, "Final movement modifier percent for light/no-penalty armor when UseCustomLightArmorMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); UseCustomMediumArmorMovementModifier = BindConfig("Armor", "UseCustomMediumArmorMovementModifier", defaultValue: true, "If true, override the final movement modifier for medium/roughly -2% movement armor; if false, use the item's vanilla movement modifier."); MediumArmorMovementModifierPercent = BindConfig("Armor", "MediumArmorMovementModifierPercent", 0f, "Final movement modifier percent for medium/roughly -2% movement armor when UseCustomMediumArmorMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); UseCustomHeavyArmorMovementModifier = BindConfig("Armor", "UseCustomHeavyArmorMovementModifier", defaultValue: true, "If true, override the final movement modifier for heavy/roughly -5% movement armor; if false, use the item's vanilla movement modifier."); HeavyArmorMovementModifierPercent = BindConfig("Armor", "HeavyArmorMovementModifierPercent", 0f, "Final movement modifier percent for heavy/roughly -5% movement armor when UseCustomHeavyArmorMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); AddStaffConfigs(); BindGearDurabilityConfigs(); if (ShouldRunConfigMigration()) { ProcessConfigVersionOverwrite(); } ProcessGeneralPresetToggles(); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private void BindSledgeAlternateMovesetHitRayConfigs() { string section = "Sledge - Alt Moveset Hit Ray"; SledgePrimary1DirectHitRayStartHeightMeters = BindConfig(section, "Primary1DirectHitRayStartHeightMeters", 1.3f, "[Experimental] Absolute hit-ray start height in meters for sledge Primary1 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary1DirectHitRayRangeMeters = BindConfig(section, "Primary1DirectHitRayRangeMeters", 2.4f, "[Experimental] Absolute hit-ray range in meters for sledge Primary1 when UseAlternateMoveset is true. Goo default is 2.4 meters. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary1DirectHitRayAngleDegrees = BindConfig(section, "Primary1DirectHitRayAngleDegrees", 135f, "[Experimental] Absolute horizontal fan angle in degrees for sledge Primary1 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary1DirectHitRayThicknessMeters = BindConfig(section, "Primary1DirectHitRayThicknessMeters", 0.65f, "[Experimental] Absolute hit-ray thickness in meters for sledge Primary1 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary2DirectHitRayStartHeightMeters = BindConfig(section, "Primary2DirectHitRayStartHeightMeters", 1.3f, "[Experimental] Absolute hit-ray start height in meters for sledge Primary2 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary2DirectHitRayRangeMeters = BindConfig(section, "Primary2DirectHitRayRangeMeters", 2.4f, "[Experimental] Absolute hit-ray range in meters for sledge Primary2 when UseAlternateMoveset is true. Goo default is 2.4 meters. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary2DirectHitRayAngleDegrees = BindConfig(section, "Primary2DirectHitRayAngleDegrees", 135f, "[Experimental] Absolute horizontal fan angle in degrees for sledge Primary2 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgePrimary2DirectHitRayThicknessMeters = BindConfig(section, "Primary2DirectHitRayThicknessMeters", 0.65f, "[Experimental] Absolute hit-ray thickness in meters for sledge Primary2 when UseAlternateMoveset is true. This is not a multiplier and has no effect in the normal sledge moveset."); SledgeRunningDirectHitRayStartHeightMeters = BindConfig(section, "RunningDirectHitRayStartHeightMeters", 1.3f, "[Experimental] Absolute hit-ray start height in meters for sledge running attacks when UseAlternateMoveset and EnableRunningAttack are both true. This is not a multiplier."); SledgeRunningDirectHitRayRangeMeters = BindConfig(section, "RunningDirectHitRayRangeMeters", 2.4f, "[Experimental] Absolute hit-ray range in meters for sledge running attacks when UseAlternateMoveset and EnableRunningAttack are both true. Goo default is 2.4 meters. This is not a multiplier."); SledgeRunningDirectHitRayAngleDegrees = BindConfig(section, "RunningDirectHitRayAngleDegrees", 135f, "[Experimental] Absolute horizontal fan angle in degrees for sledge running attacks when UseAlternateMoveset and EnableRunningAttack are both true. This is not a multiplier."); SledgeRunningDirectHitRayThicknessMeters = BindConfig(section, "RunningDirectHitRayThicknessMeters", 0.65f, "[Experimental] Absolute hit-ray thickness in meters for sledge running attacks when UseAlternateMoveset and EnableRunningAttack are both true. This is not a multiplier."); } private void AddShieldTypeConfigs() { ShieldBucklerBlockConfig = BindShieldTypeBlockConfig("Shields - Bucklers", 1f, -3f); ShieldBucklerDamageTakenOnBlockedHit = BindBlockedHitDamageTakenConfig("Shields - Bucklers"); ShieldBucklerEffectiveBlockAngleMultiplier = BindConfig("Shields - Bucklers", "EffectiveBlockAngleMultiplier", 1f, "Effective block angle multiplier for bucklers. Valheim's default block sector is 180 degrees; 1 is vanilla/no-op."); ShieldMediumBlockConfig = BindShieldTypeBlockConfig("Shields - Medium Shields", 1f, -3f); ShieldMediumDamageTakenOnBlockedHit = BindBlockedHitDamageTakenConfig("Shields - Medium Shields"); ShieldMediumEffectiveBlockAngleMultiplier = BindConfig("Shields - Medium Shields", "EffectiveBlockAngleMultiplier", 1f, "Effective block angle multiplier for medium shields. Valheim's default block sector is 180 degrees; 1 is vanilla/no-op."); ShieldTowerBlockConfig = BindShieldTypeBlockConfig("Shields - Tower Shields", 1f, -8f, 1.5f); ShieldTowerDamageTakenOnBlockedHit = BindBlockedHitDamageTakenConfig("Shields - Tower Shields", globalPrefix: false, 0f); ShieldTowerEffectiveBlockAngleMultiplier = BindConfig("Shields - Tower Shields", "EffectiveBlockAngleMultiplier", 1.2f, "Effective block angle multiplier for tower shields. Goo default is 1.2x, turning Valheim's 180-degree block sector into a 216-degree effective sector."); } private WeaponBlockExtraConfig BindWeaponBlockExtras(string section) { return new WeaponBlockExtraConfig(BindConfig(section, "CanParry", defaultValue: true, "If false, this weapon/tool/unarmed category can still block normally but cannot perform timed parries. If true and the item has no native parry bonus, GCO supplies a 1.5x base timed-block bonus so the toggle genuinely enables parrying."), BindConfig(section, "ParryBonusMultiplier", 1f, "Multiplier for this weapon category's parry bonus. Multiplies vanilla and updates tooltip. 1 is vanilla/no-op."), BindConfig(section, "ParryWindowTimeMultiplier", 1f, "Multiplier for this weapon category's parry/timed-block input window. Stacks with GlobalParryWindowTimeMultiplier. 1 keeps Valheim's 0.25 second window; 2 doubles it; 0 disables timed parries with this blocker."), BindConfig(section, "PvpParryBonusMultiplier", 1f, "PvP-only parry bonus multiplier for this weapon category. Stacks on ParryBonusMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockForceMultiplier", 1f, "PvP-only Block Force multiplier for this weapon category. Stacks on BlockForceMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockBreakThresholdMultiplier", 1f, "PvP-only block-break threshold multiplier for this weapon category. Stacks on BlockBreakThresholdMultiplier; 5 means incoming PvP stagger contributes one fifth while blocking."), BindConfig(section, "SoulsLikeBlockBreak", defaultValue: false, "Souls-like block-break rule. If true, block only breaks/staggers when the blocked attack empties the player's stamina bar."), BindConfig(section, "SoulsLikeBlockStaminaMode", defaultValue: false, "If true, block stamina cost with this weapon/tool uses the configured Souls-like formula. This replaces Valheim's vanilla block stamina formula for this blocker."), BindConfig(section, "SoulsLikeBlockStaminaBaseRate", 0.7f, "Base rate used by Souls-like block stamina for this weapon/tool. Formula: stamina consumed = stagger damage x max(0, 1 - max(0, base rate + effective block armor / 1000 - 0.05)) / block-break threshold. Goo/default is 0.7."), BindConfig(section, "KnockbackTakenWhileBlockingMultiplier", 1f, "Knockback/push-force taken multiplier while this weapon category successfully blocks. Does not apply when block breaks. 1 is vanilla/no-op."), BindConfig(section, "BlockingMovementSpeedMultiplier", 1f, "Movement speed multiplier while actively blocking with this weapon category. Stacks with GlobalBlockingMovementSpeedMultiplier. 1 is vanilla/no-op."), BindConfig(section, "EnableRunningWhileBlocking", defaultValue: true, "If true, holding Run while actively blocking with this weapon category is allowed. Valheim's normal run stamina drain is used, so this costs run stamina instead of being a free block-speed boost."), BindConfig(section, "RunningWhileBlockingMovementSpeedMultiplier", 0.7f, "Extra blocking movement-speed multiplier while holding Run and actively blocking with this weapon category. Goo default is 0.7x; 1 leaves normal blocking speed unchanged.")); } private BlockedHitDamageTakenConfig BindBlockedHitDamageTakenConfig(string section, bool globalPrefix = false, float physicalDefault = 1f, float elementalDefault = 1f, float genericDefault = 1f) { string text = (globalPrefix ? "Global multiplier" : "Multiplier"); return new BlockedHitDamageTakenConfig(BindConfig(section, Prefix("GenericDamageTakenOnBlockedHitMultiplier"), genericDefault, text + " for generic/untyped damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("BluntDamageTakenOnBlockedHitMultiplier"), physicalDefault, text + " for blunt damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("SlashDamageTakenOnBlockedHitMultiplier"), physicalDefault, text + " for slash damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("PierceDamageTakenOnBlockedHitMultiplier"), physicalDefault, text + " for pierce damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("ChopDamageTakenOnBlockedHitMultiplier"), physicalDefault, text + " for chop damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("PickaxeDamageTakenOnBlockedHitMultiplier"), physicalDefault, text + " for pickaxe damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("FireDamageTakenOnBlockedHitMultiplier"), elementalDefault, text + " for fire damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("FrostDamageTakenOnBlockedHitMultiplier"), elementalDefault, text + " for frost damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("LightningDamageTakenOnBlockedHitMultiplier"), elementalDefault, text + " for lightning damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("PoisonDamageTakenOnBlockedHitMultiplier"), elementalDefault, text + " for poison damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op."), BindConfig(section, Prefix("SpiritDamageTakenOnBlockedHitMultiplier"), elementalDefault, text + " for spirit damage that slips through a successful non-broken block. Applies only after the hit is actually blocked. 1 is vanilla/no-op.")); string Prefix(string key) { if (!globalPrefix) { return key; } return "Global" + key; } } private ShieldTypeBlockConfig BindShieldTypeBlockConfig(string section, float blockArmorDefault = 1f, float movementDefault = 0f, float blockBreakDefault = 1f, float blockStaminaDefault = 1f) { return new ShieldTypeBlockConfig(BindConfig(section, "CanParry", !section.Contains("Tower"), "If false, this shield type can still block normally but cannot perform timed parries. If true and the shield has no native parry bonus, GCO supplies a 1.5x base timed-block bonus. Goo/default is off for tower shields and on for other shields."), BindConfig(section, "BlockArmorMultiplier", blockArmorDefault, "Multiplier for this shield type's Block Armor. 1 is vanilla/no-op."), BindConfig(section, "BlockForceMultiplier", 1f, "Multiplier for this shield type's Block Force. 1 is vanilla/no-op; 0 removes Block Force and hides the tooltip line."), BindConfig(section, "ParryBonusMultiplier", 1f, "Multiplier for this shield type's parry bonus. Multiplies vanilla and updates tooltip. 1 is vanilla/no-op."), BindConfig(section, "ParryWindowTimeMultiplier", 1f, "Multiplier for this shield type's parry/timed-block input window. Stacks with GlobalParryWindowTimeMultiplier. 1 keeps Valheim's 0.25 second window; 2 doubles it; 0 disables timed parries with this shield type."), BindConfig(section, "ParryAdrenalineMultiplier", 1f, "Multiplier for this shield type's parry adrenaline. 1 is vanilla/no-op."), BindConfig(section, "BlockStaminaConsumptionRate", blockStaminaDefault, "Multiplier for final vanilla block stamina usage with this shield type when SoulsLikeBlockStaminaMode is disabled. 1 is vanilla/no-op."), BindConfig(section, "BlockingStaminaRegenMultiplier", 1f, "Stamina regeneration multiplier while actively blocking with this shield type. Stacks with GlobalBlockingStaminaRegenMultiplier. 1 is vanilla/no-op."), BindConfig(section, "BlockBreakThresholdMultiplier", blockBreakDefault, "Multiplier for general block-break threshold with this shield type. Goo default is 1 for bucklers/medium shields and 1.5 for tower shields; vanilla/no-op is 1."), BindConfig(section, "PvpBlockArmorMultiplier", 1f, "PvP-only Block Armor multiplier for this shield type. Stacks on BlockArmorMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockForceMultiplier", 1f, "PvP-only Block Force multiplier for this shield type. Stacks on BlockForceMultiplier and does not change tooltip."), BindConfig(section, "PvpParryBonusMultiplier", 1f, "PvP-only parry bonus multiplier for this shield type. Stacks on ParryBonusMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockBreakThresholdMultiplier", 1f, "PvP-only block-break threshold multiplier for this shield type. Stacks on BlockBreakThresholdMultiplier."), BindConfig(section, "SoulsLikeBlockBreak", section.Contains("Tower"), "Souls-like block-break rule. If true, block only breaks/staggers when the blocked attack empties the player's stamina bar."), BindConfig(section, "SoulsLikeBlockStaminaMode", section.Contains("Tower"), "If true, block stamina cost with this shield uses the configured Souls-like formula. This replaces Valheim's vanilla block stamina formula for this shield."), BindConfig(section, "SoulsLikeBlockStaminaBaseRate", 0.7f, "Base rate used by Souls-like block stamina for this shield. Formula: stamina consumed = stagger damage x max(0, 1 - max(0, base rate + effective block armor / 1000 - 0.05)) / block-break threshold. Goo/default is 0.7."), BindConfig(section, "KnockbackTakenWhileBlockingMultiplier", section.Contains("Tower") ? 0f : 1f, "Knockback/push-force taken multiplier while this shield successfully blocks. Does not apply when block breaks. 1 is vanilla/no-op."), BindConfig(section, "MovementModifierPercent", movementDefault, "Equipment movement modifier for this shield type in percent. Goo default is -3 for shields and -8 for tower shields; 0 leaves vanilla/no extra modifier."), BindConfig(section, "BlockingMovementSpeedMultiplier", 1f, "Movement speed multiplier while actively blocking with this shield type. 1 is vanilla/no-op."), BindConfig(section, "EnableRunningWhileBlocking", !section.Contains("Tower"), "If true, holding Run while actively blocking with this shield type is allowed. Goo default enables this for bucklers/medium shields and disables it for tower shields."), BindConfig(section, "RunningWhileBlockingMovementSpeedMultiplier", section.Contains("Tower") ? 1f : 0.7f, "Extra blocking movement-speed multiplier while holding Run and actively blocking with this shield type. Goo default is 0.7x for bucklers/medium shields and unused/1x for tower shields because tower-shield run-blocking is disabled.")); } private StaffBlockConfig BindStaffBlockConfig(string section) { return new StaffBlockConfig(BindConfig(section, "CanParry", defaultValue: true, "If false, this staff can still block normally but cannot perform timed parries. If true and the staff has no native parry bonus, GCO supplies a 1.5x base timed-block bonus so the toggle genuinely enables parrying."), BindConfig(section, "BlockArmorMultiplier", 1f, "Multiplier for this staff's Block Armor. 1 is vanilla/no-op."), BindConfig(section, "BlockForceMultiplier", 1f, "Multiplier for this staff's Block Force. 1 is vanilla/no-op; 0 removes Block Force and hides the tooltip line."), BindConfig(section, "ParryBonusMultiplier", 1f, "Multiplier for this staff's parry bonus. Multiplies vanilla and updates tooltip. 1 is vanilla/no-op."), BindConfig(section, "ParryWindowTimeMultiplier", 1f, "Multiplier for this staff's parry/timed-block input window. Stacks with GlobalParryWindowTimeMultiplier. 1 keeps Valheim's 0.25 second window; 2 doubles it; 0 disables timed parries with this staff."), BindConfig(section, "ParryAdrenalineMultiplier", 1f, "Multiplier for this staff's parry adrenaline. 1 is vanilla/no-op."), BindConfig(section, "BlockStaminaConsumptionRate", 1f, "Multiplier for this staff's final block stamina usage. 1 is vanilla/no-op."), BindConfig(section, "BlockingStaminaRegenMultiplier", 1f, "Stamina regeneration multiplier while actively blocking with this staff. Stacks with GlobalBlockingStaminaRegenMultiplier. 1 is vanilla/no-op."), BindConfig(section, "BlockBreakThresholdMultiplier", 1f, "Multiplier for this staff's general block-break threshold. 1 is vanilla/no-op."), BindConfig(section, "PvpBlockArmorMultiplier", 1f, "PvP-only Block Armor multiplier for this staff. Stacks on BlockArmorMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockForceMultiplier", 1f, "PvP-only Block Force multiplier for this staff. Stacks on BlockForceMultiplier and does not change tooltip."), BindConfig(section, "PvpParryBonusMultiplier", 1f, "PvP-only parry bonus multiplier for this staff. Stacks on ParryBonusMultiplier and does not change tooltip."), BindConfig(section, "PvpBlockBreakThresholdMultiplier", 1f, "PvP-only block-break threshold multiplier for this staff. Stacks on BlockBreakThresholdMultiplier."), BindConfig(section, "SoulsLikeBlockBreak", defaultValue: false, "Souls-like block-break rule. If true, block only breaks/staggers when the blocked attack empties the player's stamina bar."), BindConfig(section, "SoulsLikeBlockStaminaMode", defaultValue: false, "If true, block stamina cost with this staff uses the configured Souls-like formula. This replaces Valheim's vanilla block stamina formula for this staff."), BindConfig(section, "SoulsLikeBlockStaminaBaseRate", 0.7f, "Base rate used by Souls-like block stamina for this staff. Formula: stamina consumed = stagger damage x max(0, 1 - max(0, base rate + effective block armor / 1000 - 0.05)) / block-break threshold. Goo/default is 0.7."), BindConfig(section, "KnockbackTakenWhileBlockingMultiplier", 1f, "Knockback/push-force taken multiplier while this staff successfully blocks. Does not apply when block breaks. 1 is vanilla/no-op."), BindConfig(section, "EnableRunningWhileBlocking", defaultValue: true, "If true, holding Run while actively blocking with this staff is allowed. Valheim's normal run stamina drain is used."), BindConfig(section, "RunningWhileBlockingMovementSpeedMultiplier", 0.7f, "Extra blocking movement-speed multiplier while holding Run and actively blocking with this staff. Goo default is 0.7x; 1 leaves normal blocking speed unchanged."), BindConfig(section, "AttackMovementModifier", 1f, "Attack movement-speed multiplier for staff cast/attack movement. 1 leaves vanilla attack movement unchanged."), BindConfig(section, "AttackRotationFactor", -1f, "Attack rotation factor override for staff casts. -1 leaves vanilla; 0 prevents turning; 2 allows 200% turn speed.")); } private StaffShieldActiveConfig BindStaffShieldActiveConfig(string section) { return new StaffShieldActiveConfig(BindConfig(section, "HealthConsumptionRate", 1f, "Multiplier for health cost deducted when casting StaffShield. 1 is vanilla/no-op."), BindConfig(section, "StaggerMitigationMultiplier", 1f, "Incoming stagger multiplier while StaffShield/SE_Shield is active. 1 is vanilla/no-op; 0 prevents stagger buildup."), BindConfig(section, "PvpStaggerMitigationMultiplier", 1f, "PvP-only incoming stagger multiplier while StaffShield/SE_Shield is active. Stacks on StaggerMitigationMultiplier."), BindConfig(section, "KnockbackMitigationMultiplier", 1f, "Incoming knockback multiplier while StaffShield/SE_Shield is active. 1 is vanilla/no-op; 0 prevents knockback."), BindConfig(section, "PvpKnockbackMitigationMultiplier", 1f, "PvP-only incoming knockback multiplier while StaffShield/SE_Shield is active. Stacks on KnockbackMitigationMultiplier."), BindConfig(section, "ActiveParryBonusMultiplier", 1f, "Parry bonus multiplier while StaffShield/SE_Shield is active. 1 is vanilla/no-op.")); } private void BindGearDurabilityConfigs() { GearDurabilityConfigs.Clear(); (string, string, string)[] weaponPresetSections = WeaponPresetSections; for (int i = 0; i < weaponPresetSections.Length; i++) { string item = weaponPresetSections[i].Item1; string section = WeaponFeatureSection(item, "Resources"); GearDurabilityConfigs[NormalizeCommandKey(item)] = BindGearDurabilityConfig(section, "this weapon category"); RegisterGearDurabilityAlias(item, GearDurabilityConfigs[NormalizeCommandKey(item)]); } RegisterGearDurabilityAlias("Light Armor", BindGearDurabilityConfig("Light Armor - Resources", "light armor")); RegisterGearDurabilityAlias("Medium Armor", BindGearDurabilityConfig("Medium Armor - Resources", "medium armor")); RegisterGearDurabilityAlias("Heavy Armor", BindGearDurabilityConfig("Heavy Armor - Resources", "heavy armor")); RegisterGearDurabilityAlias("Bucklers", BindGearDurabilityConfig("Bucklers - Resources", "bucklers")); RegisterGearDurabilityAlias("Medium Shields", BindGearDurabilityConfig("Medium Shields - Resources", "medium shields")); RegisterGearDurabilityAlias("Tower Shields", BindGearDurabilityConfig("Tower Shields - Resources", "tower shields")); RegisterGearDurabilityAlias("Tools", BindGearDurabilityConfig("Tools - Resources", "tools and build equipment")); RegisterGearDurabilityAlias("Other Gear", BindGearDurabilityConfig("Other Gear - Resources", "other durability-using gear")); } private GearDurabilityConfig BindGearDurabilityConfig(string section, string label) { return new GearDurabilityConfig(BindConfig(section, "Unbreakable", defaultValue: false, "If true, durability loss for " + label + " is restored after use."), BindConfig(section, "DurabilityConsumptionRate", 1f, "Durability consumption rate for " + label + ". 1 is vanilla/no-op, 0 prevents durability loss, 2 doubles durability loss. Stacks with GlobalDurabilityConsumptionMultiplier."), BindConfig(section, "BaseDurabilityMultiplier", 1f, "Base durability multiplier for " + label + ". 1 is vanilla/no-op. Multiplies the item's base durability before quality-level durability is added, and stacks with GlobalBaseDurabilityMultiplier."), BindConfig(section, "DurabilityMultiplier", 1f, "Maximum durability multiplier for " + label + ". 1 is vanilla/no-op, 2 doubles max durability, 0.5 halves max durability. Stacks with GlobalDurabilityMultiplier.")); } private static void RegisterGearDurabilityAlias(string alias, GearDurabilityConfig config) { GearDurabilityConfigs[NormalizeCommandKey(alias)] = config; } private void AddStaffConfigs() { AddDirectStaffConfig("StaffFireball", accuracy: false); AddDirectStaffConfig("StaffIceShards", accuracy: true); AddDirectStaffConfig("StaffClusterbomb", accuracy: false); AddDirectStaffConfig("StaffGreenRoots", accuracy: false); AddDirectStaffConfig("StaffLightning", accuracy: true); AddUtilityStaffConfig("StaffShield", shield: true); AddUtilityStaffConfig("StaffSkeleton", shield: false); AddUtilityStaffConfig("StaffRedTroll", shield: false); StaffGreenRootsAttackPlayersInPvp = BindConfig("Magic - Staff of the Wild", "AttackPlayersInPvp", defaultValue: false, "If true, GCO treats StaffGreenRoots as allowed to affect players when PvP is enabled. This requires the spawned-root prefab path to support player targeting."); StaffSkeletonAttackPlayersInPvp = BindConfig("Magic - Dead Raiser", "AttackPlayersInPvp", defaultValue: false, "If true, GCO treats Dead Raiser / StaffSkeleton as allowed to affect players when PvP is enabled. This requires the spawned-summon prefab path to support player targeting."); StaffLightningReloadSpeedMultiplier = BindConfig("Magic - Dundr", "ReloadSpeedMultiplier", 1f, "Reload speed multiplier for Dundr / StaffLightning. 1 is vanilla/no-op; 2 reloads twice as fast."); StaffLightningRecoilStrengthMultiplier = BindConfig("Magic - Dundr", "RecoilStrengthMultiplier", 0f, "Multiplier for the backward recoil/pushback applied to the firing player by Dundr / StaffLightning. Goo default is 0, which removes recoil; 1 preserves vanilla recoil."); StaffLightningProjectileCountMultiplier = BindConfig("Magic - Dundr", "ProjectileCountMultiplier", 1f, "Projectile count multiplier for Dundr / StaffLightning. 1 is vanilla/no-op."); DundrLightningChainChancePerPellet = BindConfig("Magic - Dundr", "DundrLightningChainChancePerPellet", 0.02f, "Chance per Dundr pellet to apply the Ashlands-style lightning-chain damage effect. This is gameplay behavior and is independent from the global lightning visual toggle. Goo default is 0.02."); StaffGreenRootsVineDamageMultiplier = BindConfig("Magic - Staff of the Wild", "VineDamageMultiplier", 1f, "Best-effort Staff of the Wild vine damage multiplier. 1 is vanilla/no-op."); StaffGreenRootsVineSizeMultiplier = BindConfig("Magic - Staff of the Wild", "VineSizeMultiplier", 1f, "Best-effort Staff of the Wild spawned vine/root size multiplier. 1 is vanilla/no-op."); StaffGreenRootsVineAttackSpeedMultiplier = BindConfig("Magic - Staff of the Wild", "VineAttackSpeedMultiplier", 1f, "Best-effort Staff of the Wild vine/root attack animation or attack interval multiplier. 1 is vanilla/no-op."); StaffGreenRootsVineLevel = BindConfig("Magic - Staff of the Wild", "VineLevel", 0, "Best-effort forced spawned vine/root character level. 0 leaves Valheim's spawned level unchanged; 1+ calls Character.SetLevel on confirmed StaffGreenRoots-spawned characters."); StaffGreenRootsMaxVinesSpawned = BindConfig("Magic - Staff of the Wild", "MaxVinesSpawned", 10, "Maximum active Staff of the Wild vines/roots per caster. GCO disables Valheim's prefab-wide global SpawnAbility cap and enforces this per player without periodic scans. 0 means unlimited. Vanilla/default value is 10."); StaffSkeletonSizeMultiplier = BindConfig("Magic - Dead Raiser", "SkeletonSizeMultiplier", 1f, "Best-effort Dead Raiser summoned skeleton size multiplier. 1 is vanilla/no-op."); StaffSkeletonAttackSpeedMultiplier = BindConfig("Magic - Dead Raiser", "SkeletonAttackSpeedMultiplier", 1f, "Best-effort Dead Raiser summoned skeleton attack animation speed multiplier. 1 is vanilla/no-op."); StaffSkeletonLevel = BindConfig("Magic - Dead Raiser", "SkeletonLevel", 0, "Best-effort forced summoned skeleton character level. 0 leaves Valheim's spawned level unchanged; 1+ calls Character.SetLevel on confirmed StaffSkeleton-spawned characters."); StaffSkeletonHealthConsumptionRate = BindConfig("Magic - Dead Raiser", "HealthConsumptionRate", 1f, "Multiplier for health cost deducted when casting Dead Raiser / StaffSkeleton. 1 is vanilla/no-op."); StaffSkeletonBaseSpawnCapLevel1 = BindConfig("Magic - Dead Raiser", "BaseSpawnCapLevel1", 1, "Dead Raiser / StaffSkeleton native summon cap for item quality 1. Vanilla/default is 1."); StaffSkeletonBaseSpawnCapLevel2 = BindConfig("Magic - Dead Raiser", "BaseSpawnCapLevel2", 2, "Dead Raiser / StaffSkeleton native summon cap for item quality 2. Vanilla/default is 2."); StaffSkeletonBaseSpawnCapLevel3 = BindConfig("Magic - Dead Raiser", "BaseSpawnCapLevel3", 3, "Dead Raiser / StaffSkeleton native summon cap for item quality 3. Vanilla/default is 3."); StaffSkeletonBaseSpawnCapLevel4 = BindConfig("Magic - Dead Raiser", "BaseSpawnCapLevel4", 4, "Dead Raiser / StaffSkeleton native summon cap for item quality 4. Vanilla/default is 4."); } private static string StaffDisplayName(string prefabName) { return prefabName switch { "StaffFireball" => "Staff of Embers", "StaffIceShards" => "Staff of Frost", "StaffClusterbomb" => "Staff of Fracturing", "StaffGreenRoots" => "Staff of the Wild", "StaffLightning" => "Dundr", "StaffShield" => "Staff of Protection", "StaffSkeleton" => "Dead Raiser", "StaffRedTroll" => "Trollstav", _ => prefabName, }; } private static string StaffSection(string prefabName) { return "Magic - " + StaffDisplayName(prefabName); } private void AddDirectStaffConfig(string prefabName, bool accuracy) { string section = StaffSection(prefabName); float num = prefabName switch { "StaffIceShards" => 1f, "StaffClusterbomb" => 1f, "StaffLightning" => 1f, _ => 1f, }; StaffConfigs[prefabName] = new StaffConfig(prefabName, directAttack: true, BindConfig(section, "DamageMultiplier", 1f, "General damage multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "StaggerMultiplier", 1f, "General outgoing stagger multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "AttackAnimationSpeedMultiplier", 1f, "Attack animation-speed multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "EitrRateMultiplier", 1f, "Eitr-cost multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "AttackStartNoiseMultiplier", 1f, "Multiplier for Valheim's AI aggro/noise radius when this staff attack starts. Stacks with GlobalAttackStartNoiseMultiplier. 1 is vanilla/no-op."), BindConfig(section, "AttackHitNoiseMultiplier", 1f, "Multiplier for Valheim's AI aggro/noise radius when this staff attack hits. Stacks with GlobalAttackHitNoiseMultiplier. 1 is vanilla/no-op."), BindConfig(section, "SneakDamageMultiplier", 1f, "Local sneak damage multiplier for this staff. Final tooltip/runtime sneak damage equals Global Sneak Damage Multiplier x this local value. Goo preset default is 1; Reset to Vanilla sets this to 3 while global becomes 1."), BindConfig(section, "PvpDamageMultiplier", num, $"PvP damage multiplier for this staff. Goo default is {num:0.###}."), BindConfig(section, "PvpStaggerMultiplier", 0f, "PvP stagger multiplier for this staff. Goo default is 0."), BindConfig(section, "PvpKnockbackForceMultiplier", 0f, "PvP knockback/push-force multiplier for this staff. Goo default is 0."), BindConfig(section, "ProjectileSpeedMultiplier", 1f, "Projectile-speed multiplier for projectile-based attacks from this staff. It applies only when Valheim's cloned attack is a projectile attack with an IProjectile prefab. 1 is vanilla/no-op."), string.Equals(prefabName, "StaffFireball", StringComparison.OrdinalIgnoreCase) ? BindConfig(section, "AoeRadiusMultiplier", 1f, "Explosion AOE radius multiplier for Staff of Embers fireballs. It scales the fireball projectile's native impact AOE radius and retains spawned Aoe/collider handling as a compatibility fallback. 1 is vanilla/no-op.") : null, accuracy ? BindConfig(section, "SpreadMultiplier", IsStaffIceShardsPrefabName(prefabName) ? 0.5f : 1f, IsStaffIceShardsPrefabName(prefabName) ? "Projectile spread multiplier for this staff. Goo ruleset default for StaffIceShards is 0.5; 1 is vanilla/no-op; lower values reduce spread." : "Projectile spread multiplier for this staff. 1 is vanilla/no-op; lower values reduce spread.") : null, null, null); StaffBlockConfigs[prefabName] = BindStaffBlockConfig(section); StaffDamageTakenOnBlockedHitConfigs[prefabName] = BindBlockedHitDamageTakenConfig(section); StaffEffectiveBlockAngleMultipliers[prefabName] = BindConfig(section, "EffectiveBlockAngleMultiplier", 1f, "Effective block angle multiplier while blocking with this staff. Valheim's default block sector is 180 degrees; 1 is vanilla/no-op."); } private void AddUtilityStaffConfig(string prefabName, bool shield) { string section = StaffSection(prefabName); bool flag = string.Equals(prefabName, "StaffSkeleton", StringComparison.OrdinalIgnoreCase); StaffConfigs[prefabName] = new StaffConfig(prefabName, directAttack: false, flag ? BindConfig(section, "SkeletonDamageMultiplier", 1f, "Outgoing damage multiplier for summoned skeletons created by StaffSkeleton. 1 is vanilla/no-op.") : null, null, BindConfig(section, "AttackAnimationSpeedMultiplier", 1f, "Attack animation-speed multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "EitrRateMultiplier", 1f, "Eitr-cost multiplier for this staff. 1 is vanilla/no-op."), BindConfig(section, "AttackStartNoiseMultiplier", 1f, "Multiplier for Valheim's AI aggro/noise radius when this staff action starts. Stacks with GlobalAttackStartNoiseMultiplier. 1 is vanilla/no-op."), BindConfig(section, "AttackHitNoiseMultiplier", 1f, "Multiplier for Valheim's AI aggro/noise radius when this staff action hits. Stacks with GlobalAttackHitNoiseMultiplier. 1 is vanilla/no-op."), BindConfig(section, "SneakDamageMultiplier", 1f, "Local sneak damage multiplier for this staff. Final tooltip/runtime sneak damage equals Global Sneak Damage Multiplier x this local value. Goo preset default is 1; Reset to Vanilla sets this to 3 while global becomes 1."), flag ? BindConfig(section, "SkeletonPvpDamageMultiplier", 1f, "PvP-only outgoing damage multiplier for summoned skeletons created by StaffSkeleton when their PvP targeting is enabled. 1 is vanilla/no-op.") : null, null, null, null, null, null, shield ? BindConfig(section, "ShieldHealthMultiplier", 1f, "Multiplier for StaffShield/SE_Shield total absorb health. 1 is vanilla/no-op.") : null, shield ? BindConfig(section, "DamageTakenFromPlayersMultiplier", 1f, "Multiplier for damage that player attacks deal to StaffShield/SE_Shield. 1 is vanilla/no-op.") : null); StaffBlockConfigs[prefabName] = BindStaffBlockConfig(section); StaffDamageTakenOnBlockedHitConfigs[prefabName] = BindBlockedHitDamageTakenConfig(section); StaffEffectiveBlockAngleMultipliers[prefabName] = BindConfig(section, "EffectiveBlockAngleMultiplier", 1f, "Effective block angle multiplier while blocking with this staff. Valheim's default block sector is 180 degrees; 1 is vanilla/no-op."); if (shield) { StaffShieldActiveSettings = BindStaffShieldActiveConfig(section); } } private void BindVanillaWeaponPrefabHitRayRangeConfigs(WeaponCategory category, string hitRaySection, bool exposeHitRay) { if (exposeHitRay) { switch (category) { case WeaponCategory.OneHandedSword: BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordBronze", "Bronze Sword", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordIron", "Iron Sword", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordSilver", "Silver Sword", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordBlackmetal", "Blackmetal Sword", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordMistwalker", "Mistwalker", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordNiedhogg", "Nidhogg", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordNiedhoggBlood", "Nidhogg Blood", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordNiedhoggLightning", "Nidhogg Lightning", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordNiedhoggNature", "Nidhogg Nature", 1f); BindPrefabHitRayRangeMultiplier(OneHandedSwordPrefabHitRayRangeMultipliers, hitRaySection, "SwordDyrnwyn", "Dyrnwyn", 1f); break; case WeaponCategory.Spear: BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearFlint", "Flint Spear", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearBronze", "Bronze Spear", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearElderbark", "Ancient Bark Spear", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearCarapace", "Carapace Spear", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearWolfFang", "Fang Spear", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearSplitner", "Splitnir", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearSplitner_Blood", "Splitnir Blood", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearSplitner_Lightning", "Splitnir Lightning", 1f); BindPrefabHitRayRangeMultiplier(SpearPrefabHitRayRangeMultipliers, hitRaySection, "SpearSplitner_Nature", "Splitnir Nature", 1f); break; } } } private void BindPrefabHitRayRangeMultiplier(Dictionary> map, string section, string prefabId, string displayName, float defaultValue) { map[prefabId] = BindConfig(section, prefabId + "PrefabHitRayRangeMultiplier", defaultValue, displayName + " prefab-specific melee hit-ray range multiplier. Applies only when the currently held vanilla item prefab id is " + prefabId + ". This multiplies Attack.m_attackRange after the normal attack-slot range multiplier; 1 leaves this prefab's range unchanged. Safe bind default is 1 so existing/migrated configs do not silently gain extra reach; Apply Goo's Ruleset fills Goo's model-length preset values."); } private void BindWeaponSupplementaryConfigs(WeaponCategory category, string baseSection) { switch (category) { case WeaponCategory.TwoHandedSword: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1f); UseCustomTwoHandedSwordMovementModifier = BindConfig("Two-Handed Swords - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for two-handed swords; if false, use the item's vanilla movement modifier."); TwoHandedSwordMovementModifierPercent = BindConfig("Two-Handed Swords - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for two-handed swords when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.TwoHandedAxe: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1f); UseCustomTwoHandedAxeMovementModifier = BindConfig("Two-Handed Axes - Equipment Movement", "UseCustomMovementModifier", defaultValue: true, "If true, override the final movement modifier for two-handed axes; if false, use the item's vanilla movement modifier."); TwoHandedAxeMovementModifierPercent = BindConfig("Two-Handed Axes - Equipment Movement", "MovementModifierPercent", -8f, "Final movement modifier percent for two-handed axes when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.Sledge: SledgeUseAlternateMoveset = BindConfig("Sledges - Animation", "UseAlternateMoveset", defaultValue: false, "[Experimental] If true, sledges/two-handed clubs use GCO's alternate for-fun moveset: the player holds the weapon with Valheim's two-handed-axe stance, Primary1/Primary2/running attacks use dedicated Sledge - Alt Moveset settings and direct horizontal hit rays; Secondary uses the real two-handed-axe secondary attack with native hit rays and dedicated Sledge - Alt Moveset Secondary settings; Primary3 keeps normal Sledge settings and AOE behavior. Disabled by Goo default; Reset to Vanilla also disables it."); SledgeSlamAoeRadiusMultiplier = BindConfig("Sledges - AOE", "SlamAoeRadiusMultiplier", 1f, "Multiplies the native AOE hit radius for every native Area attack made with a weapon classified as Sledge, including normal sledge attacks, modded sledges, and alternate-moveset Primary3. Direct hit-ray attacks and the alternate two-handed-axe Secondary are unaffected. Visual-effect size is not changed. 1 = vanilla radius."); AddRunningAttackConfig(WeaponCategory.SledgeAlt, "Sledge - Alt Moveset"); BindSledgeAlternateMovesetHitRayConfigs(); UseCustomSledgeMovementModifier = BindConfig("Sledges - Equipment Movement", "UseCustomMovementModifier", defaultValue: true, "If true, override the final movement modifier for sledges/two-handed clubs; if false, use the item's vanilla movement modifier."); SledgeMovementModifierPercent = BindConfig("Sledges - Equipment Movement", "MovementModifierPercent", -8f, "Final movement modifier percent for sledges/two-handed clubs when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.DualAxes: AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 0.45f, 1f); UseCustomDualAxesMovementModifier = BindConfig("Dual Axes - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for dual axes; if false, use the item's vanilla movement modifier."); DualAxesMovementModifierPercent = BindConfig("Dual Axes - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for dual axes when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.Atgeir: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1f); UseCustomAtgeirMovementModifier = BindConfig("Atgeirs - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for atgeirs; if false, use the item's vanilla movement modifier."); AtgeirMovementModifierPercent = BindConfig("Atgeirs - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for atgeirs when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.OneHandedSword: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1f); UseCustomOneHandedSwordMovementModifier = BindConfig("One-Handed Swords - Equipment Movement", "UseCustomMovementModifier", defaultValue: true, "If true, override the final movement modifier for one-handed swords; if false, use the item's vanilla movement modifier."); OneHandedSwordMovementModifierPercent = BindConfig("One-Handed Swords - Equipment Movement", "MovementModifierPercent", -3f, "Final movement modifier percent for one-handed swords when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.OneHandedAxe: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1.7f); UseCustomOneHandedAxeMovementModifier = BindConfig("One-Handed Axes - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for one-handed axes; if false, use the item's vanilla movement modifier."); OneHandedAxeMovementModifierPercent = BindConfig("One-Handed Axes - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for one-handed axes when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.Club: AddRunningAttackConfig(category, baseSection); AddJumpAttackConfig(category, baseSection, enabledByDefault: true, 1f, 1f); UseCustomClubMovementModifier = BindConfig("Clubs and Maces - Equipment Movement", "UseCustomMovementModifier", defaultValue: true, "If true, override the final movement modifier for clubs/maces; if false, use the item's vanilla movement modifier."); ClubMovementModifierPercent = BindConfig("Clubs and Maces - Equipment Movement", "MovementModifierPercent", -3f, "Final movement modifier percent for clubs/maces when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.Knife: AddRunningAttackConfig(category, baseSection); UseCustomKnifeMovementModifier = BindConfig("Knives - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for knives; if false, use the item's vanilla movement modifier."); KnifeMovementModifierPercent = BindConfig("Knives - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for knives when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.DualKnives: AddRunningAttackConfig(category, baseSection); UseCustomDualKnivesMovementModifier = BindConfig("Dual Knives - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for dual knives; if false, use the item's vanilla movement modifier."); DualKnivesMovementModifierPercent = BindConfig("Dual Knives - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for dual knives when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.Spear: AddRunningAttackConfig(category, baseSection); UseCustomSpearMovementModifier = BindConfig("Spears - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for spears; if false, use the item's vanilla movement modifier."); SpearMovementModifierPercent = BindConfig("Spears - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for spears when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); SpearProjectileSpeedMultiplier = BindConfig("Spears - Ranged", "ProjectileSpeedMultiplier", 1.5f, "Projectile-speed multiplier for thrown spear secondary attacks and Abyssal Harpoon projectiles. Goo ruleset default is 1.5; 1 is vanilla/no-op."); SpearLoyaltyMode = BindConfig("Spears - Ranged", "SpearLoyaltyMode", defaultValue: false, "If true, thrown spears are not consumed from the player's inventory and their projectiles do not create dropped spear items on hit. Stamina, durability, damage, and every other throw cost/effect remain unchanged. Goo preset default is false."); HarpoonRopeStrengthMultiplier = BindConfig("Spears - Ranged", "HarpoonRopeStrengthMultiplier", 1f, "Multiplier for Abyssal Harpoon attach/rope pull strength. 1 is vanilla/no-op; 2 pulls about twice as strongly; lower values soften the rope pull."); break; case WeaponCategory.Bow: UseCustomBowMovementModifier = BindConfig("Bows - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for bows; if false, use the item's vanilla movement modifier."); BowMovementModifierPercent = BindConfig("Bows - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for bows when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); BowDrawSpeedMultiplier = BindConfig("Bows - Ranged", "BowDrawSpeedMultiplier", 1f, "Multiplier for bow draw percentage gain. 1 is vanilla; 2 reaches full draw twice as fast."); BowDrawRotationFactor = BindConfig("Bows - Ranged", "BowDrawRotationFactor", 999f, "Multiplier for the player's maximum body-turn speed while actively drawing/aiming a bow. 1 is vanilla; 0 prevents body rotation; 999 makes bow aiming rotation effectively uncapped. Applies only during bow draw and does not affect crossbows or magic."); BowSpreadMultiplier = BindConfig("Bows - Ranged", "BowSpreadMultiplier", 1f, "Multiplier for bow projectile spread/accuracy angles on the cloned Attack instance. 1 is vanilla; 0.5 halves spread."); BowProjectileSpeedMultiplier = BindConfig("Bows - Ranged", "ProjectileSpeedMultiplier", 1f, "Multiplier for bow projectile velocity on the cloned Attack instance. 1 is vanilla/no-op."); BowBaseSpreadMultiplier = BindConfig("Bows - Ranged", "BaseSpreadMultiplier", 1f, "Multiplier for bow base projectile spread fields on the cloned Attack instance. 1 is vanilla/no-op; lower values reduce spread."); break; case WeaponCategory.Crossbow: UseCustomCrossbowMovementModifier = BindConfig("Crossbows - Equipment Movement", "UseCustomMovementModifier", defaultValue: false, "If true, override the final movement modifier for crossbows; if false, use the item's vanilla movement modifier."); CrossbowMovementModifierPercent = BindConfig("Crossbows - Equipment Movement", "MovementModifierPercent", 0f, "Final movement modifier percent for crossbows when UseCustomMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); CrossbowReloadTimeMultiplier = BindConfig("Crossbows - Ranged", "CrossbowReloadTimeMultiplier", 1f, "Multiplier for crossbow reload/loading time after vanilla skill scaling. 1 is vanilla."); CrossbowProjectileSpeedMultiplier = BindConfig("Crossbows - Ranged", "ProjectileSpeedMultiplier", 1f, "Multiplier for crossbow projectile velocity on the cloned Attack instance. 1 is vanilla/no-op."); CrossbowBaseSpreadMultiplier = BindConfig("Crossbows - Ranged", "BaseSpreadMultiplier", 1f, "Multiplier for crossbow base projectile spread fields on the cloned Attack instance. 1 is vanilla/no-op; lower values reduce spread."); CrossbowRecoilStrengthMultiplier = BindConfig("Crossbows - Ranged", "RecoilStrengthMultiplier", 1f, "Multiplier for the backward recoil/pushback applied to the firing player by crossbow attacks. Goo default is 1, which preserves vanilla recoil; 0 removes recoil."); break; case WeaponCategory.Unarmed: AddRunningAttackConfig(category, baseSection); break; case WeaponCategory.Other: AddRunningAttackConfig(category, baseSection); UseCustomToolMovementModifier = BindConfig("Other - Equipment Movement", "UseCustomToolMovementModifier", defaultValue: false, "If true, override the final movement modifier for tools such as hammers and pickaxes; if false, use the item's vanilla movement modifier."); ToolMovementModifierPercent = BindConfig("Other - Equipment Movement", "ToolMovementModifierPercent", 0f, "Final movement modifier percent for tools such as hammers and pickaxes when UseCustomToolMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); UseCustomOtherEquipmentMovementModifier = BindConfig("Other - Equipment Movement", "UseCustomOtherEquipmentMovementModifier", defaultValue: false, "If true, override the final movement modifier for unclassified equipment; if false, use the item's vanilla movement modifier."); OtherEquipmentMovementModifierPercent = BindConfig("Other - Equipment Movement", "OtherEquipmentMovementModifierPercent", 0f, "Final movement modifier percent for unclassified equipment when UseCustomOtherEquipmentMovementModifier is true. -10 means 10% slower; +5 means 5% faster; 0 hides the movement tooltip line."); break; case WeaponCategory.SledgeAlt: case WeaponCategory.Pickaxe: case WeaponCategory.Magic: break; } } private void AddRunningAttackConfig(WeaponCategory category, string baseSection) { WeaponCategory weaponCategory = ((category == WeaponCategory.SledgeAlt) ? WeaponCategory.TwoHandedAxe : category); string section = WeaponFeatureSection(baseSection, "Running Attack"); float defaultValue = GooRunningAttackMinimumVelocity(weaponCategory); float defaultValue2 = weaponCategory switch { WeaponCategory.TwoHandedAxe => 0f, WeaponCategory.Sledge => 0f, WeaponCategory.OneHandedSword => 2f, WeaponCategory.Club => 2f, _ => 2.5f, }; float defaultValue3 = DefaultRunningAttackDamageMultiplier(weaponCategory); string description = ((weaponCategory == WeaponCategory.TwoHandedAxe) ? "Authored base movement factor used only by this running attack. Valheim multiplies character movement by the live Attack.m_speedFactor; GCO writes this value as that base factor, then stacks GlobalAttackMovementMultiplier. 0 disables normal input movement; 1 allows full normal input movement." : "Attack movement-speed multiplier used only during running attacks for live Attack.m_speedFactor / Humanoid.GetAttackSpeedFactorMovement. 0 disables normal input movement during the running attack; 1 leaves the chosen moveset's attack movement unchanged; Goo-default atgeir running attacks use 3.0x."); RegisterRunningAttackBuildingDamageMultiplier(category, baseSection); RegisterRunningAttackStaggeredEnemyDamageMultiplier(category, baseSection); RunningAttackConfigs[category] = new RunningAttackConfig(BindConfig(section, "EnableRunningAttack", weaponCategory != WeaponCategory.Knife && weaponCategory != WeaponCategory.DualKnives, "If true, a primary melee attack started while the player is actually running attempts to use this weapon category's configured running-attack chain animation. The moveset is controlled by RunningAttackUsed. Sledge running attacks are also gated by the experimental UseAlternateMoveset toggle."), BindConfig(section, "RunningAttackMinimumVelocity", defaultValue, "Minimum horizontal player velocity required for this weapon category to trigger a running attack. Goo default is 3.0 for supported running attacks."), BindConfig(section, "RunningAttackUsed", DefaultRunningAttackUsed(weaponCategory), "Which moveset this weapon category uses when a running attack triggers. Primary1/Primary2/Primary3 select a primary chain animation; Secondary uses the weapon's secondary attack data. If the chosen attack does not exist on the weapon, the running attack does not trigger."), BindConfig(section, "RunningAttackDamageMultiplier", defaultValue3, "Damage multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's damage config. 1 leaves running attack damage unchanged."), BindConfig(section, "RunningAttackStaggerMultiplier", 1f, "Outgoing stagger multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's stagger config. 1 leaves running attack stagger unchanged."), BindConfig(section, "RunningAttackStaminaRateMultiplier", DefaultRunningAttackStaminaRateMultiplier(weaponCategory), "Attack stamina-cost multiplier used only by this weapon category's running attack. This is keyed to the real weapon category, not the borrowed/selected moveset. 1 leaves running attack stamina cost unchanged."), BindConfig(section, "RunningAttackAnimationSpeedMultiplier", (weaponCategory == WeaponCategory.OneHandedAxe) ? 1.4f : 1f, "Swing animation-speed multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's animation-speed config. 1 leaves running attack animation speed unchanged."), BindConfig(section, "RunningAttackRecoveryAnimationSpeedMultiplier", (weaponCategory == WeaponCategory.OneHandedAxe) ? 0.5f : 1f, "Recovery animation-speed multiplier used only by this weapon category's running attack after the hitbox event. This does not inherit the selected vanilla attack slot's recovery-speed config. 1 leaves recovery timing unchanged."), BindConfig(section, "RunningAttackBlockCancelMode", BlockCancelMode.Disabled, "Block-cancel mode used only by this weapon category's running attack. Disabled prevents block canceling. BeforeHitbox allows startup cancel and suppresses the hitbox. AfterHitbox allows recovery cancel after the hitbox. FullAnimation allows either."), BindConfig(section, "RunningAttackBlockCancelAnimationSpeedMultiplier", 2f, "Animation-speed multiplier applied when block cancel starts for this running attack. 2 means the remaining attack animation plays at 2x speed. 1 leaves animation speed unchanged."), BindConfig(section, "RunningAttackHyperArmorMode", DefaultRunningAttackHyperArmorMode(weaponCategory), "Hyperarmor mode used only by this weapon category's running attack. Goo default mirrors the selected running moveset's hyperarmor."), BindConfig(section, "RunningAttackHyperArmorDamageTakenMultiplier", DefaultRunningAttackHyperArmorDamageTakenMultiplier(weaponCategory), "Damage taken multiplier while this running attack's hyperarmor is active. Goo default mirrors the selected running moveset's hyperarmor protection."), BindConfig(section, "RunningAttackHyperArmorStaggerTakenMultiplier", DefaultRunningAttackHyperArmorStaggerTakenMultiplier(weaponCategory), "Stagger taken multiplier while this running attack's hyperarmor is active. Goo default mirrors the selected running moveset's hyperarmor protection."), BindConfig(section, "RunningAttackHyperArmorKnockbackTakenMultiplier", DefaultRunningAttackHyperArmorKnockbackTakenMultiplier(weaponCategory), "Knockback taken multiplier while this running attack's hyperarmor is active. Goo default mirrors the selected running moveset's hyperarmor protection."), BindConfig(section, "RunningAttackPvpHyperArmorDamageTakenMultiplier", 1f, "PvP-only extra damage-taken multiplier while this running attack's hyperarmor is active and the incoming attacker is a player. Stacks on RunningAttackHyperArmorDamageTakenMultiplier. 1 leaves current PvP hyperarmor damage behavior unchanged."), BindConfig(section, "RunningAttackPvpHyperArmorStaggerTakenMultiplier", 1f, "PvP-only incoming stagger multiplier while this running attack's hyperarmor is active and the incoming attacker is a player. Stacks on RunningAttackHyperArmorStaggerTakenMultiplier. 1 leaves current PvP hyperarmor stagger behavior unchanged."), BindConfig(section, "RunningAttackPvpHyperArmorKnockbackTakenMultiplier", 1f, "PvP-only incoming push/knockback multiplier while this running attack's hyperarmor is active and the incoming attacker is a player. Stacks on RunningAttackHyperArmorKnockbackTakenMultiplier. 1 leaves current PvP hyperarmor knockback behavior unchanged."), BindConfig(section, "RunningAttackCounterDamageEnabled", DefaultRunningAttackCounterDamageEnabled(weaponCategory), "If true, this running attack can deal counter damage. Goo default mirrors the selected running moveset's counter-damage setting."), BindConfig(section, "RunningAttackCounterDamageMultiplier", DefaultRunningAttackCounterDamageMultiplier(weaponCategory), "Counter-damage multiplier for this running attack. Goo default mirrors the selected running moveset's counter-damage multiplier."), BindConfig(section, "RunningAttackPvpCounterDamageMultiplier", 1f, "PvP-only extra counter-damage multiplier when this running attack counter-hits a player or PvP test dummy. Stacks on RunningAttackCounterDamageMultiplier and GlobalCounterDamageMultiplier. 1 leaves current PvP counter-damage behavior unchanged."), BindConfig(section, "RunningAttackHitStopDurationMultiplier", DefaultRunningAttackHitStopDurationMultiplier(category), "Hit-stop duration multiplier for this running attack. Goo default follows this weapon type's hit-stop duration multiplier."), BindConfig(section, "RunningAttackPvpHitStopDurationMultiplier", DefaultRunningAttackPvpHitStopDurationMultiplier(weaponCategory), "PvP-only hit-stop duration multiplier for this running attack. This multiplies vanilla hit-stop directly and does not stack with PvE hit-stop duration multipliers."), BindConfig(section, "RunningAttackChopDamageMultiplier", 0.5f, "Chop damage multiplier used only by this running attack. Goo default is 0.5 so running attacks do reduced chop damage."), BindConfig(section, "RunningAttackPickaxeDamageMultiplier", 0f, "Pickaxe damage multiplier used only by this running attack. Goo default is 0 so running attacks do no pickaxe damage."), BindConfig(section, "RunningAttackSneakDamageMultiplier", DefaultGooBackstabDamageMultiplier(weaponCategory), "Local sneak damage multiplier used only by this running attack. Final runtime sneak damage equals Global Sneak Damage Multiplier x this local value."), BindConfig(section, "RunningAttackMovementModifier", (weaponCategory == WeaponCategory.Atgeir) ? 3f : 1f, description), BindConfig(section, "RunningAttackMovementApplicationMode", AttackMovementApplicationMode.BeforeHitbox, "Controls when RunningAttackMovementModifier applies. BeforeHitbox uses this multiplier from attack start until the hitbox event, then forces attack movement to 0 through recovery; FullAnimation keeps this multiplier through recovery until Attack.Stop/cleanup."), BindConfig(section, "RunningAttackRotationFactor", 2f, "Attack rotation factor override used only during running attacks. -1 leaves the normal attack-slot rotation factor unchanged; 0 prevents turning; 2 allows 200% turn speed during the windup/active attack until rotation is locked."), BindConfig(section, "RunningAttackLockRotationAfterAttackTrigger", defaultValue: true, "If true, running attacks lock rotation to 0 after Valheim's hitbox event until recovery ends. Goo default is true."), BindConfig(section, "RunningAttackPvpDamageMultiplier", 1f, "PvP damage multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's PvP damage config. 1 leaves running attack PvP damage unchanged."), BindConfig(section, "RunningAttackPvpStaggerMultiplier", 1f, "PvP stagger multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's PvP stagger config. 1 leaves running attack PvP stagger unchanged."), BindConfig(section, "RunningAttackPvpStaggerDurationSeconds", DefaultPvpStaggerDuration(weaponCategory, RoleFromRunningAttackUsed(DefaultRunningAttackUsed(weaponCategory))), "PvP-only stagger animation duration when this running attack actually breaks a player/PvP-test-dummy stagger bar. 0 disables PvP stagger damage for this running attack."), BindConfig(section, "RunningAttackPvpKnockbackForceMultiplier", 1f, "PvP-only knockback/push-force multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's PvP knockback config. 1 leaves running attack PvP knockback unchanged."), BindConfig(section, "RunningAttackKnockbackForceMultiplier", 1f, "Outgoing knockback/push-force multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's knockback config. 1 leaves running attack knockback unchanged."), BindConfig(section, "RunningAttackAdrenalineMultiplier", 1f, "Adrenaline multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's adrenaline config. 1 leaves running attack adrenaline unchanged."), BindConfig(section, "RunningAttackLungeDistanceMultiplier", defaultValue2, "Root-motion/lunge-distance multiplier used only during running attacks. This scales only positive forward animation-authored root motion while the attack is marked as running; backward and sideways root motion are preserved. 1 leaves vanilla lunge distance unchanged."), BindConfig(section, "RunningAttackLungeApplicationMode", LungeApplicationMode.BeforeHitbox, "Controls when the running-attack lunge multiplier applies. BeforeHitbox stops scaling root motion at Valheim's hitbox event; FullAnimation keeps scaling for the full attack animation."), BindConfig(section, "Running Attack Start Noise Multiplier", 1f, "AI aggro/noise radius multiplier for running-attack start noise. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hit Noise Multiplier", 1f, "AI aggro/noise radius multiplier for running-attack hit noise. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hit Ray Range", 1f, "Melee hit-ray range multiplier used only during running attacks. Scales Attack.m_attackRange. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hit Ray Thickness", (weaponCategory == WeaponCategory.Atgeir) ? 2f : 1f, "Melee hit-ray thickness multiplier used only during running attacks. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra. 1 is vanilla/no-op; Goo-default atgeir running attacks use 2.0x."), BindConfig(section, "Running Attack Hit Ray Start Height", (weaponCategory == WeaponCategory.OneHandedAxe) ? 1.5f : 1f, "Melee hit-ray start-height multiplier used only during running attacks. Scales Attack.m_attackHeight. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hit Ray Offset", 1f, "Melee hit-ray sideways offset multiplier used only during running attacks. Scales Attack.m_attackOffset. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hitbox Type", AttackHitboxType.Horizontal, "Hitbox fan direction used only during running attacks. Horizontal uses Valheim's yaw fan; Vertical uses Valheim's pitch fan. Vanilla/no-op values restore the mod's best-known vanilla direction for this attack."), BindConfig(section, "Running Attack Hit Ray Angle", 1f, "Multiplier for running-attack hit-ray fan angle, Attack.m_attackAngle. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Multi Target Damage Penalty", DefaultGooRunningAttackMultiTargetDamagePenaltyMode(weaponCategory), "Controls Attack.m_lowerDamagePerHit for this running attack. Enabled allows Valheim's multi-target damage falloff and forces Hit Through Mobs off; Disabled prevents damage loss across multiple targets.")); RunningAttackMultiHitEnabled[category] = BindConfig(section, "Running Attack Multi Hit", DefaultGooRunningAttackMultiHitEnabled(weaponCategory), "If true, this running attack may damage multiple valid targets collected by the melee hit ray. Multi Hit controls how many collected targets receive damage; it does not control whether an individual ray continues through a target."); RunningAttackHitThroughTargetsEnabled[category] = BindConfig(section, "Running Attack Hit Through Mobs", DefaultGooRunningAttackHitThroughMobsEnabled(weaponCategory), "If true, each individual running-attack hit ray continues through characters, creatures, and players instead of stopping at the first valid character target. This is always forced off while Multi Target Damage Penalty is Enabled."); RunningAttackHitThroughWallsEnabled[category] = BindConfig(section, "Running Attack Hit Through Walls", DefaultGooRunningAttackHitThroughWallsEnabled(weaponCategory), "If true, each individual running-attack hit ray continues through non-character obstructions such as terrain, buildings, rocks, trees, and other destructibles. This is independent from Hit Through Mobs."); } private void AddJumpAttackConfig(WeaponCategory category, string section, bool enabledByDefault, float damageDefault, float animationSpeedDefault, float pvpDamageDefault = 1f) { section = WeaponFeatureSection(section, "Jump Attack"); JumpAttackUsedSetting usedAttack = BindJumpAttackUsedConfig(category, section); RegisterJumpAttackBuildingDamageMultiplier(category, section); RegisterJumpAttackStaggeredEnemyDamageMultiplier(category, section); if (category == WeaponCategory.OneHandedAxe) { OneHandedAxeJumpAttackHitRayAddedThicknessMeters = BindConfig(section, "JumpAttackHitRayAddedThicknessMeters", 0.4f, "Additive melee hit-ray thickness in meters for the one-handed axe jump attack only. This is added after normal jump-attack thickness multipliers because the source 1H axe secondary-style ray has no meaningful vanilla ray thickness. Goo default is 0.4m; Reset to Vanilla sets this to 0."); } JumpAttackConfigs[category] = new JumpAttackConfig(BindConfig(section, "EnableJumpAttack", enabledByDefault, "If true, a primary melee attack initiated while airborne and not swimming uses this weapon category's configured jump-attack moveset. This is independent of running attacks."), usedAttack, BindConfig(section, "JumpAttackDamageMultiplier", damageDefault, "Damage multiplier used only by this weapon category's jump attack. This overrides normal attack-slot damage multipliers for the special jump attack; 1 leaves that moveset damage unchanged before BaseDamageMultiplier, airborne damage, and PvP scaling."), BindConfig(section, "JumpAttackStaggerMultiplier", 1f, "Outgoing stagger multiplier used only by this weapon category's jump attack. This overrides normal attack-slot stagger calibration before airborne stagger is applied; 1 leaves the jump moveset stagger unchanged."), BindConfig(section, "JumpAttackStaminaRateMultiplier", DefaultJumpAttackStaminaRateMultiplier(category), "Attack stamina-cost multiplier used only by this weapon category's jump attack. This is keyed to the real weapon category, not the borrowed/selected moveset. 1 leaves vanilla stamina cost unchanged."), BindConfig(section, "JumpAttackAnimationSpeedMultiplier", animationSpeedDefault, "Swing animation-speed multiplier used only by this weapon category's jump attack. This overrides normal attack-slot animation speed for the special moveset."), BindConfig(section, "JumpAttackRecoveryAnimationSpeedMultiplier", (category == WeaponCategory.OneHandedAxe) ? 0.8f : 1f, "Recovery animation-speed multiplier used only by this weapon category's jump attack after the hitbox event. This does not inherit the selected vanilla attack slot's recovery-speed config."), BindConfig(section, "JumpAttackBlockCancelMode", BlockCancelMode.Disabled, "Block-cancel mode used only by this weapon category's jump attack. Disabled prevents block canceling. BeforeHitbox allows startup cancel and suppresses the hitbox. AfterHitbox allows recovery cancel after the hitbox. FullAnimation allows either."), BindConfig(section, "JumpAttackBlockCancelAnimationSpeedMultiplier", 2f, "Animation-speed multiplier applied when block cancel starts for this jump attack. 2 means the remaining attack animation plays at 2x speed. 1 leaves animation speed unchanged."), BindConfig(section, "JumpAttackHyperArmorMode", HyperArmorMode.Off, "Hyperarmor mode used only by this weapon category's jump attack. Goo default is Off for every jump attack."), BindConfig(section, "JumpAttackHyperArmorDamageTakenMultiplier", 1f, "Damage taken multiplier while this jump attack's hyperarmor is active. Goo default is 1 because jump attacks do not have hyperarmor by default."), BindConfig(section, "JumpAttackHyperArmorStaggerTakenMultiplier", 1f, "Stagger taken multiplier while this jump attack's hyperarmor is active. Goo default is 1 because jump attacks do not have hyperarmor by default."), BindConfig(section, "JumpAttackHyperArmorKnockbackTakenMultiplier", 1f, "Knockback taken multiplier while this jump attack's hyperarmor is active. Goo default is 1 because jump attacks do not have hyperarmor by default."), BindConfig(section, "JumpAttackPvpHyperArmorDamageTakenMultiplier", 1f, "PvP-only extra damage-taken multiplier while this jump attack's hyperarmor is active and the incoming attacker is a player. Stacks on JumpAttackHyperArmorDamageTakenMultiplier. 1 leaves current PvP hyperarmor damage behavior unchanged."), BindConfig(section, "JumpAttackPvpHyperArmorStaggerTakenMultiplier", 1f, "PvP-only incoming stagger multiplier while this jump attack's hyperarmor is active and the incoming attacker is a player. Stacks on JumpAttackHyperArmorStaggerTakenMultiplier. 1 leaves current PvP hyperarmor stagger behavior unchanged."), BindConfig(section, "JumpAttackPvpHyperArmorKnockbackTakenMultiplier", 1f, "PvP-only incoming push/knockback multiplier while this jump attack's hyperarmor is active and the incoming attacker is a player. Stacks on JumpAttackHyperArmorKnockbackTakenMultiplier. 1 leaves current PvP hyperarmor knockback behavior unchanged."), BindConfig(section, "JumpAttackCounterDamageEnabled", DefaultJumpAttackCounterDamageEnabled(category), "If true, this jump attack can deal counter damage. Goo default mirrors the selected jump moveset's counter-damage setting, so spear Primary1 jump attacks keep counter damage."), BindConfig(section, "JumpAttackCounterDamageMultiplier", DefaultJumpAttackCounterDamageMultiplier(category), "Counter-damage multiplier for this jump attack. Goo default mirrors the selected jump moveset's counter-damage multiplier."), BindConfig(section, "JumpAttackPvpCounterDamageMultiplier", 1f, "PvP-only extra counter-damage multiplier when this jump attack counter-hits a player or PvP test dummy. Stacks on JumpAttackCounterDamageMultiplier and GlobalCounterDamageMultiplier. 1 leaves current PvP counter-damage behavior unchanged."), BindConfig(section, "JumpAttackHitStopDurationMultiplier", DefaultJumpAttackHitStopDurationMultiplier(category), "Hit-stop duration multiplier for this jump attack. Goo default follows this weapon type's hit-stop multiplier, except 2H Axe jump attack defaults to 5x."), BindConfig(section, "JumpAttackPvpHitStopDurationMultiplier", DefaultJumpAttackPvpHitStopDurationMultiplier(category), "PvP-only hit-stop duration multiplier for this jump attack. This multiplies vanilla hit-stop directly and does not stack with PvE hit-stop duration multipliers; 2H Axe jump attack defaults to 5x."), BindConfig(section, "JumpAttackChopDamageMultiplier", 0.5f, "Chop damage multiplier used only by this jump attack. Goo default is 0.5 so jump attacks do reduced chop damage."), BindConfig(section, "JumpAttackPickaxeDamageMultiplier", 0f, "Pickaxe damage multiplier used only by this jump attack. Goo default is 0 so jump attacks do no pickaxe damage."), BindConfig(section, "JumpAttackSneakDamageMultiplier", DefaultGooBackstabDamageMultiplier(category), "Local sneak damage multiplier used only by this jump attack. Final runtime sneak damage equals Global Sneak Damage Multiplier x this local value."), BindConfig(section, "JumpAttackMovementModifier", 0.5f, "Attack movement-speed factor used only during jump attacks for live Attack.m_speedFactor / Humanoid.GetAttackSpeedFactorMovement. This does not inherit the selected vanilla attack slot's attack movement config. 0 disables normal input movement; 1 allows full normal movement during the jump attack."), BindConfig(section, "JumpAttackMovementApplicationMode", AttackMovementApplicationMode.FullAnimation, "Controls when JumpAttackMovementModifier applies. BeforeHitbox uses this movement factor until the hitbox event, then forces attack movement to 0 through recovery; FullAnimation keeps this factor through recovery until Attack.Stop/cleanup."), BindConfig(section, "JumpAttackRotationFactor", 2f, "Attack rotation factor used only by this weapon category's jump attack. 0 prevents turning; 2 allows 200% turn speed during the attack animation until rotation is locked."), BindConfig(section, "JumpAttackLockRotationAfterAttackTrigger", defaultValue: true, "If true, this jump attack locks rotation to 0 after Valheim's hitbox event until recovery ends. Goo default is true."), BindConfig(section, "JumpAttackPvpDamageMultiplier", pvpDamageDefault, "PvP damage multiplier used only by this weapon category's jump attack when the victim is a player/PvP-test-dummy. 1 leaves PvP damage unchanged."), BindConfig(section, "JumpAttackPvpStaggerMultiplier", 1f, "PvP stagger multiplier used only by this weapon category's jump attack. 1 leaves vanilla PvP stagger amount unchanged."), BindConfig(section, "JumpAttackPvpStaggerDurationSeconds", DefaultPvpStaggerDuration(category, DefaultJumpAttackRole(category)), "PvP-only stagger animation duration when this jump attack actually breaks a player/PvP-test-dummy stagger bar. 0 disables PvP stagger damage for this jump attack."), BindConfig(section, "JumpAttackPvpKnockbackForceMultiplier", 1f, "PvP-only knockback/push-force multiplier used only by this weapon category's jump attack. 1 leaves vanilla PvP knockback unchanged."), BindConfig(section, "JumpAttackKnockbackForceMultiplier", 1f, "Outgoing knockback/push-force multiplier used only by this weapon category's jump attack. This overrides normal attack-slot knockback; 1 leaves vanilla knockback unchanged."), BindConfig(section, "JumpAttackAdrenalineMultiplier", 1f, "Adrenaline multiplier used only by this weapon category's jump attack. This overrides normal attack-slot adrenaline; 1 leaves vanilla adrenaline unchanged."), BindConfig(section, "JumpAttackLungeDistanceMultiplier", 0f, "Root-motion/lunge-distance multiplier used only by this weapon category's jump attack. It scales only positive forward animation-authored root motion; backward and sideways root motion are preserved. Goo default is 0 so jump attacks do not lunge forward."), BindConfig(section, "JumpAttackLungeApplicationMode", LungeApplicationMode.BeforeHitbox, "Controls when the jump-attack lunge multiplier applies. BeforeHitbox stops scaling root motion at Valheim's hitbox event; FullAnimation keeps scaling for the full attack animation."), BindConfig(section, "Jump Attack Start Noise Multiplier", 1f, "AI aggro/noise radius multiplier for jump-attack start noise. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Hit Noise Multiplier", 1f, "AI aggro/noise radius multiplier for jump-attack hit noise. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Hit Ray Range", 1f, "Melee hit-ray range multiplier used only during jump attacks. Scales Attack.m_attackRange. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Hit Ray Thickness", (category == WeaponCategory.Atgeir) ? 2f : 1f, "Melee hit-ray thickness multiplier used only during jump attacks. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra. 1 is vanilla/no-op; Goo-default atgeir jump attacks use 2.0x."), BindConfig(section, "Jump Attack Hit Ray Start Height", 1f, "Melee hit-ray start-height multiplier used only during jump attacks. Scales Attack.m_attackHeight. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Hit Ray Offset", 1f, "Melee hit-ray sideways offset multiplier used only during jump attacks. Scales Attack.m_attackOffset. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Hitbox Type", AttackHitboxType.Horizontal, "Hitbox fan direction used only during jump attacks. Horizontal uses Valheim's yaw fan; Vertical uses Valheim's pitch fan. Vanilla/no-op values restore the mod's best-known vanilla direction for this attack."), BindConfig(section, "Jump Attack Hit Ray Angle", 1f, "Multiplier for jump-attack hit-ray fan angle, Attack.m_attackAngle. 1 is vanilla/no-op."), BindConfig(section, "Jump Attack Multi Target Damage Penalty", DefaultGooJumpAttackMultiTargetDamagePenaltyMode(category), "Controls Attack.m_lowerDamagePerHit for this jump attack. Enabled allows Valheim's multi-target damage falloff and forces Hit Through Mobs off; Disabled prevents damage loss across multiple targets.")); JumpAttackMultiHitEnabled[category] = BindConfig(section, "Jump Attack Multi Hit", DefaultGooJumpAttackMultiHitEnabled(category), "If true, this jump attack may damage multiple valid targets collected by the melee hit ray. Multi Hit controls how many collected targets receive damage; it does not control whether an individual ray continues through a target."); JumpAttackHitThroughTargetsEnabled[category] = BindConfig(section, "Jump Attack Hit Through Mobs", DefaultGooJumpAttackHitThroughMobsEnabled(category), "If true, each individual jump-attack hit ray continues through characters, creatures, and players instead of stopping at the first valid character target. This is always forced off while Multi Target Damage Penalty is Enabled."); JumpAttackHitThroughWallsEnabled[category] = BindConfig(section, "Jump Attack Hit Through Walls", DefaultGooJumpAttackHitThroughWallsEnabled(category), "If true, each individual jump-attack hit ray continues through non-character obstructions such as terrain, buildings, rocks, trees, and other destructibles. This is independent from Hit Through Mobs."); } private ConfigEntry RegisterRunningAttackBuildingDamageMultiplier(WeaponCategory category, string baseSection) { string section = WeaponFeatureSection(baseSection, "Destruction"); ConfigEntry val = BindConfig(section, "RunningAttackDamageToBuildingsMultiplier", 1f, "Multiplier for damage dealt by this weapon category's modded running attack to WearNTear buildings/pieces. Stacks after player/global building damage and normal outgoing damage tuning. 1 is vanilla/no-op."); RunningAttackBuildingDamageMultipliers[category] = val; return val; } private ConfigEntry RegisterJumpAttackBuildingDamageMultiplier(WeaponCategory category, string jumpSection) { string section = WeaponFeatureSection(jumpSection.EndsWith(" - Jump Attack", StringComparison.Ordinal) ? jumpSection.Substring(0, jumpSection.Length - " - Jump Attack".Length) : jumpSection, "Destruction"); ConfigEntry val = BindConfig(section, "JumpAttackDamageToBuildingsMultiplier", 1f, "Multiplier for damage dealt by this weapon category's modded jump attack to WearNTear buildings/pieces. Stacks after player/global building damage and normal outgoing damage tuning. 1 is vanilla/no-op."); JumpAttackBuildingDamageMultipliers[category] = val; return val; } private ConfigEntry RegisterRunningAttackStaggeredEnemyDamageMultiplier(WeaponCategory category, string baseSection) { string section = WeaponFeatureSection(baseSection, "Stagger"); ConfigEntry val = BindConfig(section, "RunningAttackDamageToStaggeredEnemyMultiplier", 1f, "Local final damage multiplier for this weapon category's running attack against staggered PvE enemies. Stacks with GlobalDamageToStaggeredEnemiesMultiplier as global x local and replaces Valheim's normal staggered-enemy execution-damage result. 1 leaves the global value unchanged."); RunningAttackStaggeredEnemyDamageMultipliers[category] = val; return val; } private ConfigEntry RegisterJumpAttackStaggeredEnemyDamageMultiplier(WeaponCategory category, string jumpSection) { string section = WeaponFeatureSection(jumpSection.EndsWith(" - Jump Attack", StringComparison.Ordinal) ? jumpSection.Substring(0, jumpSection.Length - " - Jump Attack".Length) : jumpSection, "Stagger"); ConfigEntry val = BindConfig(section, "JumpAttackDamageToStaggeredEnemyMultiplier", 1f, "Local final damage multiplier for this weapon category's jump attack against staggered PvE enemies. Stacks with GlobalDamageToStaggeredEnemiesMultiplier as global x local and replaces Valheim's normal staggered-enemy execution-damage result. 1 leaves the global value unchanged."); JumpAttackStaggeredEnemyDamageMultipliers[category] = val; return val; } private void AddCategory(WeaponCategory category, string section, float primaryStagger, float secondaryStagger, HyperArmorMode primaryHyper, HyperArmorMode secondaryHyper, float primaryFullSeconds, float secondaryFullSeconds, bool primaryCounter = false, bool secondaryCounter = false, StaggerApplicationMode primaryStaggerMode = StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode secondaryStaggerMode = StaggerApplicationMode.MultiplyVanilla, float primaryDamageTakenMultiplierDuringHyperArmor = 1f, float secondaryDamageTakenMultiplierDuringHyperArmor = 1f, float primaryStaggerTakenMultiplierDuringHyperArmor = 1f, float secondaryStaggerTakenMultiplierDuringHyperArmor = 1f, float primaryKnockbackTakenMultiplierDuringHyperArmor = 1f, float secondaryKnockbackTakenMultiplierDuringHyperArmor = 1f, float baseDamageMultiplier = 1f, float primaryDamageRateMultiplier = 1f, float secondaryDamageRateMultiplier = 1f, float? primary1DamageRateMultiplier = null, float? primary2DamageRateMultiplier = null, float? primary3DamageRateMultiplier = null, float primaryStaminaRateMultiplier = 1f, float secondaryStaminaRateMultiplier = 1f, float primaryAttackMovementModifier = 0f, float secondaryAttackMovementModifier = 0f, float primaryLungeDistanceMultiplier = 1f, float secondaryLungeDistanceMultiplier = 1f, float? primary2LungeDistanceMultiplier = null, float? primary3LungeDistanceMultiplier = null, float primaryAttackAnimationSpeedMultiplier = 1f, float secondaryAttackAnimationSpeedMultiplier = 1f, float primaryAttackRotationFactor = 2f, float secondaryAttackRotationFactor = 2f, bool primaryLockRotationAfterAttackTrigger = true, bool secondaryLockRotationAfterAttackTrigger = true, float primaryAdrenalineMultiplier = 1f, float secondaryAdrenalineMultiplier = 1f, float primaryPvpDamageMultiplier = 1f, float? primary2PvpDamageMultiplier = null, float? primary3PvpDamageMultiplier = null, float secondaryPvpDamageMultiplier = 1f, float primaryPvpStaggerMultiplier = 1f, float secondaryPvpStaggerMultiplier = 1f, float primaryPvpStaggerDurationSeconds = -1f, float secondaryPvpStaggerDurationSeconds = -1f, float primaryPvpKnockbackForceMultiplier = 1f, float secondaryPvpKnockbackForceMultiplier = 1f, float primaryKnockbackForceMultiplier = 1f, float secondaryKnockbackForceMultiplier = 1f, bool exposePrimaryChain = true, bool exposeSecondary = true) { WeaponCategory weaponCategory = ((category == WeaponCategory.SledgeAlt) ? WeaponCategory.TwoHandedAxe : category); bool flag = category == WeaponCategory.SledgeAlt; bool flag2 = IsMinimalRangedConfigCategory(category); bool flag3 = category == WeaponCategory.Pickaxe; bool flag4 = !flag2 && !flag3; bool flag5 = !flag2; bool exposeSecondary2 = !flag2; bool expose = category != WeaponCategory.Magic; bool expose2 = category == WeaponCategory.Magic; bool expose3 = category != WeaponCategory.Magic; bool expose4 = flag4 || flag3; bool flag6 = flag4 && category != WeaponCategory.Sledge; bool expose5 = (flag4 || flag3) && category != WeaponCategory.Sledge; _bindCategoryForCurrentCategory = category; _bindPrimary3ForCurrentCategory = flag5 && !flag; _bindPrimary4ForCurrentCategory = flag5 && !flag; string section2 = WeaponFeatureSection(section, "Damage"); string section3 = WeaponFeatureSection(section, "Stagger"); string section4 = WeaponFeatureSection(section, "Hyperarmor"); string section5 = WeaponFeatureSection(section, "Hit Stop"); string section6 = WeaponFeatureSection(section, "Counter Damage"); string section7 = WeaponFeatureSection(section, "Resources"); string section8 = WeaponFeatureSection(section, "Attack Movement"); string section9 = WeaponFeatureSection(section, "Animation"); string section10 = WeaponFeatureSection(section, "Attack Rotation"); string section11 = WeaponFeatureSection(section, "Block Cancel"); string section12 = WeaponFeatureSection(section, "PvP"); string section13 = WeaponFeatureSection(section, "Knockback"); string section14 = WeaponFeatureSection(section, "Blocking"); string text = WeaponFeatureSection(section, "Hit Ray"); string section15 = WeaponFeatureSection(section, "Sneak"); string section16 = WeaponFeatureSection(section, "Multi Target Penalty"); string section17 = WeaponFeatureSection(section, "Destruction"); float num = DefaultGooAirborneDamageMultiplier(weaponCategory); float num2 = (flag ? 1f : DefaultGooHitStopDurationMultiplier(weaponCategory)); bool flag7 = weaponCategory == WeaponCategory.TwoHandedAxe; string description = (flag ? "Alternate-sledge attack movement. Primary-chain slots are authored base movement factors stacked with GlobalAttackMovementMultiplier. Secondary is additive: its configured value is added to the native two-handed-axe Secondary movement factor, then the global multiplier stacks on the sum. Root-motion lunges remain controlled separately." : (flag7 ? "Authored base movement factor for the live Attack.m_speedFactor used by this attack slot. GCO writes this value as the base, then stacks GlobalAttackMovementMultiplier. 0 disables normal input movement; 1 allows full normal input movement. Root-motion lunges remain controlled separately." : "Attack movement-speed multiplier for live Attack.m_speedFactor and Humanoid.GetAttackSpeedFactorMovement. Goo preset sets melee attacks to 0 so normal input movement is disabled during attacks; use lunge settings to tune root-motion lunges. 1 leaves attack movement unchanged.")); CategoryConfigs[category] = new CategoryConfig(category, BindCategorySetting(expose: true, section2, "BaseDamageMultiplier", baseDamageMultiplier, "Base damage multiplier for this weapon category. This scales the weapon's whole HitData damage package, including elemental/chop/pickaxe channels, and directly updates the tooltip damage block. Primary/Secondary attack multipliers do not directly change tooltips."), BindRoleSettings(!flag2, flag5, section3, "StaggerMode", primaryStaggerMode, secondaryStaggerMode, "How this attack's stagger value is applied. MultiplyVanilla multiplies Valheim's current stagger multiplier. SetFinalMultiplier replaces the final HitData stagger multiplier.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section3, "StaggerPowerValue", primaryStagger, secondaryStagger, "Attack stagger calibration. In MultiplyVanilla mode, 2 means 2x vanilla. In SetFinalMultiplier mode, 4 means final stagger multiplier becomes 4.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section3, "DamageToStaggeredEnemyMultiplier", DefaultGooDamageToStaggeredEnemyMultiplier(weaponCategory, AttackRole.Primary1), DefaultGooDamageToStaggeredEnemyMultiplier(weaponCategory, AttackRole.Primary2), DefaultGooDamageToStaggeredEnemyMultiplier(weaponCategory, AttackRole.Primary3), DefaultGooDamageToStaggeredEnemyMultiplier(weaponCategory, AttackRole.Secondary), "Local final damage multiplier for this attack slot against staggered PvE enemies. Stacks with GlobalDamageToStaggeredEnemiesMultiplier as global x local and replaces Valheim's normal staggered-enemy execution-damage result. 1 leaves the global value unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section4, "HyperArmorMode", primaryHyper, secondaryHyper, "Hyperarmor mode for this attack slot. Balanced starts when Valheim enters the real attack animation and ends immediately after the hitbox event; FullAttackAnimation lasts through recovery; WhileHoldingWeaponType is always on while the weapon type is held.", exposeSecondary2), BindRoleSettings(flag4, flag5, section4, "DamageTakenMultiplierDuringHyperArmor", primaryDamageTakenMultiplierDuringHyperArmor, secondaryDamageTakenMultiplierDuringHyperArmor, "Final HP-loss multiplier while this attack slot's hyperarmor is active. 0.70 means 30% less final damage; 1.00 means no damage reduction.", exposeSecondary2), BindRoleSettings(flag4, flag5, section4, "StaggerTakenMultiplierDuringHyperArmor", primaryStaggerTakenMultiplierDuringHyperArmor, secondaryStaggerTakenMultiplierDuringHyperArmor, "Incoming stagger multiplier during this attack slot's hyperarmor. 0 means stagger immunity; 1 means vanilla.", exposeSecondary2), BindRoleSettings(flag4, flag5, section13, "KnockbackTakenMultiplierDuringHyperArmor", primaryKnockbackTakenMultiplierDuringHyperArmor, secondaryKnockbackTakenMultiplierDuringHyperArmor, "Incoming push/knockback multiplier during this attack slot's hyperarmor. 0 means knockback immunity; 1 means vanilla.", exposeSecondary2), BindRoleSettings(flag4, flag5, section4, "PvpDamageTakenMultiplierDuringHyperArmor", 1f, 1f, "PvP-only extra damage-taken multiplier while this attack slot's hyperarmor is active and the incoming attacker is a player. Stacks on the normal hyperarmor damage-taken multiplier. 1 leaves current PvP hyperarmor damage behavior unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section4, "PvpStaggerTakenMultiplierDuringHyperArmor", 1f, 1f, "PvP-only incoming stagger multiplier while this attack slot's hyperarmor is active and the incoming attacker is a player. Stacks on the normal hyperarmor stagger-taken multiplier. 1 leaves current PvP hyperarmor stagger behavior unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section13, "PvpKnockbackTakenMultiplierDuringHyperArmor", 1f, 1f, "PvP-only incoming push/knockback multiplier while this attack slot's hyperarmor is active and the incoming attacker is a player. Stacks on the normal hyperarmor knockback-taken multiplier. 1 leaves current PvP hyperarmor knockback behavior unchanged.", exposeSecondary2), BindRoleSettings(expose5, flag5, section5, "HitStopDurationMultiplier", num2, num2, "Per attack-slot multiplier for Valheim's melee hit-stop duration. This stacks with the global hit-stop duration multiplier when no absolute override is set. 1 leaves that attack's hit-stop duration unchanged; 0.5 halves it; 0 disables it.", exposeSecondary2), BindRoleSettings(expose5, flag5, section5, "PvpHitStopDurationMultiplier", DefaultGooPvpHitStopDurationMultiplier(weaponCategory), DefaultGooPvpHitStopDurationMultiplier(weaponCategory), "PvP-only per attack-slot hit-stop duration multiplier. This multiplies Valheim's vanilla melee hit-stop directly and does not stack with PvE hit-stop multipliers.", exposeSecondary2), BindRoleSettings(flag4, flag5, section6, "CounterDamageEnabled", primaryCounter, secondaryCounter, "If true, eligible melee attacks in this slot can gain counter-damage against targets during Valheim's real attack animation/combat state.", exposeSecondary2), BindRoleSettings(flag4, flag5, section6, "CounterDamageMultiplier", 1.3f, 1.3f, "Counter-damage multiplier for eligible attacks. 1.30 means 30% additional damage.", exposeSecondary2), BindRoleSettings(flag4, flag5, section6, "PvpCounterDamageMultiplier", 1f, 1f, "PvP-only extra counter-damage multiplier when this attack counter-hits a player or PvP test dummy. Stacks on CounterDamageMultiplier and GlobalCounterDamageMultiplier. 1 leaves current PvP counter-damage behavior unchanged.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section2, "DamageRateMultiplier", primary1DamageRateMultiplier ?? primaryDamageRateMultiplier, primary2DamageRateMultiplier ?? primaryDamageRateMultiplier, primary3DamageRateMultiplier ?? primaryDamageRateMultiplier, secondaryDamageRateMultiplier, "Outgoing attack damage multiplier. 1 leaves vanilla damage unchanged.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section2, "ChopDamageMultiplier", DefaultGooChopDamageMultiplier(weaponCategory, AttackRole.Primary1), DefaultGooChopDamageMultiplier(weaponCategory, AttackRole.Primary2), DefaultGooChopDamageMultiplier(weaponCategory, AttackRole.Primary3), DefaultGooChopDamageMultiplier(weaponCategory, AttackRole.Secondary), "Attack-slot multiplier applied only to HitData.m_damage.m_chop after normal damage multipliers. Goo default keeps chop-capable axe attacks at 1. Pickaxe/tools default to 0 here, and non-chop weapons default to 0 to match vanilla utility damage channels.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section2, "PickaxeDamageMultiplier", DefaultPickaxeDamageMultiplier(weaponCategory), DefaultPickaxeDamageMultiplier(weaponCategory), "Attack-slot multiplier applied only to HitData.m_damage.m_pickaxe after normal damage multipliers. Pickaxes default to 1.5 by Goo ruleset, tools default to 1, axes and ordinary weapons default to 0 to match vanilla utility damage channels.", exposeSecondary2), BindRoleSettings(flag4, flag5, section2, "AirborneDamageMultiplier", num, num, "Outgoing damage multiplier applied to player-owned combat melee attacks that were initiated while airborne. Swimming does not count as airborne, and the Other/Pickaxes utility categories are excluded. This scales all HitData damage channels for eligible attacks. Goo default is 1.3 for melee categories without a Goo-default special jump attack and 1 otherwise; vanilla/no-op is 1.", exposeSecondary2), BindRoleSettings(flag4, flag5, section3, "AirborneStaggerMultiplier", 1f, 1f, "Outgoing stagger multiplier applied to player-owned combat melee attacks that were initiated while airborne. This stacks with normal, running, and jump attack stagger modifiers. 1 leaves airborne stagger unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section10, "AirborneRotationFactor", 1f, 1f, "Attack rotation multiplier applied to player-owned combat melee attacks that were initiated while airborne. This stacks with normal, running, and jump attack rotation factors. 1 leaves airborne rotation unchanged.", exposeSecondary2), BindRoleSettings(expose, flag5, section7, "StaminaRateMultiplier", primaryStaminaRateMultiplier, secondaryStaminaRateMultiplier, "Attack stamina-cost multiplier where the Valheim stamina method is available. 1 leaves vanilla stamina unchanged.", exposeSecondary2), BindRoleSettings(expose2, exposePrimaryChain: false, section7, "EitrRateMultiplier", 1f, 1f, "Attack eitr-cost multiplier for magic attacks. 1 leaves vanilla eitr costs unchanged.", exposeSecondary: false), BindRoleSettings(flag4, flag5, section8, "AttackMovementModifier", primaryAttackMovementModifier, secondaryAttackMovementModifier, description, exposeSecondary2), BindRoleSettings(flag4, flag5, section8, "AttackMovementApplicationMode", AttackMovementApplicationMode.FullAnimation, AttackMovementApplicationMode.FullAnimation, "Controls when this attack slot's AttackMovementModifier applies. FullAnimation preserves the previous Goo behavior for normal attacks; BeforeHitbox uses this multiplier from attack start until the hitbox event, then forces attack movement to 0 through recovery.", exposeSecondary2), BindRoleSettings(flag4, flag5, section8, "LungeDistanceMultiplier", primaryLungeDistanceMultiplier, primary2LungeDistanceMultiplier ?? primaryLungeDistanceMultiplier, primary3LungeDistanceMultiplier ?? primaryLungeDistanceMultiplier, secondaryLungeDistanceMultiplier, "Melee attack root-motion/lunge-distance multiplier. Scales only positive forward animation-authored root motion during the tracked melee attack; backward and sideways root motion are preserved. 1 leaves vanilla lunge distance unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section8, "LungeApplicationMode", LungeApplicationMode.BeforeHitbox, LungeApplicationMode.BeforeHitbox, "Controls when this attack slot's lunge/root-motion multiplier applies. BeforeHitbox stops at Valheim's hitbox event; FullAnimation keeps scaling through the full attack animation.", exposeSecondary2), BindRoleSettings(expose4, flag5, section9, "AttackAnimationSpeedMultiplier", primaryAttackAnimationSpeedMultiplier, secondaryAttackAnimationSpeedMultiplier, "Actual swing animation-speed multiplier using Animator.speed during the attack. 1 leaves vanilla animation timing unchanged.", exposeSecondary2), BindRoleSettings(expose4, flag5, section9, "RecoveryAnimationSpeedMultiplier", (category == WeaponCategory.OneHandedAxe) ? 0.8f : 1f, 1f, "Recovery animation-speed multiplier applied after Valheim's hitbox / OnAttackTrigger event. This stacks with the attack animation speed multiplier and affects only the post-hit recovery segment. 1 leaves recovery timing unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section10, "AttackRotationFactor", primaryAttackRotationFactor, secondaryAttackRotationFactor, "Attack rotation factor override for the live Attack.m_speedFactorRotation field. -1 leaves vanilla; 0 prevents turning; 2 allows 200% turn speed during the attack animation until rotation is locked.", exposeSecondary2), BindRoleSettings(flag4, flag5, section10, "LockRotationAfterAttackTrigger", primaryLockRotationAfterAttackTrigger, secondaryLockRotationAfterAttackTrigger, "If true, attack rotation is forced to 0 from Valheim's OnAttackTrigger/hitbox event until the attack recovers/stops.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section7, "AdrenalineMultiplier", primaryAdrenalineMultiplier, secondaryAdrenalineMultiplier, "Multiplier over vanilla adrenaline gained by hits. 1 leaves vanilla adrenaline unchanged; 6 attempts to add 5 extra points assuming vanilla gives 1.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section12, "PvpDamageMultiplier", primaryPvpDamageMultiplier, primary2PvpDamageMultiplier ?? primaryPvpDamageMultiplier, primary3PvpDamageMultiplier ?? primaryPvpDamageMultiplier, secondaryPvpDamageMultiplier, "PvP damage multiplier when attacker and victim are players/PvP-test-dummies. Goo default is category-specific; 1 leaves PvP unchanged.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section12, "PvpStaggerMultiplier", primaryPvpStaggerMultiplier, secondaryPvpStaggerMultiplier, "PvP stagger multiplier when attacker and victim are players/PvP-test-dummies. Goo default is category-specific; duration is controlled separately when the bar breaks.", exposeSecondary2), BindRoleSettings(expose: true, flag5, section12, "PvpStaggerDurationSeconds", (primaryPvpStaggerDurationSeconds >= 0f) ? primaryPvpStaggerDurationSeconds : DefaultPvpStaggerDuration(weaponCategory, AttackRole.Primary1), (secondaryPvpStaggerDurationSeconds >= 0f) ? secondaryPvpStaggerDurationSeconds : DefaultPvpStaggerDuration(weaponCategory, AttackRole.Secondary), "PvP-only stagger animation duration when this attack actually breaks a player/PvP-test-dummy stagger bar. 0 disables PvP stagger damage for this attack slot.", exposeSecondary2), BindRoleSettings(expose3, flag5, section13, "PvpKnockbackForceMultiplier", primaryPvpKnockbackForceMultiplier, secondaryPvpKnockbackForceMultiplier, "PvP-only outgoing knockback/push-force multiplier when attacker and victim are players/PvP-test-dummies. 1 leaves PvP knockback unchanged.", exposeSecondary2), BindRoleSettings(expose3, flag5, section13, "KnockbackForceMultiplier", primaryKnockbackForceMultiplier, secondaryKnockbackForceMultiplier, "Outgoing player knockback/push-force multiplier for this attack slot in this weapon category. 0 removes knockback; 1 is vanilla.", exposeSecondary2), BindCategorySetting(!flag, section12, "PvpBlockArmorMultiplier", DefaultGooPvpBlockArmorMultiplier(weaponCategory), "PvP-only Block Armor multiplier when this weapon category is used to block a player attack. 1 leaves vanilla PvP Block Armor unchanged."), BindCategorySetting(flag4 && !flag, section14, "BlockArmorMultiplier", 1f, "Multiplier for Block Armor when this weapon category is used to block. 1 leaves vanilla Block Armor unchanged and tooltips use the same value."), BindCategorySetting(flag4 && !flag, section14, "BlockForceMultiplier", 1f, "Multiplier for Block Force when this weapon category is used to block. 1 leaves vanilla Block Force unchanged and tooltips use the same value."), BindCategorySetting(flag4 && !flag, section14, "BlockStaminaConsumptionRate", 1f, "Multiplier for final block stamina usage when blocking with this weapon category. 1 is vanilla."), BindCategorySetting(!flag2 && !flag, section14, "BlockingStaminaRegenMultiplier", 1f, "Stamina regeneration multiplier while actively blocking with this weapon category. Stacks with GlobalBlockingStaminaRegenMultiplier. 1 is vanilla."), BindCategorySetting(flag4 && !flag, section14, "BlockBreakThresholdMultiplier", 1f, "Multiplier for effective stagger threshold while blocking with this weapon category. 1 is vanilla; 2 means blocked stagger damage contributes half as much toward block break/stagger."), BindRoleSettings(flag4, flag5, section11, "BlockCancelMode", BlockCancelMode.AfterHitbox, BlockCancelMode.AfterHitbox, "Allows block input to cancel this attack slot by accelerating the remaining attack animation without aborting the attack. Disabled prevents block canceling. BeforeHitbox allows only startup cancel and suppresses the hitbox. AfterHitbox allows only post-hit recovery cancel. FullAnimation allows either; if used before the hitbox, the hitbox is suppressed.", exposeSecondary2), BindRoleSettings(flag4, flag5, section11, "BlockCancelAnimationSpeedMultiplier", 1f, 1f, "Animation-speed multiplier applied when block cancel starts for this attack slot. 2 means the remaining attack animation plays at 2x speed. 1 leaves animation speed unchanged.", exposeSecondary2), BindRoleSettings(flag4, flag5, section9, "FullAttackAnimationDurationSeconds", primaryFullSeconds, secondaryFullSeconds, "Advanced fallback safety cap for FullAttackAnimation hyperarmor/counter windows. Attack.Stop/Abort/done hooks clear the real window earlier.", exposeSecondary2)); BlockCancelModes[category] = CategoryConfigs[category].BlockCancelMode; if (category == WeaponCategory.Atgeir) { HimminAflLightningChainChance = BindConfig(section2, "HimminAflLightningChainChance", 0.25f, "Chance per qualifying Himmin Afl hit to apply the Ashlands-style lightning-chain damage effect. This is gameplay behavior and is independent from the global lightning visual toggle. Goo default is 0.25."); } bool flag8 = IsBackstabConfigCategory(category); bool exposePrimaryChain2 = flag8 && (flag5 || category == WeaponCategory.Bow || category == WeaponCategory.Crossbow); AttackStartNoiseMultipliers[category] = BindRoleSettings(flag8, exposePrimaryChain2, section15, "AttackStartNoiseMultiplier", 1f, 1f, "Multiplier for Valheim's AI aggro/noise radius when this attack starts. Scales Attack.m_attackStartNoise on the cloned Attack instance. 1 is vanilla/no-op.", exposeSecondary2); AttackHitNoiseMultipliers[category] = BindRoleSettings(flag8, exposePrimaryChain2, section15, "AttackHitNoiseMultiplier", 1f, 1f, "Multiplier for Valheim's AI aggro/noise radius when this attack hits. Scales Attack.m_attackHitNoise on the cloned Attack instance. 1 is vanilla/no-op.", exposeSecondary2); BackstabDamageMultipliers[category] = BindRoleSettings(flag8, exposePrimaryChain2, section15, "SneakDamageMultiplier", DefaultGooBackstabDamageMultiplier(weaponCategory), DefaultGooBackstabDamageMultiplier(weaponCategory), DefaultGooBackstabDamageMultiplier(weaponCategory), DefaultGooBackstabDamageMultiplier(weaponCategory), "Local sneak damage multiplier for this attack slot. Final tooltip/runtime sneak damage equals Global Sneak Damage Multiplier x this local value. Goo preset default is 2 for knives/dual knives, 3 for unarmed, and 1 for other weapon types; Reset to Vanilla sets this to the vanilla final sneak value while global becomes 1.", exposeSecondary2); AttackRangeMultipliers[category] = BindRoleSettings(flag6, flag5, text, "HitRayRange", 1f, 1f, "Multiplier for melee hit-ray range. Scales Attack.m_attackRange on the cloned Attack instance. 1 is vanilla/no-op.", exposeSecondary2); float num3 = ((weaponCategory == WeaponCategory.Atgeir) ? 2f : 1f); AttackRayWidthMultipliers[category] = BindRoleSettings(flag6, flag5, text, "HitRayThickness", num3, num3, "Multiplier for melee hit-ray thickness. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra on the cloned Attack instance. 1 is vanilla/no-op; Goo-default atgeir attacks use 2.0x thickness; attacks with zero vanilla thickness can use 0 to preserve vanilla or positive values to give them thickness.", exposeSecondary2); if (category == WeaponCategory.OneHandedAxe) { OneHandedAxeSecondaryHitRayAddedThicknessMeters = BindConfig(text, "SecondaryHitRayAddedThicknessMeters", 0.4f, "Additive melee hit-ray thickness in meters for the one-handed axe secondary attack only. This is added after normal thickness multipliers because vanilla one-handed axe secondary has no ray thickness. Goo default is 0.4m; Reset to Vanilla sets this to 0."); } AttackHitboxHeightMultipliers[category] = BindRoleSettings(flag6, flag5, text, "HitRayStartHeight", 1f, 1f, "Multiplier for melee hit-ray start height. Scales Attack.m_attackHeight on the cloned Attack instance. 1 is vanilla/no-op.", exposeSecondary2); AttackOffsetMultipliers[category] = BindRoleSettings(flag6, flag5, text, "HitRayOffset", 1f, 1f, "Multiplier for melee hit-ray sideways offset. Scales Attack.m_attackOffset on the cloned Attack instance. 1 is vanilla/no-op.", exposeSecondary2); AttackHitboxTypes[category] = BindRoleSettings(flag6, flag5, text, "AttackHitboxType", AttackHitboxType.Horizontal, AttackHitboxType.Horizontal, "Hitbox fan direction for this attack slot. Horizontal uses Valheim's yaw fan; Vertical uses Valheim's pitch fan. Vanilla/no-op values restore the mod's best-known vanilla direction for this attack.", exposeSecondary2); AttackAngleMultipliers[category] = BindRoleSettings(flag6, flag5, text, "HitRayAngle", 1f, 1f, "Multiplier for melee hit-ray fan angle, Attack.m_attackAngle. 1 is vanilla/no-op.", exposeSecondary2); BindVanillaWeaponPrefabHitRayRangeConfigs(category, text, flag6); bool expose6 = flag4 && category != WeaponCategory.Sledge; AttackMultiTargetDamagePenaltyModes[category] = BindRoleSettings(expose6, flag5, section16, "MultiTargetDamagePenalty", DefaultGooMultiTargetDamagePenaltyModeForHitThrough(weaponCategory, AttackRole.Primary1), DefaultGooMultiTargetDamagePenaltyModeForHitThrough(weaponCategory, AttackRole.Primary2), DefaultGooMultiTargetDamagePenaltyModeForHitThrough(weaponCategory, AttackRole.Primary3), DefaultGooMultiTargetDamagePenaltyModeForHitThrough(weaponCategory, AttackRole.Secondary), "Controls Attack.m_lowerDamagePerHit for this cloned melee attack. Enabled allows Valheim's multi-target damage falloff and forces Hit Through Mobs off; Disabled prevents damage loss across multiple targets. Sledges are excluded because their AOE-style identity keeps vanilla handling.", exposeSecondary2); AttackMultiHitEnabled[category] = BindRoleSettings(flag4, flag5, section16, "MultiHit", DefaultGooMultiHitEnabled(weaponCategory, AttackRole.Primary1), DefaultGooMultiHitEnabled(weaponCategory, AttackRole.Primary2), DefaultGooMultiHitEnabled(weaponCategory, AttackRole.Primary3), DefaultGooMultiHitEnabled(weaponCategory, AttackRole.Secondary), "If true, this melee attack may damage multiple valid targets collected by its hit rays. Multi Hit controls how many collected targets receive damage; it does not control whether an individual ray continues through a target. For sledges, this only matters for the experimental alternate direct-hit-ray moveset.", exposeSecondary2); AttackHitThroughTargetsEnabled[category] = BindRoleSettings(flag4, flag5, section16, "HitThroughMobs", DefaultGooHitThroughMobsEnabled(weaponCategory, AttackRole.Primary1), DefaultGooHitThroughMobsEnabled(weaponCategory, AttackRole.Primary2), DefaultGooHitThroughMobsEnabled(weaponCategory, AttackRole.Primary3), DefaultGooHitThroughMobsEnabled(weaponCategory, AttackRole.Secondary), "If true, each individual melee hit ray continues through characters, creatures, and players instead of stopping at the first valid character target. This is always forced off while Multi Target Damage Penalty is Enabled and remains independent from Hit Through Walls.", exposeSecondary2); AttackHitThroughWallsEnabled[category] = BindRoleSettings(flag4, flag5, section16, "HitThroughWalls", DefaultGooHitThroughWallsEnabled(weaponCategory, AttackRole.Primary1), DefaultGooHitThroughWallsEnabled(weaponCategory, AttackRole.Primary2), DefaultGooHitThroughWallsEnabled(weaponCategory, AttackRole.Primary3), DefaultGooHitThroughWallsEnabled(weaponCategory, AttackRole.Secondary), "If true, each individual melee hit ray continues through non-character obstructions such as terrain, buildings, rocks, trees, and other destructibles. This is independent from Hit Through Mobs.", exposeSecondary2); AttackBuildingDamageMultipliers[category] = BindRoleSettings(expose: true, flag5, section17, "DamageToBuildingsMultiplier", 1f, 1f, "Attack-slot multiplier for damage dealt by this weapon/tool attack to WearNTear buildings/pieces. Stacks after player/global building damage and normal outgoing damage tuning. 1 is vanilla/no-op.", exposeSecondary2); AttackStaggeredEnemyDamageMultipliers[category] = CategoryConfigs[category].DamageToStaggeredEnemyMultiplier; string section18 = WeaponFeatureSection(section, "Backstab"); bool exposePrimaryChain3 = flag5 || category == WeaponCategory.Bow || category == WeaponCategory.Crossbow || category == WeaponCategory.Magic; AttackFlankingDamageEnabled[category] = BindRoleSettings(expose: true, exposePrimaryChain3, section18, "BackstabDamageEnabled", DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary1), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary2), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary3), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Secondary), "If true, this attack slot can receive flank damage when it hits the configured rear sector. Goo default disables ranged flank attacks, including bows/crossbows/magic and spear throws.", exposeSecondary2); AttackFlankingDamageMultipliers[category] = BindRoleSettings(expose: true, exposePrimaryChain3, section18, "BackstabDamageMultiplier", DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: false), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: false), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: false), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: false), "PvE damage multiplier for this attack slot when it hits the rear flank arc of an enemy. Stacks with the global flank multiplier. 1 is vanilla/no-op.", exposeSecondary2); AttackPvpFlankingDamageMultipliers[category] = BindRoleSettings(expose: true, exposePrimaryChain3, section18, "PvpBackstabDamageMultiplier", DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: true), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: true), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: true), DefaultGooFlankingDamageMultiplier(weaponCategory, pvp: true), "PvP damage multiplier for this attack slot when it hits the rear flank arc of a player/PvP target. Stacks with the global PvP flank multiplier. 1 is vanilla/no-op.", exposeSecondary2); AttackFlankingDingEnabled[category] = BindRoleSettings(expose: true, exposePrimaryChain3, section18, "BackstabDingEnabled", DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary1), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary2), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Primary3), DefaultGooFlankingDamageEnabled(weaponCategory, AttackRole.Secondary), "If true, this attack slot may play the flank ding when it successfully applies flank damage. The global Successful Flank Ding switch must also be enabled.", exposeSecondary2); if (!flag) { WeaponEffectiveBlockAngleMultipliers[category] = BindConfig(section14, "EffectiveBlockAngleMultiplier", 1f, "Effective block angle multiplier when blocking with this weapon/tool category. Valheim's default block sector is 180 degrees; 1 is vanilla/no-op."); WeaponBlockExtras[category] = BindWeaponBlockExtras(section14); WeaponDamageTakenOnBlockedHitConfigs[category] = BindBlockedHitDamageTakenConfig(section14); } } private static bool IsMinimalRangedConfigCategory(WeaponCategory category) { if (category != WeaponCategory.Bow && category != WeaponCategory.Crossbow) { return category == WeaponCategory.Magic; } return true; } private static bool IsBackstabConfigCategory(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword && category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.Sledge && category != WeaponCategory.SledgeAlt && category != WeaponCategory.Pickaxe && category != WeaponCategory.DualAxes && category != WeaponCategory.Atgeir && category != WeaponCategory.OneHandedSword && category != WeaponCategory.OneHandedAxe && category != WeaponCategory.Club && category != WeaponCategory.Knife && category != WeaponCategory.DualKnives && category != WeaponCategory.Spear && category != WeaponCategory.Bow && category != WeaponCategory.Crossbow && category != WeaponCategory.Unarmed) { return category == WeaponCategory.Other; } return true; } private static float DefaultVanillaBackstabDamageMultiplier(WeaponCategory category) { switch (category) { case WeaponCategory.Knife: return 6f; case WeaponCategory.DualKnives: return 6f; case WeaponCategory.Sledge: return 2f; case WeaponCategory.SledgeAlt: return 3f; default: if (IsBackstabConfigCategory(category)) { return 3f; } return 1f; } } private static float DefaultGooBackstabDamageMultiplier(WeaponCategory category) { switch (category) { default: return 1f; case WeaponCategory.Unarmed: return 3f; case WeaponCategory.Knife: case WeaponCategory.DualKnives: return 2f; } } private static string WeaponFeatureSection(string weaponSection, string feature) { if (weaponSection.Equals("Sledge - Alt Moveset", StringComparison.OrdinalIgnoreCase)) { return weaponSection + " " + feature; } return weaponSection + " - " + feature; } private Setting BindCategorySetting(bool expose, string section, string key, T defaultValue, string description) { if (!expose) { return new Setting(defaultValue); } return new Setting(BindConfig(section, key, defaultValue, description)); } private RoleSettings BindRoleSettings(bool expose, string section, string suffix, T primaryDefault, T secondaryDefault, string description, bool exposeSecondary = true) { return BindRoleSettings(expose, exposePrimaryChain: true, section, suffix, primaryDefault, primaryDefault, primaryDefault, secondaryDefault, description, exposeSecondary); } private RoleSettings BindRoleSettings(bool expose, bool exposePrimaryChain, string section, string suffix, T primaryDefault, T secondaryDefault, string description, bool exposeSecondary = true) { return BindRoleSettings(expose, exposePrimaryChain, section, suffix, primaryDefault, primaryDefault, primaryDefault, secondaryDefault, description, exposeSecondary); } private RoleSettings BindRoleSettings(bool expose, string section, string suffix, T primary1Default, T primary2Default, T primary3Default, T secondaryDefault, string description, bool exposeSecondary = true) { return BindRoleSettings(expose, exposePrimaryChain: true, section, suffix, primary1Default, primary2Default, primary3Default, secondaryDefault, description, exposeSecondary); } private RoleSettings BindRoleSettings(bool expose, bool exposePrimaryChain, string section, string suffix, T primary1Default, T primary2Default, T primary3Default, T secondaryDefault, string description, bool exposeSecondary = true) { bool flag = expose && exposePrimaryChain; bool flag2 = flag && _bindPrimary3ForCurrentCategory; bool item = flag2 && _bindPrimary4ForCurrentCategory; bool item2 = expose && exposeSecondary; WeaponCategory category = _bindCategoryForCurrentCategory; Setting primary1 = new Setting(primary1Default); Setting primary2 = new Setting(primary2Default); Setting primary3 = new Setting(primary3Default); Setting primary4 = new Setting(primary3Default); Setting secondary = new Setting(secondaryDefault); BindRole(AttackRole.Primary1, expose, primary1Default); List<(AttackRole, bool, T)> list = new List<(AttackRole, bool, T)>(); list.Add((AttackRole.Primary2, flag, primary2Default)); list.Add((AttackRole.Primary3, flag2, primary3Default)); list.Add((AttackRole.Primary4, item, primary3Default)); list.Add((AttackRole.Secondary, item2, secondaryDefault)); list.Sort(delegate((AttackRole Role, bool Expose, T DefaultValue) a, (AttackRole Role, bool Expose, T DefaultValue) b) { int num = ((!IsVanillaAttackRole(category, a.Role)) ? 1 : 0); int num2 = ((!IsVanillaAttackRole(category, b.Role)) ? 1 : 0); return (num != num2) ? num.CompareTo(num2) : RoleConfigSortIndex(a.Role).CompareTo(RoleConfigSortIndex(b.Role)); }); foreach (var item3 in list) { BindRole(item3.Item1, item3.Item2, item3.Item3); } return new RoleSettings(primary1, primary2, primary3, secondary, primary4); void BindRole(AttackRole role, bool roleExpose, T roleDefault) { Setting setting = BindCategorySetting(roleExpose, section, RoleConfigKeyPrefix(category, role) + suffix, roleDefault, description); switch (role) { case AttackRole.Primary2: primary2 = setting; break; case AttackRole.Primary3: primary3 = setting; break; case AttackRole.Primary4: primary4 = setting; break; case AttackRole.Secondary: secondary = setting; break; default: primary1 = setting; break; } } } private static int RoleConfigSortIndex(AttackRole role) { return role switch { AttackRole.Primary2 => 2, AttackRole.Primary3 => 3, AttackRole.Primary4 => 4, AttackRole.Secondary => 5, _ => 1, }; } private static string RoleConfigKeyPrefix(WeaponCategory category, AttackRole role) { if (!IsVanillaAttackRole(category, role)) { return "Modded" + role; } return role.ToString(); } private static bool IsVanillaAttackRole(WeaponCategory category, AttackRole role) { if (role == AttackRole.Primary1) { return true; } return category switch { WeaponCategory.Sledge => false, WeaponCategory.Pickaxe => false, WeaponCategory.Spear => role == AttackRole.Secondary, WeaponCategory.Unarmed => role == AttackRole.Primary2 || role == AttackRole.Secondary, WeaponCategory.DualAxes => true, _ => role != AttackRole.Primary4, }; } private static void RegisterCommandConfigEntry(string section, string prettyKey, ConfigEntryBase entry) { if (entry == null) { return; } string text = BuildCompactCommandPath(section, prettyKey); if (!string.IsNullOrWhiteSpace(text)) { GcoCommandPrimaryPaths[entry] = text; if (RegisterFlatGcoCommandEntry(text, entry)) { GcoPrimaryCommandRegisteredEntries.Add(entry); } RegisterAllGcoCommandEntry(text, entry); } } private static void RegisterAllGcoCommandEntry(string primaryPath, ConfigEntryBase entry) { if (entry != null && !string.IsNullOrWhiteSpace(primaryPath)) { string text = string.Join("_", from token in primaryPath.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(SanitizeFlatGcoFeatureToken) where !string.IsNullOrWhiteSpace(token) select token); if (!string.IsNullOrWhiteSpace(text) && RegisterFlatGcoCommandEntry("All " + text, entry) && GcoFlatCommandEntries.TryGetValue("all", out Dictionary value) && value.Values.Any((ConfigEntryBase candidate) => candidate == entry)) { GcoAllCommandRegisteredEntries.Add(entry); } } } private static bool RegisterFlatGcoCommandEntry(string primaryPath, ConfigEntryBase entry) { if (entry == null || string.IsNullOrWhiteSpace(primaryPath)) { return false; } if (!TryBuildFlatGcoBranchAndFeature(primaryPath, out string branchDisplay, out string featureDisplay)) { return false; } string text = NormalizeCommandKey(branchDisplay); string text2 = NormalizeCommandKey(featureDisplay); if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(text2)) { return false; } if (!GcoFlatCommandEntries.TryGetValue(text, out Dictionary value)) { value = new Dictionary(StringComparer.OrdinalIgnoreCase); GcoFlatCommandEntries[text] = value; } if (value.TryGetValue(text2, out var value2) && value2 != entry) { string text3 = featureDisplay + " " + PrettyConfigKey(entry.Definition.Key); string text4 = text3; string text5 = NormalizeCommandKey(text4); int num = 2; ConfigEntryBase value3; while (value.TryGetValue(text5, out value3) && value3 != entry) { text4 = text3 + " " + num.ToString(CultureInfo.InvariantCulture); text5 = NormalizeCommandKey(text4); num++; } GcoConsoleFeatureCollisionCount++; featureDisplay = text4; text2 = text5; } value[text2] = entry; if (!GcoFlatCommandOptions.TryGetValue(text, out SortedSet value4)) { value4 = new SortedSet(StringComparer.OrdinalIgnoreCase); GcoFlatCommandOptions[text] = value4; } value4.Add(featureDisplay); GcoFlatBranchDisplayNames[text] = branchDisplay; if (value.TryGetValue(text2, out var value5)) { return value5 == entry; } return false; } private static void EnsureConsoleCommandCoverage() { if ((Object)(object)Instance == (Object)null) { return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; List list = new List(); try { if (!((typeof(ConfigFile).GetField("_entries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(ConfigFile).GetField("Entries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(ConfigFile).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((FieldInfo field) => typeof(IDictionary).IsAssignableFrom(field.FieldType)))?.GetValue(((BaseUnityPlugin)Instance).Config) is IDictionary dictionary)) { Log.LogWarning((object)"Could not inspect BepInEx ConfigFile entries for console-command coverage audit; bind-time registration remains active."); return; } foreach (DictionaryEntry item in dictionary) { object? value = item.Value; ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null); if (val == null) { continue; } num4++; string value2; bool flag = GcoCommandPrimaryPaths.TryGetValue(val, out value2) && !string.IsNullOrWhiteSpace(value2); if (!flag) { RegisterCommandConfigEntry(val.Definition.Section, val.Definition.Key, val); flag = GcoCommandPrimaryPaths.TryGetValue(val, out value2) && !string.IsNullOrWhiteSpace(value2); if (flag) { num++; } } else if (!GcoPrimaryCommandRegisteredEntries.Contains(val) && RegisterFlatGcoCommandEntry(value2, val)) { GcoPrimaryCommandRegisteredEntries.Add(val); num2++; } if (!GcoAllCommandRegisteredEntries.Contains(val) && flag) { RegisterAllGcoCommandEntry(value2, val); if (GcoAllCommandRegisteredEntries.Contains(val)) { num3++; } } if (!flag || !GcoPrimaryCommandRegisteredEntries.Contains(val) || !GcoAllCommandRegisteredEntries.Contains(val)) { list.Add("[" + val.Definition.Section + "] " + val.Definition.Key); } } int num5 = num4 - list.Count; Log.LogInfo((object)$"GCO console-command coverage audit: {num5}/{num4} bound config entries are reachable; repaired {num} missing primary path(s), {num2} missing primary-branch registration(s), and {num3} missing exact All-branch fallback(s). Resolved {GcoConsoleFeatureCollisionCount} deterministic flat-command token collision(s) without per-entry warning spam. Resource controls are registered under the Resources branch."); if (list.Count > 0) { Log.LogError((object)("GCO console-command coverage audit still has unresolved entries: " + string.Join("; ", list))); } HashSet expectedRunnable = new HashSet(GetExpectedRunnableCommandEntries()); List source = dictionary.Values.Cast().OfType().Distinct() .ToList(); List list2 = (from entry in expectedRunnable where !IsRunnableCommandEntry(entry) || !GcoPrimaryCommandRegisteredEntries.Contains(entry) || !GcoAllCommandRegisteredEntries.Contains(entry) select "[" + entry.Definition.Section + "] " + entry.Definition.Key).ToList(); List list3 = (from entry in source where IsRunnableCommandEntry(entry) && !expectedRunnable.Contains(entry) select "[" + entry.Definition.Section + "] " + entry.Definition.Key).ToList(); if (list2.Count == 0 && list3.Count == 0) { Log.LogInfo((object)$"GCO runnable-command audit: {expectedRunnable.Count}/{expectedRunnable.Count} one-shot actions are reachable through their branch run command; no persistent settings are misclassified as actions."); } else { Log.LogError((object)("GCO runnable-command audit failed. Missing actions: " + ((list2.Count == 0) ? "none" : string.Join("; ", list2)) + ". Misclassified persistent settings: " + ((list3.Count == 0) ? "none" : string.Join("; ", list3)))); } } catch (Exception ex) { Log.LogWarning((object)("GCO console-command coverage audit failed safely: " + ex.GetType().Name + ": " + ex.Message)); } } private static bool TryBuildFlatGcoBranchAndFeature(string primaryPath, out string branchDisplay, out string featureDisplay) { branchDisplay = string.Empty; featureDisplay = string.Empty; string[] array = primaryPath.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { return false; } int count; if (NormalizeCommandKey(array[0]) == "weapon" && array.Length >= 3) { branchDisplay = array[1]; count = 2; } else { branchDisplay = FlatGcoBranchDisplayName(array[0]); count = 1; } string[] array2 = (from part in array.Skip(count) where !string.IsNullOrWhiteSpace(part) select part).ToArray(); if (array2.Length == 0) { return false; } featureDisplay = string.Join("_", from part in array2.Select(SanitizeFlatGcoFeatureToken) where !string.IsNullOrWhiteSpace(part) select part); if (!string.IsNullOrWhiteSpace(branchDisplay)) { return !string.IsNullOrWhiteSpace(featureDisplay); } return false; } private static string FlatGcoBranchDisplayName(string token) { string text = NormalizeCommandKey(token); return text switch { "enemy" => "Enemies", "enemies" => "Enemies", "player" => "Players", "players" => "Players", "pvp" => "PvP", "stealthattributes" => "Stealth", "stealth" => "Stealth", _ => string.IsNullOrWhiteSpace(token) ? string.Empty : SanitizeFlatGcoCommandToken(ToPascalToken(text)), }; } private static string SanitizeFlatGcoCommandToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in token) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } private static string SanitizeFlatGcoFeatureToken(string token) { return SanitizeFlatGcoCommandToken(token); } private static string FlatGcoCommandName(string action, string branchDisplay) { return "gco_" + action + "_" + SanitizeFlatGcoCommandToken(branchDisplay); } private static string BuildCompactCommandPath(string section, string prettyKey) { List list = CompactSectionTokens(section); string text = NormalizeCommandKey(section); string normalizedKey = NormalizeCommandKey(prettyKey); switch (text) { case "player": list = new List { "player", PlayerFeatureTokenFromKeyHint(normalizedKey) }; break; case "enemies": case "enemy": list = new List { "enemy", EnemyFeatureTokenFromKeyHint(normalizedKey) }; break; } List list2 = CompactKeyTokens(prettyKey).ToList(); if (IsPersistedFlankSection(section)) { list2 = list2.Select((string token) => ReplaceOrdinalIgnoreCase(token, "Backstab", "Flank")).ToList(); } else if (IsPersistedBackstabSection(section)) { list2 = RenamePersistedSneakCommandTokens(list2, normalizedKey); } list.AddRange(list2); return string.Join(" ", from t in list where !string.IsNullOrWhiteSpace(t) select t.Trim()); } private static List RenamePersistedSneakCommandTokens(List tokens, string normalizedKey) { return tokens; } private static List CompactSectionTokens(string section) { List list = new List(); if (string.IsNullOrWhiteSpace(section)) { return list; } string text = section.Trim(); string[] array = text.Split(new string[1] { " - " }, StringSplitOptions.None); string text2 = ((array.Length != 0) ? array[0].Trim() : text); string text3 = ((array.Length > 1) ? string.Join(" ", array.Skip(1)).Trim() : string.Empty); string text4 = NormalizeCommandKey(text2); string text5 = SectionFeatureToken(text3); if (TryGetWeaponCommandName(text2, out string weaponCommandName)) { list.Add("weapon"); list.Add(weaponCommandName); if (!string.IsNullOrWhiteSpace(text5)) { list.Add(text5); } return list; } if (TryGetShieldCommandName(text2, out string shieldCommandName)) { list.Add("blocking"); list.Add("shield"); list.Add(shieldCommandName); if (!string.IsNullOrWhiteSpace(text5)) { list.Add(text5); } return list; } switch (text4) { case "general": return new List { "general" }; case "debug": return new List { "debug" }; case "resources": case "resource": return new List { "resources" }; case "throwables": case "throwable": return new List { "throwables" }; case "player": return new List { "player", PlayerFeatureTokenFromKeyHint(string.Empty) }; case "enemies": case "enemy": return new List { "enemy" }; case "damage": return new List { "global", "damage" }; case "pvpdamage": return new List { "pvp", "damage" }; case "staggering": case "stagger": return new List { "staggering" }; case "hyperarmor": return new List { "hyperarmor", "global" }; case "counterdamage": return new List { "counter", "global" }; case "hitstophitlag": return new List { "hitstop", "global" }; case "attackmovementandlunge": return new List { "global", "movement" }; case "attackrotation": return new List { "global", "rotation" }; case "animation": return new List { "global", "animation" }; case "blockingandparry": return new List { "blocking", "global" }; case "knockback": return new List { "global", "knockback" }; case "hitray": return new List { "global", "hitray" }; case "backstab": return new List { "flank" }; case "sneak": return new List { "sneak" }; case "stealthattributes": case "stealth": return new List { "global", "stealth" }; case "armor": return new List { "armor" }; case "magic": list.Add("magic"); if (!string.IsNullOrWhiteSpace(text3)) { list.Add(MagicSourceToken(text3)); } return list; default: list.Add(ReadableToken(text2)); if (!string.IsNullOrWhiteSpace(text5)) { list.Add(text5); } return list; } } private static IEnumerable CompactKeyTokens(string prettyKey) { if (string.IsNullOrWhiteSpace(prettyKey)) { yield break; } string normalizedKey = NormalizeCommandKey(prettyKey); if (TryStripAttackSlotPrefix(normalizedKey, out string slot, out string remainder)) { yield return slot; string text = SettingTokenFromNormalizedKey(remainder); if (!string.IsNullOrWhiteSpace(text)) { yield return text; } } else if (normalizedKey.StartsWith("pvptestdummy", StringComparison.Ordinal)) { yield return "pvpDummy"; yield return SettingTokenFromNormalizedKey(normalizedKey.Substring("pvptestdummy".Length)); } else if (normalizedKey == "spawnpvptestdummy") { yield return "pvpDummy"; yield return "Spawn"; } else if (normalizedKey == "removepvptestdummies") { yield return "pvpDummy"; yield return "Remove"; } else { string text2 = SettingTokenFromNormalizedKey(normalizedKey); if (!string.IsNullOrWhiteSpace(text2)) { yield return text2; } } } private static string SectionFeatureToken(string value) { string text = NormalizeCommandKey(value); if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } return text switch { "equipmentmovement" => "movement", "attackmovementandlunge" => "movement", "attackrotation" => "rotation", "hitstophitlag" => "hitstop", "counterdamage" => "counter", "backstab" => "flank", "sneak" => "sneak", "pvpdamage" => "pvp", "blockingandparry" => "blocking", "stealthattributes" => "stealth", "stealth" => "stealth", "hitray" => "hitray", "multitarget" => "multiTarget", "resources" => "resources", "ranged" => "ranged", "destruction" => "destruction", "damage" => "damage", "animation" => "animation", "movement" => "movement", "rotation" => "rotation", "hyperarmor" => "hyperarmor", "staggering" => "staggering", "stagger" => "staggering", "knockback" => "knockback", "blockcancel" => "blockCancel", _ => ReadableToken(value), }; } private static string SettingTokenFromNormalizedKey(string normalizedKey) { if (string.IsNullOrWhiteSpace(normalizedKey)) { return string.Empty; } string text = normalizedKey; while (text.StartsWith("modded", StringComparison.Ordinal)) { text = text.Substring("modded".Length); } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["applymodoptions"] = "ApplyModOptions", ["setgooruleset"] = "ApplyGoosRuleset", ["settogoosruleset"] = "ApplyGoosRuleset", ["settovanillaruleset"] = "ResetToVanilla", ["broadcastconfigchanges"] = "BroadcastConfigChanges", ["roll"] = "Roll", ["alwaysdomaxdamage"] = "AlwaysDoMaxDamage", ["alwaysdomaxdamageinpvp"] = "AlwaysDoMaxDamageInPvp", ["globaldamagemultiplier"] = "Damage", ["damageratemultiplier"] = "Damage", ["basedamagemultiplier"] = "BaseDamage", ["backstabdamagemultiplier"] = "BackstabDamage", ["globalbackstabdamagemultiplier"] = "BackstabDamage", ["sneakdamagemultiplier"] = "SneakDamage", ["globalsneakdamagemultiplier"] = "SneakDamage", ["sneakdamagestackablewithotherbonuses"] = "SneakDamageStackable", ["sneakdamagescaleswithsneaklevel"] = "SneakScalesWithSneakLevel", ["sneakdamagescalebasemultiplier"] = "SneakScaleBase", ["sneakdamagemultiplierpersneaklevel"] = "SneakScalePerSneakLevel", ["damagetobuildingsmultiplier"] = "DamageToBuildings", ["playerdamagetobuildingsmultiplier"] = "DamageToBuildings", ["enemydamagetobuildingsmultiplier"] = "DamageToBuildings", ["enemyspeedmultiplier"] = "EnemySpeed", ["enemyattackanimationspeedmultiplier"] = "EnemyAttackSpeed", ["enemymovementanimationspeedmultiplier"] = "EnemyMovementAnimSpeed", ["enemysizemultiplier"] = "EnemySize", ["enemyattackhitrayrangemultiplier"] = "EnemyHitRayRange", ["enemyattackhitraysizemultiplier"] = "EnemyHitRaySize", ["enemyattackhitrayheightmultiplier"] = "EnemyHitRayHeight", ["enemyattackhitrayoffsetmultiplier"] = "EnemyHitRayOffset", ["pvpdamagemultiplier"] = "PvpDamage", ["pvpcounterdamagemultiplier"] = "PvpCounterDamage", ["pvpstaggermultiplier"] = "PvpStagger", ["pvpstaggerdurationseconds"] = "PvpStaggerDuration", ["pvpknockbackforcemultiplier"] = "PvpKnockback", ["knockbackforcemultiplier"] = "Knockback", ["staggerpowervalue"] = "StaggerPower", ["staggermode"] = "StaggerMode", ["globalplayerstaggerpowermultiplier"] = "PlayerStaggerPower", ["enemy_staggerdamagedealtmultiplier"] = "EnemyStaggerDamageDealt", ["enemystaggerdamagedealtmultiplier"] = "EnemyStaggerDamageDealt", ["enemypoisemultiplier"] = "EnemyPoise", ["playerpoisemultiplier"] = "PlayerPoise", ["hyperarmormode"] = "Mode", ["damagetakenmultiplierduringhyperarmor"] = "DamageTaken", ["staggertakenmultiplierduringhyperarmor"] = "StaggerTaken", ["knockbacktakenmultiplierduringhyperarmor"] = "KnockbackTaken", ["globalhyperarmordamagetakenmultiplier"] = "DamageTaken", ["globalhyperarmorknockbacktakenmultiplier"] = "KnockbackTaken", ["counterdamageenabled"] = "Enable", ["counterdamagemultiplier"] = "Damage", ["globalcounterdamagemultiplier"] = "Damage", ["hitstopdurationmultiplier"] = "Duration", ["pvphitstopdurationmultiplier"] = "PvpDuration", ["hitstopdurationoverrideseconds"] = "OverrideSeconds", ["globalhitstopenabled"] = "Enable", ["attackanimationspeedmultiplier"] = "Speed", ["recoveryanimationspeedmultiplier"] = "RecoverySpeed", ["morelightningvisualeffectsonhit"] = "LightningVisualOnHit", ["betterlightningeffects"] = "LightningVisualOnHit", ["himminafllightningchainchance"] = "HimminAflChainChance", ["himminalflightningchainchance"] = "HimminAflChainChance", ["dundrlightningchainchanceperpellet"] = "DundrChainChance", ["sneakxponsuccessfulsneakattack"] = "SneakXpOnSneakAttack", ["sneakxponsuccessfulsneakattackbaseamount"] = "SneakXpBase", ["sneakxpscaleswithsneakdamagemultiplier"] = "SneakXpScalesWithSneakDamage", ["sneakxprangedattackmultiplier"] = "SneakXpRangedAttack", ["usealternatemoveset"] = "UseAlternateMoveset", ["use2haxeanimation"] = "UseAlternateMoveset", ["usetwohandedaxeanimation"] = "UseAlternateMoveset", ["primary1directhitraystartheightmeters"] = "Primary1StartHeight", ["primary1directhitrayrangemeters"] = "Primary1Range", ["primary1directhitrayangledegrees"] = "Primary1Angle", ["primary1directhitraythicknessmeters"] = "Primary1Thickness", ["primary2directhitraystartheightmeters"] = "Primary2StartHeight", ["primary2directhitrayrangemeters"] = "Primary2Range", ["primary2directhitrayangledegrees"] = "Primary2Angle", ["primary2directhitraythicknessmeters"] = "Primary2Thickness", ["runningdirecthitraystartheightmeters"] = "RunningStartHeight", ["runningdirecthitrayrangemeters"] = "RunningRange", ["runningdirecthitrayangledegrees"] = "RunningAngle", ["runningdirecthitraythicknessmeters"] = "RunningThickness", ["fullattackanimationdurationseconds"] = "FullDurationSeconds", ["attackmovementmodifier"] = "AttackMovement", ["attackmovementapplicationmode"] = "AttackMovementMode", ["lungedistancemultiplier"] = "Lunge", ["lungeapplicationmode"] = "LungeMode", ["lockrotationafterattacktrigger"] = "LockAfterHitbox", ["attackrotationmultiplier"] = "Rotation", ["hitrrayrange"] = "Range", ["hitrayrange"] = "Range", ["hitraythickness"] = "Thickness", ["hitrayaddedthicknessmeters"] = "AddedThicknessMeters", ["hitraystartheight"] = "StartHeight", ["hitrayoffset"] = "Offset", ["hitrayangle"] = "Angle", ["attackhitboxtype"] = "Type", ["multitargetdamagepenalty"] = "MultiTargetPenalty", ["blockarmormultiplier"] = "BlockArmor", ["blockforcemultiplier"] = "BlockForce", ["blockstaminaconsumptionrate"] = "BlockStaminaUse", ["blockingstaminaregenmultiplier"] = "BlockingStaminaRegen", ["blockbreakthresholdmultiplier"] = "BlockBreakThreshold", ["parrybonusmultiplier"] = "ParryBonus", ["parryadrenalinemultiplier"] = "ParryAdrenaline", ["canstaggerrangedattackswithparrypve"] = "ParryStaggerRangedPve", ["canstaggerrangedattackswithparrypvp"] = "ParryStaggerRangedPvp", ["canstaggeraoeattackswithparrypve"] = "ParryStaggerAoePve", ["canstaggeraoeattackswithparrypvp"] = "ParryStaggerAoePvp", ["pvpblockarmormultiplier"] = "PvpBlockArmor", ["pvpblockforcemultiplier"] = "PvpBlockForce", ["pvpparrybonusmultiplier"] = "PvpParryBonus", ["pvpblockbreakthresholdmultiplier"] = "PvpBlockBreakThreshold", ["soulslikeblockbreak"] = "SoulsLikeBlockBreak", ["soulslikeblockstaminamode"] = "SoulsLikeBlockStaminaMode", ["soulslikeblockstaminabaserate"] = "SoulsLikeBlockStaminaBaseRate", ["soulslikeblockstaminaconsumptionrate"] = "SoulsLikeBlockStaminaBaseRate", ["blockingmovementspeedmultiplier"] = "BlockingMovementSpeed", ["enableRunningwhileblocking".ToLowerInvariant()] = "RunWhileBlocking", ["runningwhileblockingmovementspeedmultiplier"] = "RunWhileBlockingSpeed", ["usecustommovementmodifier"] = "UseCustomMovementModifier", ["movementmodifierpercent"] = "MovementModifierPercent", ["usecustomtoolmovementmodifier"] = "UseCustomToolMovementModifier", ["toolmovementmodifierpercent"] = "ToolMovementModifierPercent", ["usecustomotherequipmentmovementmodifier"] = "UseCustomOtherEquipmentMovementModifier", ["otherequipmentmovementmodifierpercent"] = "OtherEquipmentMovementModifierPercent", ["durabilityconsumptionmultiplier"] = "DurabilityUse", ["basedurabilitymultiplier"] = "BaseDurability", ["durabilitymultiplier"] = "Durability", ["globaldurabilityconsumptionmultiplier"] = "GlobalDurabilityUse", ["globalbasedurabilitymultiplier"] = "GlobalBaseDurability", ["globaldurabilitymultiplier"] = "GlobalDurability", ["globalunbreakablegear"] = "GlobalUnbreakableGear", ["unbreakable"] = "Unbreakable", ["goocombatdebugmode"] = "CombatDebugMode", ["debuglogging"] = "GeneralDebugLogging", ["verbosedebuglogging"] = "VerboseDebugLogging", ["debugsledgealternatemoveset"] = "DebugSledgeAlternateMoveset", ["enabledebugflytoggle"] = "EnableDebugFlyToggle", ["enablenoplacementcosttoggle"] = "EnableDebugNoCostToggle", ["enableghostmodetoggle"] = "EnableDebugGhostToggle", ["toggledebugfly"] = "DebugFly", ["toggledebugflykeybind"] = "DebugFlyKey", ["togglenoplacementcost"] = "DebugNoCost", ["togglenoplacementcostkeybind"] = "DebugNoCostKey", ["toggleghostmode"] = "DebugGhost", ["toggleghostmodekeybind"] = "DebugGhostKey", ["debugmodeheal"] = "DebugHeal", ["debugmodehealkeybind"] = "DebugHealKey", ["debugmodeaddrested"] = "DebugAddRested", ["debugmodeaddrestedkeybind"] = "DebugAddRestedKey", ["spawnpvptestdummy"] = "Spawn", ["removepvptestdummies"] = "Remove", ["health"] = "Health", ["armor"] = "Armor" }; if (dictionary.TryGetValue(text, out var value)) { return value; } string[] array = new string[7] { "multiplier", "seconds", "percent", "enabled", "mode", "rate", "value" }; foreach (string text2 in array) { if (text.EndsWith(text2, StringComparison.Ordinal) && text.Length > text2.Length) { string text3 = text.Substring(0, text.Length - text2.Length); if (dictionary.TryGetValue(text3, out var value2)) { return value2; } text = text3; break; } } return ToPascalToken(text); } private static string ReadableToken(string value) { string text = NormalizeCommandKey(value); if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } return ToPascalToken(text); } private static string ToPascalToken(string normalized) { if (string.IsNullOrWhiteSpace(normalized)) { return string.Empty; } if (normalized.Length == 1) { return normalized.ToUpperInvariant(); } return char.ToUpperInvariant(normalized[0]) + normalized.Substring(1); } private static bool TryStripAttackSlotPrefix(string normalizedKey, out string slot, out string remainder) { (string, string)[] array = new(string, string)[18] { ("moddedprimary1", "Primary1"), ("primary1", "Primary1"), ("moddedprimary2", "Primary2"), ("primary2", "Primary2"), ("moddedprimary3", "Primary3"), ("primary3", "Primary3"), ("moddedprimary4", "Primary4"), ("primary4", "Primary4"), ("moddedsecondary", "Secondary"), ("secondary", "Secondary"), ("moddedrunningattack", "Running"), ("runningattack", "Running"), ("moddedrunning", "Running"), ("running", "Running"), ("moddedjumpattack", "Jump"), ("jumpattack", "Jump"), ("moddedjump", "Jump"), ("jump", "Jump") }; for (int i = 0; i < array.Length; i++) { var (text, text2) = array[i]; if (normalizedKey.StartsWith(text, StringComparison.Ordinal)) { slot = text2; remainder = normalizedKey.Substring(text.Length); return true; } } slot = string.Empty; remainder = normalizedKey; return false; } private static string PrimaryCommandAliasForWeapon(string canonical) { return NormalizeCommandKey(canonical) switch { "twohandedswords" => "2HSwords", "twohandedaxes" => "2HAxes", "onehandedswords" => "1HSwords", "onehandedaxes" => "1HAxes", "clubsandmaces" => "Maces", "dualaxes" => "DualAxes", "dualknives" => "DualKnives", "knives" => "Knives", "crossbows" => "Crossbows", "pickaxes" => "Pickaxes", "sledges" => "Sledges", "atgeirs" => "Atgeirs", "spears" => "Spears", "bows" => "Bows", "unarmed" => "Unarmed", "other" => "Other", _ => ReadableToken(canonical), }; } private static bool TryGetWeaponCommandName(string head, out string weaponCommandName) { string headNorm = NormalizeCommandKey(head); foreach (var item3 in CommandWeaponAliases()) { string item = item3.Canonical; string[] item2 = item3.Aliases; string text = NormalizeCommandKey(item); if (headNorm == text || headNorm.Contains(text) || item2.Any((string alias) => headNorm == NormalizeCommandKey(alias))) { weaponCommandName = PrimaryCommandAliasForWeapon(item); return true; } } weaponCommandName = string.Empty; return false; } private static bool TryGetShieldCommandName(string head, out string shieldCommandName) { string text = NormalizeCommandKey(head); if (text.Contains("buckler")) { shieldCommandName = "Bucklers"; return true; } if (text.Contains("tower") && text.Contains("shield")) { shieldCommandName = "TowerShields"; return true; } if ((text.Contains("medium") || text.Contains("round")) && text.Contains("shield")) { shieldCommandName = "MediumShields"; return true; } shieldCommandName = string.Empty; return false; } private static string MagicSourceToken(string tail) { return NormalizeCommandKey(tail) switch { "staffofthewild" => "GreenRoots", "deadraiser" => "Skeleton", "staffoffrost" => "IceShards", "dundr" => "Dundr", "staffoffireball" => "Fireball", _ => ReadableToken(tail), }; } private static string PlayerFeatureTokenFromKeyHint(string normalizedKey) { if (string.IsNullOrWhiteSpace(normalizedKey)) { return "general"; } if (normalizedKey.Contains("damage")) { return "damage"; } if (normalizedKey.Contains("stamina") || normalizedKey.Contains("eitr") || normalizedKey.Contains("health") || normalizedKey.Contains("healing") || normalizedKey.Contains("carry")) { return "resources"; } if (normalizedKey.Contains("speed") || normalizedKey.Contains("jump") || normalizedKey.Contains("fall") || normalizedKey.Contains("roll") || normalizedKey.Contains("rotation") || normalizedKey.Contains("size")) { return "movement"; } return "general"; } private static string EnemyFeatureTokenFromKeyHint(string normalizedKey) { if (string.IsNullOrWhiteSpace(normalizedKey)) { return "general"; } if (normalizedKey.Contains("damage")) { return "damage"; } if (normalizedKey.Contains("movement") || normalizedKey.Contains("speed") || normalizedKey.Contains("rotation")) { return "movement"; } if (normalizedKey.Contains("size")) { return "size"; } if (normalizedKey.Contains("stagger") || normalizedKey.Contains("poise")) { return "stagger"; } return "general"; } private static string NormalizeCommandKey(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString().Replace("weapons", "weapon").Replace("multipliers", "multiplier") .Replace("twahanded", "twohanded"); } private static bool IsRunnableCommandEntry(ConfigEntryBase entry) { if (entry == null || entry.SettingType != typeof(bool)) { return false; } string text = NormalizeCommandKey(entry.Definition.Key); switch (text) { default: return text == "debugmodeaddrested"; case "settogooruleset": case "resettovanilla": case "spawnpvptestdummy": case "removepvptestdummies": case "toggledebugfly": case "togglenoplacementcost": case "toggleghostmode": case "debugmodeheal": return true; } } private static IEnumerable GetExpectedRunnableCommandEntries() { return ((IEnumerable)(object)new ConfigEntryBase[9] { (ConfigEntryBase)SetToGoosRuleset, (ConfigEntryBase)SetToVanillaRuleset, (ConfigEntryBase)SpawnPvpTestDummy, (ConfigEntryBase)RemovePvpTestDummies, (ConfigEntryBase)ToggleDebugFly, (ConfigEntryBase)ToggleNoPlacementCost, (ConfigEntryBase)ToggleGhostMode, (ConfigEntryBase)DebugModeHeal, (ConfigEntryBase)DebugModeAddRested }).Where((ConfigEntryBase entry) => entry != null).Cast(); } private static string StripCommonCommandSuffixes(string key) { string[] array = new string[6] { "multiplier", "value", "mode", "enabled", "seconds", "rate" }; foreach (string text in array) { if (key.EndsWith(text, StringComparison.Ordinal)) { key = key.Substring(0, key.Length - text.Length); } } return key; } private static IEnumerable<(string Canonical, string[] Aliases)> CommandWeaponAliases() { yield return (Canonical: "Two-Handed Swords", Aliases: new string[5] { "2HSwords", "thsword", "2hsword", "twohandedsword", "twohandedswords" }); yield return (Canonical: "Two-Handed Axes", Aliases: new string[5] { "2HAxes", "thaxe", "2haxe", "twohandedaxe", "twohandedaxes" }); yield return (Canonical: "Sledges", Aliases: new string[2] { "Sledges", "sledge" }); yield return (Canonical: "Pickaxes", Aliases: new string[2] { "Pickaxes", "pickaxe" }); yield return (Canonical: "Dual Axes", Aliases: new string[2] { "DualAxes", "dualaxe" }); yield return (Canonical: "Atgeirs", Aliases: new string[2] { "Atgeirs", "atgeir" }); yield return (Canonical: "One-Handed Swords", Aliases: new string[5] { "1HSwords", "1hsword", "ohsword", "onehandedsword", "onehandedswords" }); yield return (Canonical: "One-Handed Axes", Aliases: new string[5] { "1HAxes", "1haxe", "ohaxe", "onehandedaxe", "onehandedaxes" }); yield return (Canonical: "Clubs and Maces", Aliases: new string[4] { "Maces", "mace", "club", "clubs" }); yield return (Canonical: "Knives", Aliases: new string[3] { "Knives", "knife", "dagger" }); yield return (Canonical: "Dual Knives", Aliases: new string[3] { "DualKnives", "dualknife", "dualblade" }); yield return (Canonical: "Spears", Aliases: new string[2] { "Spears", "spear" }); yield return (Canonical: "Bows", Aliases: new string[2] { "Bows", "bow" }); yield return (Canonical: "Crossbows", Aliases: new string[2] { "Crossbows", "crossbow" }); yield return (Canonical: "Unarmed", Aliases: new string[2] { "Unarmed", "fist" }); yield return (Canonical: "Other", Aliases: new string[3] { "Other", "tools", "tool" }); } private static void RegisterGcoFlatConsoleCommands() { //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Expected O, but got Unknown //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Expected O, but got Unknown //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Expected O, but got Unknown //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Expected O, but got Unknown //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Expected O, but got Unknown //IL_0277: Unknown result type (might be due to invalid IL or missing references) if (_gcoFlatConsoleCommandsRegistered) { return; } try { string value2; foreach (string item in GcoFlatCommandOptions.Keys.OrderBy((string key) => (!GcoFlatBranchDisplayNames.TryGetValue(key, out value2)) ? key : value2, StringComparer.OrdinalIgnoreCase).ToList()) { if (!GcoFlatBranchDisplayNames.TryGetValue(item, out string value) || string.IsNullOrWhiteSpace(value)) { continue; } string localBranchKey = item; string text = value; ConsoleOptionsFetcher val = (ConsoleOptionsFetcher)(() => GetGcoFlatFeatureOptions(localBranchKey)); new ConsoleCommand(FlatGcoCommandName("list", text), "List GCO settings for " + text + ". Usage: " + FlatGcoCommandName("list", text) + " [Feature]", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatListCommand(localBranchKey, args); }, false, false, false, false, false, val, true, false, false); new ConsoleCommand(FlatGcoCommandName("get", text), "Get a GCO setting for " + text + ". Usage: " + FlatGcoCommandName("get", text) + " ", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatGetCommand(localBranchKey, args); }, false, false, false, false, false, val, true, false, false); new ConsoleCommand(FlatGcoCommandName("set", text), "Set a GCO setting for " + text + ". Usage: " + FlatGcoCommandName("set", text) + " ", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatSetCommand(localBranchKey, args); }, false, false, false, false, false, val, true, false, false); new ConsoleCommand(FlatGcoCommandName("reset", text), "Reset a GCO setting for " + text + ". Usage: " + FlatGcoCommandName("reset", text) + " ", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatResetCommand(localBranchKey, args); }, false, false, false, false, false, val, true, false, false); if (!localBranchKey.Equals("debug", StringComparison.OrdinalIgnoreCase)) { new ConsoleCommand(FlatGcoCommandName("applypreset", text), "Apply the Goo/default preset to a GCO setting for " + text + ". Usage: " + FlatGcoCommandName("applypreset", text) + " ", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatApplyPresetCommand(localBranchKey, args); }, false, false, false, false, false, val, true, false, false); } ConsoleOptionsFetcher val2 = (ConsoleOptionsFetcher)(() => GetGcoFlatRunnableOptions(localBranchKey)); new ConsoleCommand(FlatGcoCommandName("run", text), "Run a button-style GCO action for " + text + ". Usage: " + FlatGcoCommandName("run", text) + " ", (ConsoleEvent)delegate(ConsoleEventArgs args) { GcoFlatRunCommand(localBranchKey, args); }, false, false, false, false, false, val2, true, false, false); } ConsoleEvent val3 = GcoFlatApplyFullCommand; object obj = <>c.<>9__694_0; if (obj == null) { ConsoleOptionsFetcher val4 = () => new List { "goo", "vanilla" }; <>c.<>9__694_0 = val4; obj = (object)val4; } new ConsoleCommand("gco_applypreset", "Apply a full GCO preset. Usage: gco_applypreset ", val3, false, false, false, false, false, (ConsoleOptionsFetcher)obj, true, false, false); new ConsoleCommand("gco_save", "Save the GCO config file. Usage: gco_save", new ConsoleEvent(GcoFlatSaveCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); _gcoFlatConsoleCommandsRegistered = true; RefreshFlatTerminalCommandLists(); } catch (Exception ex) { _gcoFlatConsoleCommandsRegistered = false; Log.LogWarning((object)("Failed to register flat gco console commands: " + ex.Message)); } } private static void RefreshFlatTerminalCommandLists() { try { FieldInfo fieldInfo = AccessTools.Field(typeof(Terminal), "m_commandList"); if (fieldInfo == null) { return; } List list = new List(); foreach (string item in GcoFlatBranchDisplayNames.Values.Distinct(StringComparer.OrdinalIgnoreCase)) { list.Add(FlatGcoCommandName("list", item)); list.Add(FlatGcoCommandName("get", item)); list.Add(FlatGcoCommandName("set", item)); list.Add(FlatGcoCommandName("reset", item)); if (!NormalizeCommandKey(item).Equals("debug", StringComparison.OrdinalIgnoreCase)) { list.Add(FlatGcoCommandName("applypreset", item)); } list.Add(FlatGcoCommandName("run", item)); } list.Add("gco_applypreset"); list.Add("gco_save"); Terminal[] array = Object.FindObjectsOfType(); foreach (Terminal obj in array) { if (!(fieldInfo.GetValue(obj) is List list2)) { continue; } bool flag = false; foreach (string item2 in list) { if (!list2.Contains(item2)) { list2.Add(item2); flag = true; } } if (flag) { list2.Sort(StringComparer.OrdinalIgnoreCase); } } } catch (Exception ex) { Log.LogDebug((object)("Failed to refresh terminal command lists for GCO flat commands: " + ex.Message)); } } private static List GetGcoFlatFeatureOptions(string branchKey) { if (!GcoFlatCommandOptions.TryGetValue(branchKey, out SortedSet value)) { return new List(); } return value.ToList(); } private static List GetGcoFlatRunnableOptions(string branchKey) { if (!GcoFlatCommandOptions.TryGetValue(branchKey, out SortedSet value)) { return new List(); } ConfigEntryBase entry; return value.Where((string option) => TryResolveGcoFlatEntry(branchKey, option, out entry) && entry != null && IsRunnableCommandEntry(entry)).ToList(); } private static bool TryResolveGcoFlatEntry(string branchKey, string featureToken, out ConfigEntryBase? entry) { entry = null; if (string.IsNullOrWhiteSpace(featureToken) || !GcoFlatCommandEntries.TryGetValue(branchKey, out Dictionary value)) { return false; } string key = NormalizeCommandKey(featureToken); if (value.TryGetValue(key, out entry)) { return true; } string stripped = StripCommonCommandSuffixes(key); List> list = (from pair in value where !string.IsNullOrWhiteSpace(stripped) && (pair.Key.Contains(stripped) || stripped.Contains(pair.Key)) group pair by pair.Value into @group select @group.First()).Take(3).ToList(); if (list.Count == 1) { entry = list[0].Value; return true; } return false; } private static string FlatGcoUsagePrefix(string branchKey, string action) { string value; string branchDisplay = (GcoFlatBranchDisplayNames.TryGetValue(branchKey, out value) ? value : branchKey); return FlatGcoCommandName(action, branchDisplay); } private static void GcoFlatListCommand(string branchKey, ConsoleEventArgs args) { string value; string text = (GcoFlatBranchDisplayNames.TryGetValue(branchKey, out value) ? value : branchKey); if (args.Length < 2) { args.Context.AddString("GCO " + text + " feature keys:"); { foreach (string gcoFlatFeatureOption in GetGcoFlatFeatureOptions(branchKey)) { args.Context.AddString(" " + gcoFlatFeatureOption); } return; } } string value2 = NormalizeCommandKey(args[1]); int num = 0; foreach (string gcoFlatFeatureOption2 in GetGcoFlatFeatureOptions(branchKey)) { if (NormalizeCommandKey(gcoFlatFeatureOption2).Contains(value2) && TryResolveGcoFlatEntry(branchKey, gcoFlatFeatureOption2, out ConfigEntryBase entry) && entry != null) { args.Context.AddString(" " + gcoFlatFeatureOption2 + " = " + entry.GetSerializedValue() + " [" + entry.SettingType.Name + "]"); num++; } } if (num == 0) { args.Context.AddString("No GCO " + text + " features matched: " + args[1]); } } private static void GcoFlatGetCommand(string branchKey, ConsoleEventArgs args) { if (args.Length < 2 || !TryResolveGcoFlatEntry(branchKey, args[1], out ConfigEntryBase entry) || entry == null) { args.Context.AddString("Usage: " + FlatGcoUsagePrefix(branchKey, "get") + " "); return; } args.Context.AddString(args[1] + " = " + entry.GetSerializedValue() + " [" + GetPrimaryGcoCommandPath(entry) + "]"); } private static void GcoFlatSetCommand(string branchKey, ConsoleEventArgs args) { if (args.Length < 3 || !TryResolveGcoFlatEntry(branchKey, args[1], out ConfigEntryBase entry) || entry == null) { args.Context.AddString("Usage: " + FlatGcoUsagePrefix(branchKey, "set") + " "); } else { if (RejectGuestRestrictedDebugCommand(entry, args)) { return; } string serializedValue = string.Join(" ", args.Args.Skip(2)); try { _suppressGcoChatBroadcast = true; try { entry.SetSerializedValue(serializedValue); ((BaseUnityPlugin)Instance).Config.Save(); } finally { _suppressGcoChatBroadcast = false; } BroadcastGcoConfigChange(entry); args.Context.AddString("Set " + args[1] + " = " + entry.GetSerializedValue()); } catch (Exception ex) { args.Context.AddString("Could not set value: " + ex.Message); } } } private static void GcoFlatResetCommand(string branchKey, ConsoleEventArgs args) { if (args.Length < 2 || !TryResolveGcoFlatEntry(branchKey, args[1], out ConfigEntryBase entry) || entry == null) { args.Context.AddString("Usage: " + FlatGcoUsagePrefix(branchKey, "reset") + " "); return; } try { object obj = ((object)entry).GetType().GetProperty("DefaultValue")?.GetValue(entry); if (obj == null) { args.Context.AddString("Could not read default value for this entry."); return; } _suppressGcoChatBroadcast = true; try { entry.BoxedValue = obj; ((BaseUnityPlugin)Instance).Config.Save(); } finally { _suppressGcoChatBroadcast = false; } BroadcastGcoConfigChange(entry); args.Context.AddString("Reset " + args[1] + " = " + entry.GetSerializedValue()); } catch (Exception ex) { args.Context.AddString("Could not reset value: " + ex.Message); } } private static void GcoFlatApplyPresetCommand(string branchKey, ConsoleEventArgs args) { if (args.Length < 2 || !TryResolveGcoFlatEntry(branchKey, args[1], out ConfigEntryBase entry) || entry == null) { args.Context.AddString("Usage: " + FlatGcoUsagePrefix(branchKey, "applypreset") + " "); } else { ApplyGcoEntryDefault(args, entry, "Applied Goo/default preset to "); } } private static void GcoFlatRunCommand(string branchKey, ConsoleEventArgs args) { if (args.Length < 2 || !TryResolveGcoFlatEntry(branchKey, args[1], out ConfigEntryBase entry) || entry == null) { args.Context.AddString("Usage: " + FlatGcoUsagePrefix(branchKey, "run") + " "); } else if (!IsRunnableCommandEntry(entry) || entry.SettingType != typeof(bool)) { args.Context.AddString("That feature is a value, not a button-style action. Use " + FlatGcoUsagePrefix(branchKey, "set") + " instead."); } else if (!RejectGuestRestrictedDebugCommand(entry, args)) { try { entry.BoxedValue = true; ((BaseUnityPlugin)Instance).Config.Save(); BroadcastGcoConfigChange(entry); args.Context.AddString("Ran " + args[1]); } catch (Exception ex) { args.Context.AddString("Could not run action: " + ex.Message); } } } private static void GcoFlatApplyFullCommand(ConsoleEventArgs args) { if (args.Length < 2) { args.Context.AddString("Usage: gco_applypreset "); return; } bool flag; switch (args[1].Trim().ToLowerInvariant()) { case "goo": case "goos": case "gooruleset": flag = true; break; case "vanilla": case "reset": case "noop": case "no-op": flag = false; break; default: args.Context.AddString("Unknown preset '" + args[1] + "'. Use: goo or vanilla."); return; } _suppressGcoChatBroadcast = true; BeginConfigRuntimeRefreshBatch(); try { if (flag) { SetToGooRulesetValues(); } else { ApplyVanillaPresetValues(); } ((BaseUnityPlugin)Instance).Config.Save(); } finally { _suppressGcoChatBroadcast = false; EndConfigRuntimeRefreshBatch(); FlushPendingHostConfigSync(force: true); } BroadcastGcoChatMessage(flag ? "Applied Goo's preset " : "Applied vanilla/no-op preset "); args.Context.AddString(flag ? "Applied Goo ruleset." : "Applied vanilla/no-op ruleset."); } private static void GcoFlatSaveCommand(ConsoleEventArgs args) { ((BaseUnityPlugin)Instance).Config.Save(); args.Context.AddString("Saved GCO config."); } private static void ApplyGcoEntryDefault(ConsoleEventArgs args, ConfigEntryBase entry, string messagePrefix) { try { object obj = ((object)entry).GetType().GetProperty("DefaultValue")?.GetValue(entry); if (obj == null) { args.Context.AddString("Could not read default value for this entry."); return; } _suppressGcoChatBroadcast = true; try { entry.BoxedValue = obj; ((BaseUnityPlugin)Instance).Config.Save(); } finally { _suppressGcoChatBroadcast = false; } BroadcastGcoConfigChange(entry); args.Context.AddString(messagePrefix + GetPrimaryGcoCommandPath(entry) + " = " + entry.GetSerializedValue()); } catch (Exception ex) { args.Context.AddString("Could not apply default: " + ex.Message); } } private static string GetPrimaryGcoCommandPath(ConfigEntryBase entry) { if (entry != null && GcoCommandPrimaryPaths.TryGetValue(entry, out string value) && !string.IsNullOrWhiteSpace(value)) { return value; } return BuildCompactCommandPath(entry.Definition.Section, entry.Definition.Key); } private bool HandleGcoConfigSettingChanged(SettingChangedEventArgs eventArgs) { if (_applyingGuestDebugAuthorityRestriction || eventArgs == null) { return false; } ConfigEntryBase changedSetting = eventArgs.ChangedSetting; if (changedSetting == null) { return false; } ZNet instance = ZNet.instance; if (_clientMultiplayerConfigSyncActive && (Object)(object)instance != (Object)null && !instance.IsServer() && IsMultiplayerSyncEntry(changedSetting)) { try { Log.LogDebug((object)("Stored client-local GCO edit while host configuration is active: " + changedSetting.Definition.Section + " / " + changedSetting.Definition.Key)); } catch { } return true; } if (ProcessGeneralPresetToggles()) { return true; } QueueMultiplayerConfigBroadcast(changedSetting); if (_suppressGcoChatBroadcast || ShouldSkipGcoConfigChangeBroadcast(changedSetting)) { return false; } BroadcastGcoConfigChange(changedSetting); return false; } private static bool ShouldSkipGcoConfigChangeBroadcast(ConfigEntryBase entry) { if (entry == null) { return true; } string obj = entry.Definition.Section ?? string.Empty; string text = entry.Definition.Key ?? string.Empty; if (obj.Equals("General", StringComparison.OrdinalIgnoreCase) && text.Equals("Config Version", StringComparison.OrdinalIgnoreCase)) { return true; } if ((object)entry == ConfigVersion || (object)entry == SetToGoosRuleset || (object)entry == SetToVanillaRuleset) { return true; } if ((object)entry == ToggleNoPlacementCost || (object)entry == ToggleDebugFly || (object)entry == ToggleGhostMode || (object)entry == DebugModeHeal || (object)entry == DebugModeAddRested) { return true; } return false; } private static void BroadcastGcoConfigChange(ConfigEntryBase entry) { try { string text = FormatGcoConfigChangeLabel(entry); string text2 = FormatGcoConfigValue(entry); object boxedValue = entry.BoxedValue; bool force = (object)entry == BroadcastConfigChanges && boxedValue is bool flag && !flag; if (boxedValue is bool) { BroadcastGcoChatMessage((((bool)boxedValue) ? "Enabled " : "Disabled ") + text + " ", force); return; } BroadcastGcoChatMessage("Set " + text + " to " + text2 + " ", force); } catch (Exception ex) { try { Log.LogDebug((object)("Failed to broadcast GCO config change: " + ex.Message)); } catch { } } finally { FlushPendingHostConfigSync(); } } private static string FormatGcoConfigChangeLabel(ConfigEntryBase entry) { string text = entry.Definition.Section ?? string.Empty; string text2 = entry.Definition.Key ?? string.Empty; string primaryGcoCommandPath = GetPrimaryGcoCommandPath(entry); if (!string.IsNullOrWhiteSpace(primaryGcoCommandPath)) { return ToFriendlyGcoCommandPath(primaryGcoCommandPath); } return (text + " " + text2).Replace(" - ", " ").Trim(); } private static string ToFriendlyGcoCommandPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return string.Empty; } string[] array = path.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); List list = new List(); string[] array2 = array; foreach (string text in array2) { string text2 = NormalizeCommandKey(text); List list2 = list; list2.Add(text2 switch { "weapon" => string.Empty, "shield" => "Shield", "resource" => "Resources", "resources" => "Resources", "debug" => "Debug", "general" => "General", "player" => "Player", "magic" => "Magic", "staff" => "Staff", "thsword" => "2H Swords", "thaxe" => "2H Axes", "sledge" => "Sledges", "pickaxe" => "Pickaxes", "dualaxe" => "Dual Axes", "atgeir" => "Atgeirs", "ohsword" => "1H Swords", "ohaxe" => "1H Axes", "mace" => "Clubs/Maces", "club" => "Clubs/Maces", "knife" => "Knives", "dualknife" => "Dual Knives", "spear" => "Spears", "bow" => "Bows", "crossbow" => "Crossbows", "unarmed" => "Unarmed", "other" => "Other", "p1" => "P1", "p2" => "P2", "p3" => "P3", "p4" => "P4", "secondary" => "Secondary", "running" => "Running", "jump" => "Jump", "attackspeed" => "Attack Animation Speed", "recoveryspeed" => "Recovery Animation Speed", "damage" => "Damage", "stagger" => "Stagger", "movement" => "Movement", "lunge" => "Lunge", "hitstop" => "Hit-Stop", "blockcancel" => "Block Cancel", "destruction" => "Destruction", "damagetobuildings" => "Damage to Buildings", "durability" => "Durability", "basedurability" => "Base Durability", "unbreakable" => "Unbreakable", "globaldurability" => "Global Durability", _ => HumanizeCompactToken(text), }); } return string.Join(" ", list.Where((string x) => !string.IsNullOrWhiteSpace(x))).Trim(); } private static string HumanizeCompactToken(string token) { if (string.IsNullOrWhiteSpace(token)) { return string.Empty; } string input = Regex.Replace(token.Trim(), "(?<=[a-z])(?=[A-Z])", " "); input = Regex.Replace(input, "(?<=[A-Za-z])(?=\\d)|(?<=\\d)(?=[A-Za-z])", " "); return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(input.ToLowerInvariant()).Replace("Pvp", "PvP").Replace("Pve", "PvE") .Replace("Aoe", "AOE"); } private static string FormatGcoConfigValue(ConfigEntryBase entry) { object boxedValue = entry.BoxedValue; string text = entry.Definition.Key ?? string.Empty; bool flag = text.IndexOf("Multiplier", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Rate", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Speed", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Damage", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Stagger", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Knockback", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Lunge", StringComparison.OrdinalIgnoreCase) >= 0; if (boxedValue is float num) { if (!flag) { return num.ToString("0.###", CultureInfo.InvariantCulture); } return num.ToString("0.###", CultureInfo.InvariantCulture) + "x"; } if (boxedValue is double num2) { if (!flag) { return num2.ToString("0.###", CultureInfo.InvariantCulture); } return num2.ToString("0.###", CultureInfo.InvariantCulture) + "x"; } return entry.GetSerializedValue(); } private static void BroadcastGcoChatMessage(string message, bool force = false) { if (string.IsNullOrWhiteSpace(message)) { return; } string text = NormalizeGcoBroadcastMessage(message); if (!force && (BroadcastConfigChanges == null || !BroadcastConfigChanges.Value)) { try { Log.LogDebug((object)("[GCO broadcast suppressed] " + text)); return; } catch { return; } } try { Log.LogInfo((object)((force ? "[GCO forced shout] " : "[GCO shout] ") + text)); } catch { } try { if ((Object)(object)Chat.instance == (Object)null) { try { Log.LogWarning((object)("Could not shout GCO config disclosure because Chat.instance is not available yet: " + text)); return; } catch { return; } } Chat.instance.SendText((Type)2, text); } catch (Exception ex) { try { Log.LogWarning((object)("Failed to shout GCO config disclosure through Chat.SendText: " + ex.Message)); } catch { } } } private static string NormalizeGcoBroadcastMessage(string message) { string input = message.Trim(); input = Regex.Replace(input, "\\s*(|\\[GCO\\])\\s*", " ", RegexOptions.IgnoreCase).Trim(); return "[GCO] " + input; } private bool ProcessGeneralPresetToggles() { if (_processingGlobalRulesetButton || SetToGoosRuleset == null || SetToVanillaRuleset == null) { return false; } if (!SetToGoosRuleset.Value && !SetToVanillaRuleset.Value) { return false; } _processingGlobalRulesetButton = true; _suppressGcoChatBroadcast = true; BeginConfigRuntimeRefreshBatch(); try { if (SetToVanillaRuleset.Value) { ApplyVanillaPresetValues(); SetToVanillaRuleset.Value = false; SetToGoosRuleset.Value = false; ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Reset Goo's Combat Overhaul combat/balance config entries to vanilla/no-op Valheim values."); BroadcastGcoChatMessage("Applied vanilla/no-op preset "); return true; } if (SetToGoosRuleset.Value) { SetToGooRulesetValues(); SetToGoosRuleset.Value = false; ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Set config to Goo's ruleset. SetToGooRuleset was reset to false; edit individual entries freely."); BroadcastGcoChatMessage("Applied Goo's preset "); return true; } } finally { _suppressGcoChatBroadcast = false; _processingGlobalRulesetButton = false; EndConfigRuntimeRefreshBatch(); FlushPendingHostConfigSync(force: true); } return false; } private int RemoveRemovedHitStopEnableConfigEntries() { int num = 0; (string, string, string)[] weaponPresetSections = WeaponPresetSections; for (int i = 0; i < weaponPresetSections.Length; i++) { (string, string, string) tuple = weaponPresetSections[i]; num += RemoveScopedPresetEntryVariants(tuple.Item1, "Primary1EnableHitStopOnHit"); num += RemoveScopedPresetEntryVariants(tuple.Item1, "Primary2EnableHitStopOnHit"); num += RemoveScopedPresetEntryVariants(tuple.Item1, "Primary3EnableHitStopOnHit"); num += RemoveScopedPresetEntryVariants(tuple.Item1, "Primary4EnableHitStopOnHit"); num += RemoveScopedPresetEntryVariants(tuple.Item1, "SecondaryEnableHitStopOnHit"); } return num; } private void ProcessConfigVersionOverwrite() { if (ConfigVersion == null) { return; } if (!_startupConfigVersionDecisionInitialized) { EvaluateStartupConfigVersionDecision(); } if (!_startupConfigVersionNeedsBump) { return; } if (!_startupHadExistingConfigFile) { SetToGoosRuleset.Value = false; SetToVanillaRuleset.Value = false; ConfigVersion.Value = "1.4.0"; ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Created fresh config version 1.4.0 with Goo's Ruleset defaults."); return; } string value = ConfigVersion.Value; int num = RemoveRenamedBlockConfigEntries(); num += RemoveCompactConfigEntriesReplacedBySpacedNames(); num += RemoveGeneralFeatureScopedPresetEntries(); num += RemoveRemovedHitStopEnableConfigEntries(); num += RemoveDeprecatedGlobalPresetEntries(); num += RemoveUnsupportedPrimary4ConfigEntries(); num += RemoveOrphanedConfigEntry("Debug", "DebugModeSetSpawnPoint"); num += RemoveOrphanedConfigEntry("Debug", "DebugModeSetSpawnPointKeybind"); num += RemoveScopedPresetEntryVariants("Debug", "PvpTestDummyFullHealIntervalSeconds"); num += RemoveScopedPresetEntryVariants("Spears - Animation", "UseAtgeirMoveset"); num += RemoveOrphanedConfigSection("Sledges - Running Attack"); string[] array = new string[12] { "Primary1DirectHitRayStartHeightMeters", "Primary1DirectHitRayRangeMeters", "Primary1DirectHitRayAngleDegrees", "Primary1DirectHitRayThicknessMeters", "Primary2DirectHitRayStartHeightMeters", "Primary2DirectHitRayRangeMeters", "Primary2DirectHitRayAngleDegrees", "Primary2DirectHitRayThicknessMeters", "RunningDirectHitRayStartHeightMeters", "RunningDirectHitRayRangeMeters", "RunningDirectHitRayAngleDegrees", "RunningDirectHitRayThicknessMeters" }; foreach (string compactKey in array) { num += RemoveScopedPresetEntryVariants("Sledges - Hit Ray", compactKey); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Config version '" + value + "' is older than 1.4.0; preserving compatible existing values and applying Reset to Vanilla values only to newly added or failed-to-inherit entries.")); ApplyVanillaFallbackThenRestoreInheritedValues(); if (SyncConfigInMultiplayer != null) { SyncConfigInMultiplayer.Value = true; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Enabled Sync Config in Multiplayer for the migrated older-version config."); SetToGoosRuleset.Value = false; SetToVanillaRuleset.Value = false; ConfigVersion.Value = "1.4.0"; ((BaseUnityPlugin)this).Config.Save(); if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Removed {0} stale or renamed config entr{1} during version migration.", num, (num == 1) ? "y" : "ies")); } } private int RemoveGeneralFeatureScopedPresetEntries() { int num = 0; string[] array = new string[16] { "FeatureDamage", "FeatureHyperArmor", "FeatureCounterDamage", "FeatureHitStop", "FeatureAttackMovementAndLunge", "FeatureAttackRotation", "FeatureHitRayAndAINoise", "FeatureHitRay", "FeatureAINoise", "FeatureStealthAttributes", "FeatureMultiTargetPenalty", "FeaturePvp", "FeaturePlayerStats", "FeatureResources", "FeatureBlockingAndParry", "FeatureStaffSpawnedEntities" }; foreach (string text in array) { num += RemoveScopedPresetEntryVariants("General", "ApplyGooRulesetTo" + text); num += RemoveScopedPresetEntryVariants("General", "Reset" + text + "ToVanilla"); } (string, string)[] array2 = new(string, string)[16] { ("Damage", "Damage"), ("Hyperarmor", "Hyperarmor"), ("Counter Damage", "CounterDamage"), ("Hit-Stop / Hitlag", "HitStop"), ("Attack movement and lunge", "AttackMovementAndLunge"), ("Attack rotation", "AttackRotation"), ("Hit ray and AI noise", "HitRayAndAINoise"), ("Hit Ray", "HitRay"), ("Stealth Attributes", "StealthAttributes"), ("Stealth", "Stealth"), ("Multi target penalty", "MultiTargetPenalty"), ("PvP Damage", "Pvp"), ("Player", "PlayerStats"), ("Resources, stamina, eitr, and adrenaline", "Resources"), ("Blocking and Parry", "BlockingAndParry"), ("Staff spawned entities", "StaffSpawnedEntities") }; for (int i = 0; i < array2.Length; i++) { (string, string) tuple = array2[i]; string item = tuple.Item1; string item2 = tuple.Item2; num += RemoveScopedPresetEntryVariants(item, "ApplyGooRulesetTo" + item2); num += RemoveScopedPresetEntryVariants(item, "Reset" + item2 + "ToVanilla"); (string, string, string)[] weaponPresetSections = WeaponPresetSections; for (int j = 0; j < weaponPresetSections.Length; j++) { (string, string, string) tuple2 = weaponPresetSections[j]; num += RemoveScopedPresetEntryVariants(item, "ApplyGooRulesetTo" + item2 + "For" + tuple2.Item3); num += RemoveScopedPresetEntryVariants(item, "Reset" + item2 + "For" + tuple2.Item3 + "ToVanilla"); } } return num; } private int RemoveDeprecatedGlobalPresetEntries() { int num = 0; num += RemoveScopedPresetEntryVariants("General", "ApplyGooRuleset"); num += RemoveScopedPresetEntryVariants("General", "ApplyGooRulesetToWeaponCategories"); num += RemoveScopedPresetEntryVariants("General", "ResetWeaponCategoriesToVanilla"); num += RemoveScopedPresetEntryVariants("General", "SetToVanillaRuleset"); num += RemoveScopedPresetEntryVariants("General", "AffectPlayersOnlyForOutgoingStagger"); num += RemoveScopedPresetEntryVariants("Player", "DoMaxDamage"); (WeaponCategory, string, string)[] featureToggleWeapons = FeatureToggleWeapons; for (int i = 0; i < featureToggleWeapons.Length; i++) { (WeaponCategory, string, string) tuple = featureToggleWeapons[i]; num += RemoveScopedPresetEntryVariants("General", "ApplyModFor" + tuple.Item3); num += RemoveScopedPresetEntryVariants("Hit ray and AI noise", "ApplyModHitRayAndAINoiseFor" + tuple.Item3); } num += RemoveScopedPresetEntryVariants("Hit ray and AI noise", "ApplyModHitRayAndAINoise"); num += RemoveOrphanedConfigSection("Hit ray and AI noise"); num += RemoveOrphanedConfigSection("Pickaxes - Hit Ray"); return num + RemoveOrphanedConfigSection("Pickaxes - Multi Target Penalty"); } private int RemoveScopedPresetEntryVariants(string section, string compactKey) { return 0 + RemoveOrphanedConfigEntry(section, compactKey) + RemoveOrphanedConfigEntry(section, PrettyConfigKey(compactKey)) + RemoveOrphanedConfigEntry(section, LegacyTitleConfigKey(compactKey)); } private int RemoveRenamedBlockConfigEntries() { int num = 0; string[] array = new string[16] { "Two-Handed Swords", "Two-Handed Axes", "Sledges", "Pickaxes", "Dual Axes", "Atgeirs", "One-Handed Swords", "One-Handed Axes", "Clubs and Maces", "Knives", "Spears", "Bows", "Crossbows", "Magic", "Unarmed", "Tools and Other" }; num += RemoveOrphanedConfigEntry("PvP Damage", "PvpWeaponBlockPowerMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "PvpShieldBlockPowerMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "PvpTowerShieldBlockPowerMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "PvpWeaponBlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "PvpShieldBlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "PvpTowerShieldBlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("PvP Damage", "ClearStaggerWhenStaggered"); num += RemoveOrphanedConfigEntry("Blocking and Parry", "GlobalDamageTakenWhileBlockingMultiplier"); num += RemoveOrphanedConfigEntry("Blocking and Parry", "GlobalSoulsLikeBlockStaminaConsumptionRate"); num += RemoveOrphanedConfigEntry("Shields", "BlockPowerMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "DeflectionForceMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "TimedBlockBonusMultiplier"); num += RemoveOrphanedConfigSection("Magic"); num += RemoveOrphanedConfigEntry("Bows", "BaseAccuracyMultiplier"); num += RemoveOrphanedConfigEntry("Crossbows", "BaseAccuracyMultiplier"); string[] array2 = new string[8] { "Magic - Staff of Embers", "Magic - Staff of Frost", "Magic - Staff of Fracturing", "Magic - Staff of the Wild", "Magic - Dundr", "Magic - Staff of Protection", "Magic - Dead Raiser", "Magic - Trollstav" }; foreach (string section in array2) { num += RemoveOrphanedConfigEntry(section, "AccuracyMultiplier"); num += RemoveOrphanedConfigEntry(section, "DamageTakenWhileBlockingMultiplier"); num += RemoveOrphanedConfigEntry(section, "SoulsLikeBlockStaminaConsumptionRate"); } array2 = array; foreach (string section2 in array2) { num += RemoveOrphanedConfigEntry(section2, "BlockPowerMultiplier"); num += RemoveOrphanedConfigEntry(section2, "DeflectionForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "DamageTakenWhileBlockingMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SoulsLikeBlockStaminaConsumptionRate"); num += RemoveOrphanedConfigEntry(section2, "Primary1PvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "Primary2PvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "Primary3PvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "Primary4PvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "SecondaryPvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "Primary1PvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary2PvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary3PvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary4PvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SecondaryPvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackPvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackPvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackPvpStaggerDurationSeconds"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackPvpKnockbackForceMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary1AttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary2AttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary3AttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary4AttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SecondaryAttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary1AttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary2AttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary3AttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary4AttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SecondaryAttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary1AttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary2AttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary3AttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary4AttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SecondaryAttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary1AttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary2AttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary3AttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "Primary4AttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "SecondaryAttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "RunningAttackAngleMultiplier"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackRangeMultiplier"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackRayWidthMultiplier"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackHitboxHeightMultiplier"); num += RemoveOrphanedConfigEntry(section2, "JumpAttackAngleMultiplier"); } array2 = new string[48] { "Primary1MultiTargetDamagePenalty", "Primary2MultiTargetDamagePenalty", "Primary3MultiTargetDamagePenalty", "SecondaryMultiTargetDamagePenalty", "RunningAttackMultiTargetDamagePenalty", "JumpAttackMultiTargetDamagePenalty", "Running Attack Multi Target Damage Penalty", "Jump Attack Multi Target Damage Penalty", "Primary1HitRayRange", "Primary2HitRayRange", "Primary3HitRayRange", "Primary4HitRayRange", "SecondaryHitRayRange", "Primary1HitRayThickness", "Primary2HitRayThickness", "Primary3HitRayThickness", "Primary4HitRayThickness", "SecondaryHitRayThickness", "Primary1HitRayStartHeight", "Primary2HitRayStartHeight", "Primary3HitRayStartHeight", "Primary4HitRayStartHeight", "SecondaryHitRayStartHeight", "Primary1HitRayOffset", "Primary2HitRayOffset", "Primary3HitRayOffset", "Primary4HitRayOffset", "SecondaryHitRayOffset", "Primary1AttackHitboxType", "Primary2AttackHitboxType", "Primary3AttackHitboxType", "Primary4AttackHitboxType", "SecondaryAttackHitboxType", "Primary1HitRayAngle", "Primary2HitRayAngle", "Primary3HitRayAngle", "Primary4HitRayAngle", "SecondaryHitRayAngle", "RunningAttackHitRayRange", "RunningAttackHitRayThickness", "RunningAttackHitRayStartHeight", "RunningAttackHitRayOffset", "RunningAttackHitRayAngle", "JumpAttackHitRayRange", "JumpAttackHitRayThickness", "JumpAttackHitRayStartHeight", "JumpAttackHitRayOffset", "JumpAttackHitRayAngle" }; foreach (string key in array2) { num += RemoveOrphanedConfigEntry("Sledges", key); } num += RemoveOrphanedConfigEntry("Magic", "UseCustomMovementModifier"); num += RemoveOrphanedConfigEntry("Other", "MagicMovementModifierPercent"); num += RemoveOrphanedConfigEntry("Shields", "UseCustomShieldMovementModifier"); num += RemoveOrphanedConfigEntry("Shields", "ShieldMovementModifierPercent"); num += RemoveOrphanedConfigEntry("Shields", "BlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "BlockForceMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "ParryBonusMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "ParryAdrenalineMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "PvpBlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "TowerPvpBlockArmorMultiplier"); num += RemoveOrphanedConfigEntry("Shields", "BlockStaminaConsumptionRate"); num += RemoveOrphanedConfigEntry("Shields", "BlockBreakThresholdMultiplier"); array2 = new string[3] { "Shields - Bucklers", "Shields - Medium Shields", "Shields - Tower Shields" }; foreach (string section3 in array2) { num += RemoveOrphanedConfigEntry(section3, "DamageTakenWhileBlockingMultiplier"); num += RemoveOrphanedConfigEntry(section3, "SoulsLikeBlockStaminaConsumptionRate"); } return num + RemoveOrphanedConfigEntriesStartingWith("Unarmed", "Primary3"); } private int RemoveUnsupportedPrimary4ConfigEntries() { int num = 0; (string, string, string)[] weaponPresetSections = WeaponPresetSections; for (int i = 0; i < weaponPresetSections.Length; i++) { (string, string, string) tuple = weaponPresetSections[i]; if (!(tuple.Item1 == "Dual Axes")) { num += RemoveOrphanedConfigEntriesStartingWith(tuple.Item1, "Primary4"); num += RemoveOrphanedConfigEntriesStartingWith(tuple.Item1, "Primary 4"); } } return num; } private int RemoveCompactConfigEntriesReplacedBySpacedNames() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown try { if (!(typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary)) { return 0; } HashSet hashSet = new HashSet(((BaseUnityPlugin)this).Config.Keys); List list = new List(); foreach (object key in dictionary.Keys) { ConfigDefinition val = (ConfigDefinition)((key is ConfigDefinition) ? key : null); if (val != null) { string text = PrettyConfigKey(val.Key); if (!(text == val.Key) && hashSet.Contains(new ConfigDefinition(val.Section, text))) { list.Add(key); } } } foreach (object item in list) { dictionary.Remove(item); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Removed {0} stale compact config entr{1} after migrating to spaced config names.", list.Count, (list.Count == 1) ? "y" : "ies")); } return list.Count; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove stale compact config entries: " + ex.Message)); } return 0; } private int RemoveOrphanedConfigEntry(string section, string key) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown try { if (typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary) { ConfigDefinition key2 = new ConfigDefinition(section, key); if (dictionary.Contains(key2)) { dictionary.Remove(key2); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Removed stale config entry [" + section + "] " + key + ".")); return 1; } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove stale config entry [" + section + "] " + key + ": " + ex.Message)); } return 0; } private int RemoveOrphanedConfigSection(string section) { try { if (typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary) { List list = new List(); foreach (object key in dictionary.Keys) { ConfigDefinition val = (ConfigDefinition)((key is ConfigDefinition) ? key : null); if (val != null && val.Section == section) { list.Add(key); } } foreach (object item in list) { dictionary.Remove(item); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Removed {0} stale config entr{1} from removed section [{2}].", list.Count, (list.Count == 1) ? "y" : "ies", section)); } return list.Count; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove stale config section [" + section + "]: " + ex.Message)); } return 0; } private int RemoveOrphanedConfigEntriesStartingWith(string section, string keyPrefix) { try { if (typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary dictionary) { List list = new List(); foreach (object key in dictionary.Keys) { ConfigDefinition val = (ConfigDefinition)((key is ConfigDefinition) ? key : null); if (val != null && val.Section == section && val.Key.StartsWith(keyPrefix, StringComparison.Ordinal)) { list.Add(key); } } foreach (object item in list) { dictionary.Remove(item); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Removed {0} stale config entr{1} from [{2}] with prefix {3}.", list.Count, (list.Count == 1) ? "y" : "ies", section, keyPrefix)); } return list.Count; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove stale config entries in [" + section + "] with prefix " + keyPrefix + ": " + ex.Message)); } return 0; } private string? GetConfigFilePath() { try { if (typeof(ConfigFile).GetProperty("ConfigFilePath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is string text && !string.IsNullOrWhiteSpace(text)) { return text; } if ((typeof(ConfigFile).GetField("_configFilePath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? typeof(ConfigFile).GetField("configFilePath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(((BaseUnityPlugin)this).Config) is string text2 && !string.IsNullOrWhiteSpace(text2)) { return text2; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not resolve BepInEx config file path: " + ex.Message)); } return null; } private static bool IsVersionOlderThan(string? current, string target) { if (!Version.TryParse(current ?? string.Empty, out Version result)) { return true; } if (!Version.TryParse(target, out Version result2)) { return false; } return result < result2; } private void BindFeatureApplicationToggles(string controlSection, string featureLabel, string keySuffix, ModFeature feature, IEnumerable weaponCategories) { FeatureGlobalToggles[feature] = BindConfig(controlSection, "ApplyMod" + keySuffix, defaultValue: true, "Persistent feature toggle. If true, Goo's Combat Overhaul applies " + featureLabel + " logic globally for supported weapon categories. If false, this feature stays vanilla/no-op everywhere without deleting stored values."); foreach (WeaponCategory category in weaponCategories) { (WeaponCategory, string, string) tuple = FeatureToggleWeapons.First(((WeaponCategory Category, string Label, string KeySuffix) entry) => entry.Category == category); string key = "ApplyMod" + keySuffix + "For" + tuple.Item3; ConfigEntry value = BindConfig(controlSection, key, defaultValue: true, "Persistent feature toggle. If true, Goo's Combat Overhaul applies " + featureLabel + " logic and this weapon category's stored " + featureLabel + " settings to " + tuple.Item2 + ". If false, this feature stays vanilla/no-op for " + tuple.Item2 + " without deleting the stored per-attack values."); FeatureWeaponToggles[FeatureToggleKey(feature, category)] = value; } } private void BindStaggeringApplicationToggles() { FeatureGlobalToggles[ModFeature.Staggering] = BindConfig("Staggering", "ApplyModStaggerSystem", defaultValue: true, "Persistent feature toggle. If true, Goo's Combat Overhaul applies stagger/poise behavior globally for supported weapon categories. If false, player outgoing stagger tuning stays vanilla/no-op everywhere without deleting stored values."); WeaponCategory[] staggerFeatureWeaponCategories = StaggerFeatureWeaponCategories; foreach (WeaponCategory category in staggerFeatureWeaponCategories) { (WeaponCategory, string, string) tuple = FeatureToggleWeapons.First(((WeaponCategory Category, string Label, string KeySuffix) entry) => entry.Category == category); ConfigEntry value = BindConfig("Staggering", "ApplyModStaggeringBehaviorFor" + tuple.Item3, defaultValue: true, "Persistent feature toggle. If true, Goo's Combat Overhaul applies stagger/poise behavior and this weapon category's stored stagger settings to " + tuple.Item2 + ". If false, this weapon category's outgoing stagger tuning stays vanilla/no-op without deleting stored values."); FeatureWeaponToggles[FeatureToggleKey(ModFeature.Staggering, category)] = value; } } private static string FeatureToggleKey(ModFeature feature, WeaponCategory category) { return feature.ToString() + ":" + category; } private static bool IsWeaponCategoryApplied(WeaponCategory category) { return true; } private static bool IsFeatureApplied(WeaponCategory category, ModFeature feature) { category = PublicFeatureCategory(category); if (FeatureGlobalToggles.TryGetValue(feature, out ConfigEntry value) && value.Value && FeatureWeaponToggles.TryGetValue(FeatureToggleKey(feature, category), out ConfigEntry value2)) { return value2.Value; } return false; } private static void SetFeatureWeaponToggles(bool enabled) { foreach (ConfigEntry value in FeatureGlobalToggles.Values) { value.Value = enabled; } foreach (ConfigEntry value2 in FeatureWeaponToggles.Values) { value2.Value = enabled; } } private static void ApplyVanillaPresetValues() { SetFeatureWeaponToggles(enabled: false); EnableCounterDamage.Value = true; OnlyPlayersCanDealCounterDamage.Value = true; HyperArmorMitigationMode.Value = HyperArmorDamageMitigationMode.RawHitData; MitigateUnknownAttackerDamageDuringHyperArmor.Value = true; EnablePvpDamageModifiers.Value = true; AlwaysDoMaxDamage.Value = false; AlwaysDoMaxDamageInPvp.Value = false; GlobalAdrenalineRate.Value = 1f; GlobalDamageMultiplier.Value = 1f; GlobalHyperArmorDamageTakenMultiplier.Value = 1f; GlobalHyperArmorKnockbackTakenMultiplier.Value = 1f; GlobalPvpDamageMultiplier.Value = 1f; GlobalPvpDamageTakenMultiplier.Value = 1f; if (UseDamageCurveInPve != null) { UseDamageCurveInPve.Value = false; } if (PveAlternateDamageCurveBaseRawDamage != null) { PveAlternateDamageCurveBaseRawDamage.Value = 100f; } if (PveAlternateDamageCurveExponent != null) { PveAlternateDamageCurveExponent.Value = 0.5f; } if (PveAlternateDamageCurveMinimumMultiplier != null) { PveAlternateDamageCurveMinimumMultiplier.Value = 0f; } if (PveAlternateDamageCurveMaximumMultiplier != null) { PveAlternateDamageCurveMaximumMultiplier.Value = 1f; } if (UseDamageCurveInPvp != null) { UseDamageCurveInPvp.Value = false; } if (PvpAlternateDamageCurveBaseRawDamage != null) { PvpAlternateDamageCurveBaseRawDamage.Value = 100f; } if (PvpAlternateDamageCurveExponent != null) { PvpAlternateDamageCurveExponent.Value = 0.5f; } if (PvpAlternateDamageCurveMinimumMultiplier != null) { PvpAlternateDamageCurveMinimumMultiplier.Value = 0f; } if (PvpAlternateDamageCurveMaximumMultiplier != null) { PvpAlternateDamageCurveMaximumMultiplier.Value = 2f; } GlobalPlayerKnockbackMultiplier.Value = 1f; GlobalPvpKnockbackMultiplier.Value = 1f; GlobalPlayerStaggerPowerMultiplier.Value = 1f; ClearEnemyStaggerMeterWhenStaggered.Value = false; if (MobMaintainDirectionWhenStaggered != null) { MobMaintainDirectionWhenStaggered.Value = false; } if (PlayerMaintainDirectionWhenStaggered != null) { PlayerMaintainDirectionWhenStaggered.Value = false; } GlobalPlayerDamageTakenMultiplier.Value = 1f; if (PlayerDamageToBuildingsMultiplier != null) { PlayerDamageToBuildingsMultiplier.Value = 1f; } if (EnemyDamageToBuildingsMultiplier != null) { EnemyDamageToBuildingsMultiplier.Value = 1f; } ResetBuildingDamageMultipliersToVanilla(); ResetStaggeredEnemyDamageMultipliersToVanilla(); ResetFlankingDamageMultipliersToVanilla(); ResetEffectiveBlockAngleMultipliersToVanilla(); GlobalCounterDamageMultiplier.Value = 1f; GlobalHitStopEnabled.Value = true; GlobalBlockingStaminaRegenMultiplier.Value = 1f; GlobalBlockArmorMultiplier.Value = 1f; GlobalBlockForceMultiplier.Value = 1f; GlobalParryBonusMultiplier.Value = 1f; GlobalParryWindowTimeMultiplier.Value = 1f; GlobalParryAdrenalineMultiplier.Value = 1f; if (CanStaggerRangedAttacksWithParryPve != null) { CanStaggerRangedAttacksWithParryPve.Value = true; } if (CanStaggerRangedAttacksWithParryPvp != null) { CanStaggerRangedAttacksWithParryPvp.Value = true; } if (CanStaggerAoeAttacksWithParryPve != null) { CanStaggerAoeAttacksWithParryPve.Value = true; } if (CanStaggerAoeAttacksWithParryPvp != null) { CanStaggerAoeAttacksWithParryPvp.Value = true; } GlobalBlockStaminaConsumptionRate.Value = 1f; GlobalBlockBreakThresholdMultiplier.Value = 1f; GlobalPvpBlockArmorMultiplier.Value = 1f; GlobalPvpBlockForceMultiplier.Value = 1f; GlobalPvpParryBonusMultiplier.Value = 1f; GlobalPvpBlockBreakThresholdMultiplier.Value = 1f; GlobalBlockingMovementSpeedMultiplier.Value = 1f; GlobalKnockbackTakenWhileBlockingMultiplier.Value = 1f; if (GlobalSoulsLikeBlockStaminaBaseRateMultiplier != null) { GlobalSoulsLikeBlockStaminaBaseRateMultiplier.Value = 1f; } ResetBlockedHitDamageTakenConfigsToVanilla(); GlobalHitRayRangeMultiplier.Value = 1f; GlobalHitRayThicknessMultiplier.Value = 1f; GlobalHitRayStartHeightMultiplier.Value = 1f; GlobalHitRayOffsetMultiplier.Value = 1f; GlobalHitRayAngleMultiplier.Value = 1f; ResetPrefabHitRayRangeMultipliersToVanilla(); if (OneHandedAxeSecondaryHitRayAddedThicknessMeters != null) { OneHandedAxeSecondaryHitRayAddedThicknessMeters.Value = 0f; } GlobalAttackStartNoiseMultiplier.Value = 1f; GlobalAttackHitNoiseMultiplier.Value = 1f; if (GlobalSneakDamageMultiplier != null) { GlobalSneakDamageMultiplier.Value = 1f; } if (SneakDamageScalesWithSneakLevel != null) { SneakDamageScalesWithSneakLevel.Value = false; } if (SneakDamageScaleBaseMultiplier != null) { SneakDamageScaleBaseMultiplier.Value = 1f; } if (SneakDamageMultiplierPerSneakLevel != null) { SneakDamageMultiplierPerSneakLevel.Value = 0f; } if (SneakXpOnSuccessfulSneakAttack != null) { SneakXpOnSuccessfulSneakAttack.Value = false; } if (SneakXpOnSuccessfulSneakAttackBaseAmount != null) { SneakXpOnSuccessfulSneakAttackBaseAmount.Value = 1f; } if (SneakXpScalesWithSneakDamageMultiplier != null) { SneakXpScalesWithSneakDamageMultiplier.Value = false; } if (SneakXpRangedAttackMultiplier != null) { SneakXpRangedAttackMultiplier.Value = 1f; } GlobalAttackMovementMultiplier.Value = 1f; GlobalLungeDistanceMultiplier.Value = 1f; GlobalAttackRotationMultiplier.Value = 1f; GlobalPvpHitStopDurationMultiplier.Value = 1f; GlobalAttackAnimationSpeedMultiplier.Value = 1f; GlobalRecoveryAnimationSpeedMultiplier.Value = 1f; if (BetterLightningEffects != null) { BetterLightningEffects.Value = false; } if (HimminAflLightningChainChance != null) { HimminAflLightningChainChance.Value = 0f; } if (DundrLightningChainChancePerPellet != null) { DundrLightningChainChancePerPellet.Value = 0f; } MustEatUniqueFood.Value = true; FoodHealthMultiplier.Value = 1f; FoodStaminaMultiplier.Value = 1f; FoodEitrMultiplier.Value = 1f; if (FoodHealingRateMultiplier != null) { FoodHealingRateMultiplier.Value = 1f; } if (PermanentFoodBuffs != null) { PermanentFoodBuffs.Value = false; } if (PermanentMeadBuffs != null) { PermanentMeadBuffs.Value = false; } if (PermanentRestedBuff != null) { PermanentRestedBuff.Value = false; } if (NoConsumption != null) { NoConsumption.Value = false; } if (BroadcastConfigChanges != null) { BroadcastConfigChanges.Value = false; } if (SyncConfigInMultiplayer != null) { SyncConfigInMultiplayer.Value = false; } if (LimitGuestsAuthority != null) { LimitGuestsAuthority.Value = true; } if (MeadStaminaRecoveryBonusMultiplier != null) { MeadStaminaRecoveryBonusMultiplier.Value = 1f; } if (MeadEitrRecoveryBonusMultiplier != null) { MeadEitrRecoveryBonusMultiplier.Value = 1f; } if (MeadHealthRegenBonusMultiplier != null) { MeadHealthRegenBonusMultiplier.Value = 1f; } if (GlobalUnbreakableGear != null) { GlobalUnbreakableGear.Value = false; } if (GlobalDurabilityConsumptionMultiplier != null) { GlobalDurabilityConsumptionMultiplier.Value = 1f; } if (GlobalBaseDurabilityMultiplier != null) { GlobalBaseDurabilityMultiplier.Value = 1f; } if (GlobalDurabilityMultiplier != null) { GlobalDurabilityMultiplier.Value = 1f; } ResetGearDurabilityConfigsToVanilla(); HitStopDurationMultiplier.Value = 1f; HitStopDurationOverrideSeconds.Value = -1f; AttackMovementGroundingModeConfig.Value = AttackMovementGroundingMode.Always; BetterFreeAim.Value = false; EnemyStaggerDamageDealtMultiplier.Value = 1f; EnemyKnockbackDealtMultiplier.Value = 1f; if (EnemySpeedMultiplier != null) { EnemySpeedMultiplier.Value = 1f; } EnemyAttackAnimationSpeedMultiplier.Value = 1f; EnemyMovementAnimationSpeedMultiplier.Value = 1f; EnemySizeMultiplier.Value = 1f; if (EnemyAttackHitRayRangeMultiplier != null) { EnemyAttackHitRayRangeMultiplier.Value = 1f; } if (EnemyAttackHitRaySizeMultiplier != null) { EnemyAttackHitRaySizeMultiplier.Value = 1f; } if (EnemyAttackHitRayHeightMultiplier != null) { EnemyAttackHitRayHeightMultiplier.Value = 1f; } if (EnemyAttackHitRayOffsetMultiplier != null) { EnemyAttackHitRayOffsetMultiplier.Value = 1f; } EnemyBaseRotationFactor.Value = 1f; EnemyAttackRotationFactor.Value = 1f; if (EnemyLockRotationAfterHitRay != null) { EnemyLockRotationAfterHitRay.Value = false; } EnemyPoiseMultiplier.Value = 1f; EnemyStaggerDecaySpeedMultiplier.Value = 1f; EnemyStaggerDecayCooldownSeconds.Value = 0f; EnableNegativeStamina.Value = false; NegativeStaminaFloor.Value = 0f; if (NegativeStaminaActionGate != null) { NegativeStaminaActionGate.Value = NegativeStaminaActionGateMode.PositiveOnlyLegacy; } PlayerBaseHealth.Value = 25f; PlayerBaseStamina.Value = 75f; PlayerBaseEitr.Value = 0f; PlayerBaseEitrRegenRate.Value = 1f; PlayerBaseArmor.Value = 0f; PlayerBaseCarryWeight.Value = 300f; PlayerJumpForceMultiplier.Value = 1f; PlayerRunSpeedMultiplier.Value = 1f; PlayerJogSpeedMultiplier.Value = 1f; PlayerWalkSpeedMultiplier.Value = 1f; PlayerCrouchSpeedMultiplier.Value = 1f; PlayerBaseRotationFactor.Value = 1f; PlayerSizeMultiplier.Value = 1f; PlayerPoiseMultiplier.Value = 1f; PlayerStaggerDecaySpeedMultiplier.Value = 1f; PlayerStaggerDecayCooldownSeconds.Value = 0f; JumpStaminaConsumptionRate.Value = 1f; RunStaminaConsumptionRate.Value = 1f; DrainRunStaminaDuringAttacksAndAirborne.Value = true; RollStaminaConsumptionRate.Value = 1f; CrouchStaminaConsumptionRate.Value = 1f; RollAnimationSpeed.Value = 1f; if (RollDistanceMultiplier != null) { RollDistanceMultiplier.Value = 1f; } StaminaRecoveryRate.Value = 1f; StaminaRegenCurve.Value = StaminaRegenCurveMode.Vanilla; ResetMovementModifierOverridesToVanilla(); PlayerHealingRate.Value = 1f; FallDamageStartHeight.Value = 4f; FallDamagePerMeter.Value = 6.25f; FallDamageCap.Value = 100f; BowDrawSpeedMultiplier.Value = 1f; if (BowDrawRotationFactor != null) { BowDrawRotationFactor.Value = 1f; } BowSpreadMultiplier.Value = 1f; BowProjectileSpeedMultiplier.Value = 1f; BowBaseSpreadMultiplier.Value = 1f; CrossbowReloadTimeMultiplier.Value = 1f; CrossbowProjectileSpeedMultiplier.Value = 1f; if (CrossbowRecoilStrengthMultiplier != null) { CrossbowRecoilStrengthMultiplier.Value = 1f; } SpearProjectileSpeedMultiplier.Value = 1f; if (SpearLoyaltyMode != null) { SpearLoyaltyMode.Value = false; } if (ThrowableStaminaConsumptionRate != null) { ThrowableStaminaConsumptionRate.Value = 1f; } if (ThrowableDamageMultiplier != null) { ThrowableDamageMultiplier.Value = 1f; } if (ThrowableProjectileSpeedMultiplier != null) { ThrowableProjectileSpeedMultiplier.Value = 1f; } if (ThrowableThrowAnimationSpeedMultiplier != null) { ThrowableThrowAnimationSpeedMultiplier.Value = 1f; } if (SledgeUseAlternateMoveset != null) { SledgeUseAlternateMoveset.Value = false; } if (SledgeSlamAoeRadiusMultiplier != null) { SledgeSlamAoeRadiusMultiplier.Value = 1f; } foreach (RunningAttackConfig value3 in RunningAttackConfigs.Values) { value3.Enabled.Value = false; } foreach (JumpAttackConfig value4 in JumpAttackConfigs.Values) { value4.Enabled.Value = false; } if (HarpoonRopeStrengthMultiplier != null) { HarpoonRopeStrengthMultiplier.Value = 1f; } CrossbowBaseSpreadMultiplier.Value = 1f; StaffLightningReloadSpeedMultiplier.Value = 1f; if (StaffLightningRecoilStrengthMultiplier != null) { StaffLightningRecoilStrengthMultiplier.Value = 1f; } StaffLightningProjectileCountMultiplier.Value = 1f; StaffGreenRootsVineDamageMultiplier.Value = 1f; StaffGreenRootsVineSizeMultiplier.Value = 1f; StaffGreenRootsVineAttackSpeedMultiplier.Value = 1f; if (StaffGreenRootsVineLevel != null) { StaffGreenRootsVineLevel.Value = 0; } if (StaffSkeletonSizeMultiplier != null) { StaffSkeletonSizeMultiplier.Value = 1f; } if (StaffSkeletonAttackSpeedMultiplier != null) { StaffSkeletonAttackSpeedMultiplier.Value = 1f; } if (StaffSkeletonLevel != null) { StaffSkeletonLevel.Value = 0; } foreach (StaffBlockConfig value5 in StaffBlockConfigs.Values) { value5.BlockForceMultiplier.Value = 0f; } if (StaffGreenRootsMaxVinesSpawned != null) { StaffGreenRootsMaxVinesSpawned.Value = 10; } if (StaffSkeletonHealthConsumptionRate != null) { StaffSkeletonHealthConsumptionRate.Value = 1f; } if (StaffSkeletonBaseSpawnCapLevel1 != null) { StaffSkeletonBaseSpawnCapLevel1.Value = 1; } if (StaffSkeletonBaseSpawnCapLevel2 != null) { StaffSkeletonBaseSpawnCapLevel2.Value = 2; } if (StaffSkeletonBaseSpawnCapLevel3 != null) { StaffSkeletonBaseSpawnCapLevel3.Value = 3; } if (StaffSkeletonBaseSpawnCapLevel4 != null) { StaffSkeletonBaseSpawnCapLevel4.Value = 4; } LightArmorBaseArmorMultiplier.Value = 1f; MediumArmorBaseArmorMultiplier.Value = 1f; HeavyArmorBaseArmorMultiplier.Value = 1f; foreach (KeyValuePair categoryConfig in CategoryConfigs) { if (categoryConfig.Key != WeaponCategory.SledgeAlt) { SetCategoryToVanilla(categoryConfig.Value); SetVanillaStealthAndHitboxPreset(categoryConfig.Key, 1f, 1f, 1f, 1f); if (BackstabDamageMultipliers.TryGetValue(categoryConfig.Key, out RoleSettings value)) { float num = DefaultVanillaBackstabDamageMultiplier(categoryConfig.Key); SetVanillaAttackRoleValues(categoryConfig.Key, value, num, num); } float num2 = DefaultChopDamageMultiplier(categoryConfig.Key); float num3 = VanillaPickaxeDamageMultiplier(categoryConfig.Key); SetVanillaAttackRoleValues(categoryConfig.Key, categoryConfig.Value.ChopDamageMultiplier, num2, num2); SetVanillaAttackRoleValues(categoryConfig.Key, categoryConfig.Value.PickaxeDamageMultiplier, num3, num3); } } foreach (KeyValuePair weaponBlockExtra in WeaponBlockExtras) { if (weaponBlockExtra.Key != WeaponCategory.SledgeAlt) { WeaponBlockExtraConfig value2 = weaponBlockExtra.Value; value2.CanParry.Value = true; value2.ParryBonusMultiplier.Value = 1f; value2.ParryWindowTimeMultiplier.Value = 1f; value2.PvpParryBonusMultiplier.Value = 1f; value2.PvpBlockForceMultiplier.Value = 1f; value2.PvpBlockBreakThresholdMultiplier.Value = 1f; value2.SoulsLikeBlockBreak.Value = false; value2.SoulsLikeBlockStaminaMode.Value = false; value2.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value2.KnockbackTakenWhileBlockingMultiplier.Value = 1f; value2.BlockingMovementSpeedMultiplier.Value = 1f; value2.EnableRunningWhileBlocking.Value = false; value2.RunningWhileBlockingMovementSpeedMultiplier.Value = 1f; } } ResetShieldTypeBlockConfig(ShieldBucklerBlockConfig); ResetShieldTypeBlockConfig(ShieldMediumBlockConfig); ResetShieldTypeBlockConfig(ShieldTowerBlockConfig); ResetBackstabDamageMultipliersToVanilla(); SetStaffConfigsToVanilla(); if (SledgeUseAlternateMoveset != null) { SledgeUseAlternateMoveset.Value = false; } } private static void SetCategoryToVanilla(CategoryConfig cfg) { cfg.BaseDamageMultiplier.Value = 1f; SetVanillaAttackRoleValues(cfg.Category, cfg.StaggerMode, StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla); SetVanillaAttackRoleValues(cfg.Category, cfg.StaggerPowerValue, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.DamageToStaggeredEnemyMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.HyperArmorMode, HyperArmorMode.Off, HyperArmorMode.Off); SetVanillaAttackRoleValues(cfg.Category, cfg.DamageTakenMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.StaggerTakenMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.KnockbackTakenMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpDamageTakenMultiplierDuringHyperArmor, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpStaggerTakenMultiplierDuringHyperArmor, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpKnockbackTakenMultiplierDuringHyperArmor, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.HitStopDurationMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpHitStopDurationMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.CounterDamageEnabled, primaryValue: false, secondaryValue: false); SetVanillaAttackRoleValues(cfg.Category, cfg.CounterDamageMultiplier, 1.3f, 1.3f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpCounterDamageMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.DamageRateMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.ChopDamageMultiplier, 0f, 0f); SetVanillaAttackRoleValues(cfg.Category, cfg.PickaxeDamageMultiplier, 0f, 0f); SetVanillaAttackRoleValues(cfg.Category, cfg.AirborneDamageMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.AirborneStaggerMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.AirborneRotationFactor, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.StaminaRateMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.EitrRateMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.AttackMovementModifier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.AttackMovementApplicationMode, AttackMovementApplicationMode.FullAnimation, AttackMovementApplicationMode.FullAnimation); SetVanillaAttackRoleValues(cfg.Category, cfg.LungeDistanceMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.LungeApplicationMode, LungeApplicationMode.BeforeHitbox, LungeApplicationMode.BeforeHitbox); SetVanillaAttackRoleValues(cfg.Category, cfg.AttackAnimationSpeedMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.RecoveryAnimationSpeedMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.AttackRotationFactor, -1f, -1f); SetVanillaAttackRoleValues(cfg.Category, cfg.LockRotationAfterAttackTrigger, primaryValue: false, secondaryValue: false); SetVanillaAttackRoleValues(cfg.Category, cfg.AdrenalineMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpDamageMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpStaggerMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpStaggerDurationSeconds, 0f, 0f); SetVanillaAttackRoleValues(cfg.Category, cfg.PvpKnockbackForceMultiplier, 1f, 1f); SetVanillaAttackRoleValues(cfg.Category, cfg.KnockbackForceMultiplier, 1f, 1f); cfg.PvpBlockArmorMultiplier.Value = 1f; cfg.BlockArmorMultiplier.Value = 1f; cfg.BlockForceMultiplier.Value = 1f; cfg.BlockStaminaConsumptionRate.Value = 1f; cfg.BlockingStaminaRegenMultiplier.Value = 1f; cfg.BlockBreakThresholdMultiplier.Value = 1f; SetVanillaAttackRoleValues(cfg.Category, cfg.BlockCancelMode, BlockCancelMode.Disabled, BlockCancelMode.Disabled); SetVanillaAttackRoleValues(cfg.Category, cfg.BlockCancelAnimationSpeedMultiplier, 1f, 1f); } private static void SetStealthAndHitboxPreset(WeaponCategory category, float startNoise, float hitNoise, float range, float rayWidth, float hitboxHeight) { if (AttackStartNoiseMultipliers.TryGetValue(category, out RoleSettings value)) { value.SetAll(startNoise, startNoise); } if (AttackHitNoiseMultipliers.TryGetValue(category, out RoleSettings value2)) { value2.SetAll(hitNoise, hitNoise); } if (AttackRangeMultipliers.TryGetValue(category, out RoleSettings value3)) { value3.SetAll(range, range); } if (AttackRayWidthMultipliers.TryGetValue(category, out RoleSettings value4)) { value4.SetAll(rayWidth, rayWidth); } if (AttackHitboxHeightMultipliers.TryGetValue(category, out RoleSettings value5)) { value5.SetAll(hitboxHeight, hitboxHeight); } if (AttackOffsetMultipliers.TryGetValue(category, out RoleSettings value6)) { value6.SetAll(1f, 1f); } if (AttackAngleMultipliers.TryGetValue(category, out RoleSettings value7)) { value7.SetAll(1f, 1f); } if (AttackHitboxTypes.TryGetValue(category, out RoleSettings value8)) { SetRoleSettingsByRole(value8, (AttackRole role) => DefaultVanillaHitboxType(category, role)); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(category, out RoleSettings value9)) { SetRoleSettingsByRole(value9, (AttackRole role) => DefaultVanillaMultiTargetDamagePenaltyMode(category, role)); } if (AttackMultiHitEnabled.TryGetValue(category, out RoleSettings value10)) { SetRoleSettingsByRole(value10, (AttackRole role) => DefaultVanillaMultiHitEnabled(category, role)); } if (AttackHitThroughTargetsEnabled.TryGetValue(category, out RoleSettings value11)) { value11.SetAll(primary: false, secondary: false); } if (AttackHitThroughWallsEnabled.TryGetValue(category, out RoleSettings value12)) { value12.SetAll(primary: false, secondary: false); } } private static void SetVanillaStealthAndHitboxPreset(WeaponCategory category, float startNoise, float hitNoise, float range, float hitboxHeight) { if (AttackStartNoiseMultipliers.TryGetValue(category, out RoleSettings value)) { SetVanillaAttackRoleValues(category, value, startNoise, startNoise); } if (AttackHitNoiseMultipliers.TryGetValue(category, out RoleSettings value2)) { SetVanillaAttackRoleValues(category, value2, hitNoise, hitNoise); } if (AttackRangeMultipliers.TryGetValue(category, out RoleSettings value3)) { SetVanillaAttackRoleValues(category, value3, range, range); } if (AttackRayWidthMultipliers.TryGetValue(category, out RoleSettings value4)) { SetRoleHitRayThicknessToVanillaDefaults(category, value4); } if (AttackHitboxHeightMultipliers.TryGetValue(category, out RoleSettings value5)) { SetVanillaAttackRoleValues(category, value5, hitboxHeight, hitboxHeight); } if (AttackOffsetMultipliers.TryGetValue(category, out RoleSettings value6)) { SetVanillaAttackRoleValues(category, value6, 1f, 1f); } if (AttackAngleMultipliers.TryGetValue(category, out RoleSettings value7)) { SetVanillaAttackRoleValues(category, value7, 1f, 1f); } if (AttackHitboxTypes.TryGetValue(category, out RoleSettings value8)) { SetRoleHitboxTypesToVanillaDefaults(category, value8); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(category, out RoleSettings value9)) { SetRoleMultiTargetDamagePenaltyToVanillaDefaults(category, value9); } if (AttackMultiHitEnabled.TryGetValue(category, out RoleSettings value10)) { SetRoleMultiHitToVanillaDefaults(category, value10); } if (AttackHitThroughTargetsEnabled.TryGetValue(category, out RoleSettings value11)) { SetVanillaAttackRoleValues(category, value11, primaryValue: false, secondaryValue: false); } if (AttackHitThroughWallsEnabled.TryGetValue(category, out RoleSettings value12)) { SetVanillaAttackRoleValues(category, value12, primaryValue: false, secondaryValue: false); } } private static void SetRoleHitRayThicknessToVanillaDefaults(WeaponCategory category, RoleSettings settings) { SetVanillaAttackRoleValues(category, settings, (AttackRole role) => DefaultVanillaHitRayThicknessMultiplier(category, role)); } private static float DefaultVanillaHitRayThicknessMultiplier(WeaponCategory category, AttackRole role) { if (role != AttackRole.Secondary || category != WeaponCategory.DualAxes) { return 1f; } return 0f; } private static void SetRoleMultiTargetDamagePenaltyToVanillaDefaults(WeaponCategory category, RoleSettings settings) { SetVanillaAttackRoleValues(category, settings, (AttackRole role) => DefaultVanillaMultiTargetDamagePenaltyMode(category, role)); } private static AttackMultiTargetDamagePenaltyMode DefaultVanillaMultiTargetDamagePenaltyMode(WeaponCategory category, AttackRole role) { switch (category) { case WeaponCategory.TwoHandedSword: case WeaponCategory.OneHandedSword: return AttackMultiTargetDamagePenaltyMode.Disabled; case WeaponCategory.TwoHandedAxe: if (role != AttackRole.Secondary) { return AttackMultiTargetDamagePenaltyMode.Disabled; } break; } switch (category) { case WeaponCategory.DualAxes: return AttackMultiTargetDamagePenaltyMode.Disabled; case WeaponCategory.Atgeir: if (role == AttackRole.Secondary) { return AttackMultiTargetDamagePenaltyMode.Disabled; } break; } return AttackMultiTargetDamagePenaltyMode.Enabled; } private static void SetRoleMultiHitToVanillaDefaults(WeaponCategory category, RoleSettings settings) { SetVanillaAttackRoleValues(category, settings, (AttackRole role) => DefaultVanillaMultiHitEnabled(category, role)); } private static bool DefaultVanillaMultiHitEnabled(WeaponCategory category, AttackRole role) { return DefaultVanillaMultiTargetDamagePenaltyMode(category, role) == AttackMultiTargetDamagePenaltyMode.Disabled; } private static void SetRoleHitboxTypesToVanillaDefaults(WeaponCategory category, RoleSettings settings) { SetVanillaAttackRoleValues(category, settings, (AttackRole role) => DefaultVanillaHitboxType(category, role)); } private static AttackHitboxType DefaultVanillaHitboxType(WeaponCategory category, AttackRole role) { if (category != WeaponCategory.OneHandedAxe || role != AttackRole.Secondary) { return AttackHitboxType.Horizontal; } return AttackHitboxType.Vertical; } private static AttackHitboxType DefaultVanillaRunningHitboxType(WeaponCategory category) { return DefaultVanillaHitboxType(category, RoleFromRunningAttackUsed(DefaultRunningAttackUsed(category))); } private static AttackHitboxType DefaultVanillaJumpHitboxType(WeaponCategory category) { return DefaultVanillaHitboxType(category, DefaultJumpAttackRole(category)); } private static void ResetPrefabHitRayRangeMultipliersToVanilla() { foreach (ConfigEntry value in OneHandedSwordPrefabHitRayRangeMultipliers.Values) { value.Value = 1f; } foreach (ConfigEntry value2 in SpearPrefabHitRayRangeMultipliers.Values) { value2.Value = 1f; } } private static void SetGooPrefabHitRayRangeDefaults() { SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordBronze", 0.85f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordIron", 1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordSilver", 1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordBlackmetal", 1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordMistwalker", 1.1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordNiedhogg", 1.1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordNiedhoggBlood", 1.1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordNiedhoggLightning", 1.1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordNiedhoggNature", 1.1f); SetPrefabHitRayRangeDefault(OneHandedSwordPrefabHitRayRangeMultipliers, "SwordDyrnwyn", 1.15f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearFlint", 1.25f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearBronze", 1.25f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearElderbark", 1.25f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearCarapace", 1.25f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearWolfFang", 1.2f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearSplitner", 1.45f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearSplitner_Blood", 1.45f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearSplitner_Lightning", 1.45f); SetPrefabHitRayRangeDefault(SpearPrefabHitRayRangeMultipliers, "SpearSplitner_Nature", 1.45f); } private static void SetPrefabHitRayRangeDefault(Dictionary> map, string prefabId, float value) { if (map.TryGetValue(prefabId, out ConfigEntry value2)) { value2.Value = value; } } private static void SetGooMeleeHitboxDefaults() { WeaponCategory[] array = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; for (int i = 0; i < array.Length; i++) { SetStealthAndHitboxPreset(array[i], 1f, 1f, 1f, 1f, 1f); } } private static void SetGooHitboxShapeDefaults() { SetGooPrefabHitRayRangeDefaults(); if (AttackRangeMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value)) { value.Primary1.Value = 1.1f; value.Secondary.Value = 1.1f; } if (AttackRangeMultipliers.TryGetValue(WeaponCategory.Spear, out RoleSettings value2)) { value2.Primary1.Value = 1f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Atgeir, out RoleSettings value3)) { value3.SetAll(2f, 2f); } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value4)) { value4.SetAll(2.2f, 2.2f); value4.Secondary.Value = 0.55f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value5)) { value5.SetPrimary(1.67f); if (OneHandedAxeSecondaryHitRayAddedThicknessMeters != null) { OneHandedAxeSecondaryHitRayAddedThicknessMeters.Value = 0.4f; } if (OneHandedAxeJumpAttackHitRayAddedThicknessMeters != null) { OneHandedAxeJumpAttackHitRayAddedThicknessMeters.Value = 0.4f; } } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Spear, out RoleSettings value6)) { value6.SetPrimary(1.2f); } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value7)) { value7.Primary1.Value = 1.25f; value7.Primary2.Value = 1.25f; value7.Primary3.Value = 1.4f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value8)) { value8.Primary3.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value9)) { value9.Primary3.Value = 1f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value10)) { value10.Primary1.Value = 1.4f; value10.Primary2.Value = 1.4f; value10.Primary3.Value = 1.67f; value10.Secondary.Value = 1.67f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Knife, out RoleSettings value11)) { value11.Primary1.Value = 2f; value11.Primary2.Value = 2f; value11.Primary3.Value = 2f; value11.Secondary.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.DualKnives, out RoleSettings value12)) { value12.Primary1.Value = 2f; value12.Primary2.Value = 2f; value12.Primary3.Value = 2f; value12.Secondary.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Unarmed, out RoleSettings value13)) { value13.Primary1.Value = 2.5f; value13.Primary2.Value = 2.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value14)) { value14.SetPrimary(1.3f); value14.Secondary.Value = 1.8f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value15)) { value15.Primary1.Value = 1.3f; value15.Primary2.Value = 1.3f; value15.Primary3.Value = 1.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value16)) { value16.Primary3.Value = 1.6f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value17)) { value17.Primary1.Value = 1.5f; value17.Primary2.Value = 1.5f; value17.Primary3.Value = 1.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value18)) { value18.Primary3.Value = 1.6f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value19)) { value19.Primary1.Value = 1.25f; value19.Primary2.Value = 1.25f; value19.Primary3.Value = 2f; value19.Secondary.Value = 2f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.Knife, out RoleSettings value20)) { value20.Primary1.Value = 2f; value20.Primary2.Value = 2f; value20.Primary3.Value = 2f; value20.Secondary.Value = 2f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.DualKnives, out RoleSettings value21)) { value21.Primary1.Value = 2f; value21.Primary2.Value = 2f; value21.Primary3.Value = 2f; value21.Secondary.Value = 2f; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value22)) { value22.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value23)) { value23.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value24)) { value24.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.Spear, out RoleSettings value25)) { value25.Primary1.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.Club, out RoleSettings value26)) { value26.Primary3.Value = AttackHitboxType.Vertical; value26.Secondary.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value27)) { value27.Secondary.Value = DefaultVanillaHitboxType(WeaponCategory.OneHandedAxe, AttackRole.Secondary); } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value28)) { value28.Primary1.Value = 1.5f; value28.Primary2.Value = 1.5f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value29)) { value29.Primary3.Value = 0.7f; value29.Secondary.Value = 2f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value30)) { value30.Primary1.Value = 1.4f; value30.Primary2.Value = 1.4f; value30.Primary3.Value = 0.5f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value31)) { value31.Primary1.Value = 1.2f; value31.Primary2.Value = 1.2f; value31.Primary3.Value = 0.7f; } SetGooMultiTargetPenaltyDefaults(); SetGooDualAxesJumpAttackDefaults(); if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value32)) { value32.HitboxType.Value = AttackHitboxType.Vertical; value32.HitboxHeightMultiplier.Value = 1f; value32.RayWidthMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.OneHandedSword, out JumpAttackConfig value33)) { value33.HitboxType.Value = AttackHitboxType.Vertical; value33.HitboxHeightMultiplier.Value = 1f; value33.RayWidthMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value34)) { value34.HitboxType.Value = AttackHitboxType.Vertical; value34.HitboxHeightMultiplier.Value = 1f; value34.RayWidthMultiplier.Value = 1.67f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out JumpAttackConfig value35)) { value35.HitboxType.Value = AttackHitboxType.Vertical; value35.HitboxHeightMultiplier.Value = 1f; value35.RayWidthMultiplier.Value = 2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value36)) { value36.RayWidthMultiplier.Value = 2.2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Knife, out JumpAttackConfig value37)) { value37.HitboxHeightMultiplier.Value = 2f; value37.RayWidthMultiplier.Value = 2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualKnives, out JumpAttackConfig value38)) { value38.HitboxHeightMultiplier.Value = 2f; value38.RayWidthMultiplier.Value = 2f; } ResetModdedHitRayRoleDefaultsToVanilla(); CopyRunningHitRayDefaultsFromUsedAttacks(); } private static void ResetModdedHitRayRoleDefaultsToVanilla() { WeaponCategory[] directHitRayWeaponCategories = DirectHitRayWeaponCategories; foreach (WeaponCategory weaponCategory in directHitRayWeaponCategories) { AttackRole[] array = new AttackRole[4] { AttackRole.Primary2, AttackRole.Primary3, AttackRole.Primary4, AttackRole.Secondary }; foreach (AttackRole role in array) { if (!IsVanillaAttackRole(weaponCategory, role)) { if (AttackRangeMultipliers.TryGetValue(weaponCategory, out RoleSettings value)) { value.Get(role).Value = 1f; } if (AttackRayWidthMultipliers.TryGetValue(weaponCategory, out RoleSettings value2)) { value2.Get(role).Value = 1f; } if (AttackHitboxHeightMultipliers.TryGetValue(weaponCategory, out RoleSettings value3)) { value3.Get(role).Value = 1f; } if (AttackOffsetMultipliers.TryGetValue(weaponCategory, out RoleSettings value4)) { value4.Get(role).Value = 1f; } if (AttackAngleMultipliers.TryGetValue(weaponCategory, out RoleSettings value5)) { value5.Get(role).Value = 1f; } if (AttackHitboxTypes.TryGetValue(weaponCategory, out RoleSettings value6)) { value6.Get(role).Value = DefaultVanillaHitboxType(weaponCategory, role); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(weaponCategory, out RoleSettings value7)) { value7.Get(role).Value = DefaultVanillaMultiTargetDamagePenaltyMode(weaponCategory, role); } } } } } private static void CopyRunningHitRayDefaultsFromUsedAttacks() { foreach (KeyValuePair runningAttackConfig in RunningAttackConfigs) { WeaponCategory key = runningAttackConfig.Key; RunningAttackConfig value = runningAttackConfig.Value; AttackRole role = RoleFromRunningAttackUsed(value.UsedAttack.Value); if (!IsVanillaAttackRole(key, role)) { value.RangeMultiplier.Value = 1f; value.RayWidthMultiplier.Value = 1f; value.HitboxHeightMultiplier.Value = 1f; value.OffsetMultiplier.Value = 1f; value.AngleMultiplier.Value = 1f; value.HitboxType.Value = DefaultVanillaRunningHitboxType(key); value.MultiTargetDamagePenalty.Value = DefaultVanillaMultiTargetDamagePenaltyMode(key, role); continue; } if (AttackRangeMultipliers.TryGetValue(key, out RoleSettings value2)) { value.RangeMultiplier.Value = value2.Get(role).Value; } if (AttackRayWidthMultipliers.TryGetValue(key, out RoleSettings value3)) { value.RayWidthMultiplier.Value = value3.Get(role).Value; } if (AttackHitboxHeightMultipliers.TryGetValue(key, out RoleSettings value4)) { value.HitboxHeightMultiplier.Value = value4.Get(role).Value; } if (AttackOffsetMultipliers.TryGetValue(key, out RoleSettings value5)) { value.OffsetMultiplier.Value = value5.Get(role).Value; } if (AttackAngleMultipliers.TryGetValue(key, out RoleSettings value6)) { value.AngleMultiplier.Value = value6.Get(role).Value; } if (AttackHitboxTypes.TryGetValue(key, out RoleSettings value7)) { value.HitboxType.Value = value7.Get(role).Value; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(key, out RoleSettings value8)) { value.MultiTargetDamagePenalty.Value = value8.Get(role).Value; } } } private static void ResetGearDurabilityConfigsToVanilla() { foreach (GearDurabilityConfig item in GearDurabilityConfigs.Values.Distinct()) { item.Unbreakable.Value = false; item.DurabilityConsumptionMultiplier.Value = 1f; item.BaseDurabilityMultiplier.Value = 1f; item.DurabilityMultiplier.Value = 1f; } } private static void ApplyGooHorizontalHitRayThicknessBonus() { WeaponCategory[] directHitRayWeaponCategories = DirectHitRayWeaponCategories; foreach (WeaponCategory key in directHitRayWeaponCategories) { if (AttackRayWidthMultipliers.TryGetValue(key, out RoleSettings value) && AttackHitboxTypes.TryGetValue(key, out RoleSettings value2)) { ApplyHorizontalHitRayThicknessBonus(value.Primary1, value2.Primary1); ApplyHorizontalHitRayThicknessBonus(value.Primary2, value2.Primary2); ApplyHorizontalHitRayThicknessBonus(value.Primary3, value2.Primary3); ApplyHorizontalHitRayThicknessBonus(value.Primary4, value2.Primary4); ApplyHorizontalHitRayThicknessBonus(value.Secondary, value2.Secondary); } } } private static void ApplyHorizontalHitRayThicknessBonus(Setting width, Setting type) { if (type.Value == AttackHitboxType.Horizontal && Mathf.Approximately(width.Value, 1f)) { width.Value = 1.15f; } } private static bool DefaultGooMultiHitEnabled(WeaponCategory category, AttackRole role) { if (category == WeaponCategory.Spear) { return role != AttackRole.Primary1; } return true; } private static bool DefaultGooRunningAttackMultiHitEnabled(WeaponCategory category) { return true; } private static bool DefaultGooJumpAttackMultiHitEnabled(WeaponCategory category) { return true; } private static bool DefaultGooHitThroughMobsEnabled(WeaponCategory category, AttackRole role) { return DefaultGooMultiTargetDamagePenaltyModeForHitThrough(category, role) == AttackMultiTargetDamagePenaltyMode.Disabled; } private static AttackMultiTargetDamagePenaltyMode DefaultGooRunningAttackMultiTargetDamagePenaltyMode(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword && category != WeaponCategory.OneHandedSword && category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.DualAxes && category != WeaponCategory.OneHandedAxe && category != WeaponCategory.Club && category != WeaponCategory.SledgeAlt) { return AttackMultiTargetDamagePenaltyMode.Enabled; } return AttackMultiTargetDamagePenaltyMode.Disabled; } private static AttackMultiTargetDamagePenaltyMode DefaultGooJumpAttackMultiTargetDamagePenaltyMode(WeaponCategory category) { return DefaultGooRunningAttackMultiTargetDamagePenaltyMode(category); } private static bool DefaultGooRunningAttackHitThroughMobsEnabled(WeaponCategory category) { return DefaultGooRunningAttackMultiTargetDamagePenaltyMode(category) == AttackMultiTargetDamagePenaltyMode.Disabled; } private static bool DefaultGooJumpAttackHitThroughMobsEnabled(WeaponCategory category) { return DefaultGooJumpAttackMultiTargetDamagePenaltyMode(category) == AttackMultiTargetDamagePenaltyMode.Disabled; } private static bool DefaultGooHitThroughWallsEnabled(WeaponCategory category, AttackRole role) { if (DefaultGooMultiTargetDamagePenaltyModeForHitThrough(category, role) != AttackMultiTargetDamagePenaltyMode.Disabled) { return DefaultGooHitboxTypeForHitThrough(category, role) == AttackHitboxType.Vertical; } return true; } private static AttackMultiTargetDamagePenaltyMode DefaultGooMultiTargetDamagePenaltyModeForHitThrough(WeaponCategory category, AttackRole role) { switch (category) { case WeaponCategory.TwoHandedSword: case WeaponCategory.OneHandedSword: return AttackMultiTargetDamagePenaltyMode.Disabled; case WeaponCategory.DualAxes: case WeaponCategory.OneHandedAxe: return AttackMultiTargetDamagePenaltyMode.Disabled; case WeaponCategory.TwoHandedAxe: if (role != AttackRole.Secondary) { return AttackMultiTargetDamagePenaltyMode.Disabled; } break; } if (category == WeaponCategory.Atgeir && role == AttackRole.Secondary) { return AttackMultiTargetDamagePenaltyMode.Disabled; } if (category == WeaponCategory.Club && (role == AttackRole.Primary3 || role == AttackRole.Secondary)) { return AttackMultiTargetDamagePenaltyMode.Disabled; } return DefaultVanillaMultiTargetDamagePenaltyMode(category, role); } private static AttackHitboxType DefaultGooHitboxTypeForHitThrough(WeaponCategory category, AttackRole role) { if (AttackHitboxTypes.TryGetValue(category, out RoleSettings value)) { return value.Get(role).Value; } return DefaultVanillaHitboxType(category, role); } private static bool DefaultGooRunningAttackHitThroughWallsEnabled(WeaponCategory category) { return true; } private static bool DefaultGooJumpAttackHitThroughWallsEnabled(WeaponCategory category) { return true; } private static void SetGoo120HitStopDefaults() { GetConfig(WeaponCategory.TwoHandedSword).HitStopDurationMultiplier.SetAll(0.6f, 0.6f); GetConfig(WeaponCategory.TwoHandedAxe).HitStopDurationMultiplier.SetAll(0.9f, 0.9f); GetConfig(WeaponCategory.TwoHandedAxe).HitStopDurationMultiplier.Primary3.Value = 5f; GetConfig(WeaponCategory.Sledge).HitStopDurationMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.Pickaxe).HitStopDurationMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.DualAxes).HitStopDurationMultiplier.SetAll(0.7f, 0.7f); GetConfig(WeaponCategory.Atgeir).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.OneHandedSword).HitStopDurationMultiplier.SetAll(0.6f, 0.6f); GetConfig(WeaponCategory.OneHandedAxe).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.Club).HitStopDurationMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.Knife).HitStopDurationMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.DualKnives).HitStopDurationMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.Spear).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.Unarmed).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.Other).HitStopDurationMultiplier.SetAll(1f, 1f); WeaponCategory[] allFeatureWeaponCategories = AllFeatureWeaponCategories; foreach (WeaponCategory weaponCategory in allFeatureWeaponCategories) { if (CategoryConfigs.TryGetValue(weaponCategory, out CategoryConfig value)) { value.PvpHitStopDurationMultiplier.SetAll(DefaultGooPvpHitStopDurationMultiplier(weaponCategory), DefaultGooPvpHitStopDurationMultiplier(weaponCategory)); } } } private static void SetGooMultiTargetPenaltyDefaults() { if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value)) { value.SetAll(AttackMultiTargetDamagePenaltyMode.Disabled, AttackMultiTargetDamagePenaltyMode.Disabled); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value2)) { value2.SetAll(AttackMultiTargetDamagePenaltyMode.Disabled, AttackMultiTargetDamagePenaltyMode.Disabled); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value3)) { value3.SetAll(AttackMultiTargetDamagePenaltyMode.Disabled, AttackMultiTargetDamagePenaltyMode.Disabled); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value4)) { value4.SetAll(AttackMultiTargetDamagePenaltyMode.Disabled, AttackMultiTargetDamagePenaltyMode.Disabled); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value5)) { value5.Primary1.Value = AttackMultiTargetDamagePenaltyMode.Disabled; value5.Primary2.Value = AttackMultiTargetDamagePenaltyMode.Disabled; value5.Primary3.Value = AttackMultiTargetDamagePenaltyMode.Disabled; value5.Primary4.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.Atgeir, out RoleSettings value6)) { value6.Secondary.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } WeaponCategory[] array = new WeaponCategory[4] { WeaponCategory.TwoHandedSword, WeaponCategory.OneHandedSword, WeaponCategory.DualAxes, WeaponCategory.OneHandedAxe }; foreach (WeaponCategory key in array) { if (RunningAttackConfigs.TryGetValue(key, out RunningAttackConfig value7)) { value7.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (JumpAttackConfigs.TryGetValue(key, out JumpAttackConfig value8)) { value8.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } } if (RunningAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out RunningAttackConfig value9)) { value9.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value10)) { value10.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.Club, out RoleSettings value11)) { value11.Primary3.Value = AttackMultiTargetDamagePenaltyMode.Disabled; value11.Secondary.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.Spear, out RoleSettings value12)) { value12.Primary1.Value = AttackMultiTargetDamagePenaltyMode.Enabled; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Club, out RunningAttackConfig value13)) { value13.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value14)) { value14.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } SetGooMultiHitDefaults(); } private static void SetGooMultiHitDefaults() { foreach (KeyValuePair> item in AttackMultiHitEnabled) { WeaponCategory key = item.Key; RoleSettings value = item.Value; value.Primary1.Value = DefaultGooMultiHitEnabled(key, AttackRole.Primary1); value.Primary2.Value = DefaultGooMultiHitEnabled(key, AttackRole.Primary2); value.Primary3.Value = DefaultGooMultiHitEnabled(key, AttackRole.Primary3); value.Primary4.Value = DefaultGooMultiHitEnabled(key, AttackRole.Primary4); value.Secondary.Value = DefaultGooMultiHitEnabled(key, AttackRole.Secondary); } foreach (KeyValuePair> item2 in AttackHitThroughTargetsEnabled) { WeaponCategory key2 = item2.Key; RoleSettings value2 = item2.Value; value2.Primary1.Value = DefaultGooHitThroughMobsEnabled(key2, AttackRole.Primary1); value2.Primary2.Value = DefaultGooHitThroughMobsEnabled(key2, AttackRole.Primary2); value2.Primary3.Value = DefaultGooHitThroughMobsEnabled(key2, AttackRole.Primary3); value2.Primary4.Value = DefaultGooHitThroughMobsEnabled(key2, AttackRole.Primary4); value2.Secondary.Value = DefaultGooHitThroughMobsEnabled(key2, AttackRole.Secondary); } foreach (KeyValuePair> item3 in AttackHitThroughWallsEnabled) { WeaponCategory key3 = item3.Key; RoleSettings value3 = item3.Value; value3.Primary1.Value = DefaultGooHitThroughWallsEnabled(key3, AttackRole.Primary1); value3.Primary2.Value = DefaultGooHitThroughWallsEnabled(key3, AttackRole.Primary2); value3.Primary3.Value = DefaultGooHitThroughWallsEnabled(key3, AttackRole.Primary3); value3.Primary4.Value = DefaultGooHitThroughWallsEnabled(key3, AttackRole.Primary4); value3.Secondary.Value = DefaultGooHitThroughWallsEnabled(key3, AttackRole.Secondary); } foreach (KeyValuePair> item4 in RunningAttackMultiHitEnabled) { item4.Value.Value = DefaultGooRunningAttackMultiHitEnabled(item4.Key); } foreach (KeyValuePair> item5 in RunningAttackHitThroughTargetsEnabled) { item5.Value.Value = DefaultGooRunningAttackHitThroughMobsEnabled(item5.Key); } foreach (KeyValuePair> item6 in RunningAttackHitThroughWallsEnabled) { item6.Value.Value = DefaultGooRunningAttackHitThroughWallsEnabled(item6.Key); } foreach (KeyValuePair> item7 in JumpAttackMultiHitEnabled) { item7.Value.Value = DefaultGooJumpAttackMultiHitEnabled(item7.Key); } foreach (KeyValuePair> item8 in JumpAttackHitThroughTargetsEnabled) { item8.Value.Value = DefaultGooJumpAttackHitThroughMobsEnabled(item8.Key); } foreach (KeyValuePair> item9 in JumpAttackHitThroughWallsEnabled) { item9.Value.Value = DefaultGooJumpAttackHitThroughWallsEnabled(item9.Key); } } private static void SetGooMeleeBlockCancelAndAttackAdrenalineDefaults() { WeaponCategory[] array = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; foreach (WeaponCategory weaponCategory in array) { CategoryConfig config = GetConfig(weaponCategory); config.BlockCancelMode.SetAll(BlockCancelMode.AfterHitbox, BlockCancelMode.AfterHitbox); config.BlockCancelAnimationSpeedMultiplier.SetAll(1f, 1f); config.AdrenalineMultiplier.SetAll(2f, 2f); if (RunningAttackConfigs.TryGetValue(weaponCategory, out RunningAttackConfig value)) { value.BlockCancelMode.Value = BlockCancelMode.AfterHitbox; value.BlockCancelAnimationSpeedMultiplier.Value = 1f; value.AdrenalineMultiplier.Value = 2f; } if (JumpAttackConfigs.TryGetValue(weaponCategory, out JumpAttackConfig value2)) { value2.BlockCancelMode.Value = BlockCancelMode.AfterHitbox; value2.BlockCancelAnimationSpeedMultiplier.Value = 1f; value2.AdrenalineMultiplier.Value = 2f; } } } private static void SetToGooRulesetValues() { ApplyVanillaPresetValues(); SetFeatureWeaponToggles(enabled: true); if (SyncConfigInMultiplayer != null) { SyncConfigInMultiplayer.Value = true; } if (LimitGuestsAuthority != null) { LimitGuestsAuthority.Value = true; } if (CrossbowRecoilStrengthMultiplier != null) { CrossbowRecoilStrengthMultiplier.Value = 1f; } if (StaffLightningRecoilStrengthMultiplier != null) { StaffLightningRecoilStrengthMultiplier.Value = 0f; } if (BowDrawRotationFactor != null) { BowDrawRotationFactor.Value = 999f; } if (RollDistanceMultiplier != null) { RollDistanceMultiplier.Value = 1f; } EnableCounterDamage.Value = true; OnlyPlayersCanDealCounterDamage.Value = true; HyperArmorMitigationMode.Value = HyperArmorDamageMitigationMode.RawHitData; MitigateUnknownAttackerDamageDuringHyperArmor.Value = true; EnablePvpDamageModifiers.Value = true; AlwaysDoMaxDamage.Value = false; AlwaysDoMaxDamageInPvp.Value = true; GlobalAdrenalineRate.Value = 1f; GlobalHitStopEnabled.Value = true; GlobalPvpKnockbackMultiplier.Value = 1f; if (UseDamageCurveInPve != null) { UseDamageCurveInPve.Value = true; } if (PveAlternateDamageCurveBaseRawDamage != null) { PveAlternateDamageCurveBaseRawDamage.Value = 100f; } if (PveAlternateDamageCurveExponent != null) { PveAlternateDamageCurveExponent.Value = 0.5f; } if (PveAlternateDamageCurveMinimumMultiplier != null) { PveAlternateDamageCurveMinimumMultiplier.Value = 0f; } if (PveAlternateDamageCurveMaximumMultiplier != null) { PveAlternateDamageCurveMaximumMultiplier.Value = 1f; } if (UseDamageCurveInPvp != null) { UseDamageCurveInPvp.Value = true; } if (PvpAlternateDamageCurveBaseRawDamage != null) { PvpAlternateDamageCurveBaseRawDamage.Value = 100f; } if (PvpAlternateDamageCurveExponent != null) { PvpAlternateDamageCurveExponent.Value = 0.5f; } if (PvpAlternateDamageCurveMinimumMultiplier != null) { PvpAlternateDamageCurveMinimumMultiplier.Value = 0f; } if (PvpAlternateDamageCurveMaximumMultiplier != null) { PvpAlternateDamageCurveMaximumMultiplier.Value = 2f; } GlobalKnockbackTakenWhileBlockingMultiplier.Value = 1f; if (GlobalSoulsLikeBlockStaminaBaseRateMultiplier != null) { GlobalSoulsLikeBlockStaminaBaseRateMultiplier.Value = 1f; } ResetBlockedHitDamageTakenConfigsToVanilla(); GlobalHitRayRangeMultiplier.Value = 1f; GlobalHitRayThicknessMultiplier.Value = 1f; GlobalHitRayStartHeightMultiplier.Value = 1f; GlobalHitRayOffsetMultiplier.Value = 1f; GlobalHitRayAngleMultiplier.Value = 1f; GlobalAttackStartNoiseMultiplier.Value = 1f; GlobalAttackHitNoiseMultiplier.Value = 1f; if (GlobalSneakDamageMultiplier != null) { GlobalSneakDamageMultiplier.Value = 2f; } if (SneakDamageScalesWithSneakLevel != null) { SneakDamageScalesWithSneakLevel.Value = true; } if (SneakDamageScaleBaseMultiplier != null) { SneakDamageScaleBaseMultiplier.Value = 1f; } if (SneakDamageMultiplierPerSneakLevel != null) { SneakDamageMultiplierPerSneakLevel.Value = 0.01f; } if (SneakXpOnSuccessfulSneakAttack != null) { SneakXpOnSuccessfulSneakAttack.Value = true; } if (SneakXpOnSuccessfulSneakAttackBaseAmount != null) { SneakXpOnSuccessfulSneakAttackBaseAmount.Value = 1f; } if (SneakXpScalesWithSneakDamageMultiplier != null) { SneakXpScalesWithSneakDamageMultiplier.Value = true; } if (SneakXpRangedAttackMultiplier != null) { SneakXpRangedAttackMultiplier.Value = 0.5f; } GlobalAttackMovementMultiplier.Value = 1f; GlobalLungeDistanceMultiplier.Value = 1f; GlobalRunningAttackLungeDistanceMultiplier.Value = 1f; GlobalJumpAttackLungeDistanceMultiplier.Value = 1f; GlobalAttackRotationMultiplier.Value = 1f; GlobalPvpHitStopDurationMultiplier.Value = 1f; GlobalAttackAnimationSpeedMultiplier.Value = 1f; GlobalRecoveryAnimationSpeedMultiplier.Value = 1f; if (BetterLightningEffects != null) { BetterLightningEffects.Value = true; } if (HimminAflLightningChainChance != null) { HimminAflLightningChainChance.Value = 0.25f; } if (DundrLightningChainChancePerPellet != null) { DundrLightningChainChancePerPellet.Value = 0.02f; } ClearEnemyStaggerMeterWhenStaggered.Value = true; if (MobMaintainDirectionWhenStaggered != null) { MobMaintainDirectionWhenStaggered.Value = true; } if (PlayerMaintainDirectionWhenStaggered != null) { PlayerMaintainDirectionWhenStaggered.Value = true; } MustEatUniqueFood.Value = true; FoodHealthMultiplier.Value = 1f; FoodStaminaMultiplier.Value = 1f; FoodEitrMultiplier.Value = 1f; if (FoodHealingRateMultiplier != null) { FoodHealingRateMultiplier.Value = 1f; } if (PermanentFoodBuffs != null) { PermanentFoodBuffs.Value = false; } if (PermanentMeadBuffs != null) { PermanentMeadBuffs.Value = false; } if (PermanentRestedBuff != null) { PermanentRestedBuff.Value = false; } if (NoConsumption != null) { NoConsumption.Value = false; } if (BroadcastConfigChanges != null) { BroadcastConfigChanges.Value = false; } if (GlobalParryWindowTimeMultiplier != null) { GlobalParryWindowTimeMultiplier.Value = 1f; } if (CanStaggerRangedAttacksWithParryPve != null) { CanStaggerRangedAttacksWithParryPve.Value = false; } if (CanStaggerRangedAttacksWithParryPvp != null) { CanStaggerRangedAttacksWithParryPvp.Value = true; } if (CanStaggerAoeAttacksWithParryPve != null) { CanStaggerAoeAttacksWithParryPve.Value = false; } if (CanStaggerAoeAttacksWithParryPvp != null) { CanStaggerAoeAttacksWithParryPvp.Value = false; } ResetParryWindowMultipliersToVanilla(); if (PlayerDamageToBuildingsMultiplier != null) { PlayerDamageToBuildingsMultiplier.Value = 1f; } if (EnemyDamageToBuildingsMultiplier != null) { EnemyDamageToBuildingsMultiplier.Value = 0.2f; } if (EnemyKnockbackDealtMultiplier != null) { EnemyKnockbackDealtMultiplier.Value = 0.7f; } ResetBuildingDamageMultipliersToGoo(); ResetStaggeredEnemyDamageMultipliersToGoo(); ResetBackstabDamageMultipliersToGoo(); ResetFlankingDamageMultipliersToGoo(); ResetEffectiveBlockAngleMultipliersToGoo(); if (MeadStaminaRecoveryBonusMultiplier != null) { MeadStaminaRecoveryBonusMultiplier.Value = 1f; } if (MeadEitrRecoveryBonusMultiplier != null) { MeadEitrRecoveryBonusMultiplier.Value = 1f; } if (MeadHealthRegenBonusMultiplier != null) { MeadHealthRegenBonusMultiplier.Value = 1f; } if (GlobalUnbreakableGear != null) { GlobalUnbreakableGear.Value = false; } if (GlobalDurabilityConsumptionMultiplier != null) { GlobalDurabilityConsumptionMultiplier.Value = 1f; } if (GlobalBaseDurabilityMultiplier != null) { GlobalBaseDurabilityMultiplier.Value = 1f; } if (GlobalDurabilityMultiplier != null) { GlobalDurabilityMultiplier.Value = 1f; } ResetGearDurabilityConfigsToVanilla(); foreach (KeyValuePair categoryConfig in CategoryConfigs) { categoryConfig.Value.PvpBlockArmorMultiplier.Value = DefaultGooPvpBlockArmorMultiplier(categoryConfig.Key); } SetGooBlockForceDefaults(); if (GlobalBlockingMovementSpeedMultiplier != null) { GlobalBlockingMovementSpeedMultiplier.Value = 1f; } SetGooPvpBlockBreakDefaults(); foreach (WeaponBlockExtraConfig value18 in WeaponBlockExtras.Values) { value18.SoulsLikeBlockBreak.Value = false; value18.SoulsLikeBlockStaminaMode.Value = false; value18.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value18.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } SetGooRunningWhileBlockingDefaults(); foreach (StaffBlockConfig value19 in StaffBlockConfigs.Values) { value19.SoulsLikeBlockBreak.Value = false; value19.SoulsLikeBlockStaminaMode.Value = false; value19.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value19.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } HitStopDurationMultiplier.Value = 1f; HitStopDurationOverrideSeconds.Value = -1f; AttackMovementGroundingModeConfig.Value = AttackMovementGroundingMode.GroundOnly; BetterFreeAim.Value = false; EnemyStaggerDamageDealtMultiplier.Value = 1f; EnemyKnockbackDealtMultiplier.Value = 0.7f; if (EnemySpeedMultiplier != null) { EnemySpeedMultiplier.Value = 1f; } EnemyAttackAnimationSpeedMultiplier.Value = 1f; EnemyMovementAnimationSpeedMultiplier.Value = 1f; EnemySizeMultiplier.Value = 1f; if (EnemyAttackHitRayRangeMultiplier != null) { EnemyAttackHitRayRangeMultiplier.Value = 1f; } if (EnemyAttackHitRaySizeMultiplier != null) { EnemyAttackHitRaySizeMultiplier.Value = 1f; } if (EnemyAttackHitRayHeightMultiplier != null) { EnemyAttackHitRayHeightMultiplier.Value = 1f; } if (EnemyAttackHitRayOffsetMultiplier != null) { EnemyAttackHitRayOffsetMultiplier.Value = 1f; } EnemyBaseRotationFactor.Value = 1f; EnemyAttackRotationFactor.Value = 1f; if (EnemyLockRotationAfterHitRay != null) { EnemyLockRotationAfterHitRay.Value = true; } EnemyPoiseMultiplier.Value = 1f; EnemyStaggerDecaySpeedMultiplier.Value = 1f; EnemyStaggerDecayCooldownSeconds.Value = 1f; PlayerBaseRotationFactor.Value = 2f; PlayerSizeMultiplier.Value = 1f; PlayerPoiseMultiplier.Value = 1f; PlayerStaggerDecaySpeedMultiplier.Value = 1f; PlayerStaggerDecayCooldownSeconds.Value = 1f; AirborneAttackMinimumAirTimeSeconds.Value = 1f; EnableNegativeStamina.Value = true; NegativeStaminaFloor.Value = -50f; if (NegativeStaminaActionGate != null) { NegativeStaminaActionGate.Value = NegativeStaminaActionGateMode.RequireCostWithinFloor; } DrainRunStaminaDuringAttacksAndAirborne.Value = false; PlayerBaseEitr.Value = 0f; PlayerBaseEitrRegenRate.Value = 1f; PlayerBaseCarryWeight.Value = 300f; JumpStaminaConsumptionRate.Value = 0.5f; CrouchStaminaConsumptionRate.Value = 0f; StaminaRecoveryRate.Value = 1.5f; PlayerHealingRate.Value = 1f; StaminaRegenCurve.Value = StaminaRegenCurveMode.Vanilla; SetGooMovementModifierOverrides(); FallDamageStartHeight.Value = 8f; WeaponCategory[] array = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; for (int i = 0; i < array.Length; i++) { SetRotationPreset(GetConfig(array[i]), 2f, 2f, primaryLock: true, secondaryLock: true); } SetGooSlowWeaponRotationDefaults(); array = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; foreach (WeaponCategory weaponCategory in array) { CategoryConfig config = GetConfig(weaponCategory); bool flag = weaponCategory == WeaponCategory.Knife || weaponCategory == WeaponCategory.DualKnives || weaponCategory == WeaponCategory.OneHandedAxe || weaponCategory == WeaponCategory.Atgeir; config.AttackMovementModifier.SetPrimary(flag ? 1f : 0f); config.AttackMovementModifier.Secondary.Value = ((weaponCategory == WeaponCategory.OneHandedAxe || weaponCategory == WeaponCategory.Atgeir) ? 1f : 0f); config.AttackMovementApplicationMode.SetAll(AttackMovementApplicationMode.FullAnimation, AttackMovementApplicationMode.FullAnimation); } CategoryConfig config2 = GetConfig(WeaponCategory.TwoHandedAxe); config2.AttackMovementModifier.SetPrimary(0f); config2.AttackMovementModifier.Secondary.Value = 0.2f; config2.AttackMovementApplicationMode.SetAll(AttackMovementApplicationMode.FullAnimation, AttackMovementApplicationMode.FullAnimation); foreach (KeyValuePair categoryConfig2 in CategoryConfigs) { RoleSettings chopDamageMultiplier = categoryConfig2.Value.ChopDamageMultiplier; chopDamageMultiplier.Primary1.Value = DefaultGooChopDamageMultiplier(categoryConfig2.Key, AttackRole.Primary1); chopDamageMultiplier.Primary2.Value = DefaultGooChopDamageMultiplier(categoryConfig2.Key, AttackRole.Primary2); chopDamageMultiplier.Primary3.Value = DefaultGooChopDamageMultiplier(categoryConfig2.Key, AttackRole.Primary3); chopDamageMultiplier.Primary4.Value = DefaultGooChopDamageMultiplier(categoryConfig2.Key, AttackRole.Primary4); chopDamageMultiplier.Secondary.Value = DefaultGooChopDamageMultiplier(categoryConfig2.Key, AttackRole.Secondary); categoryConfig2.Value.PickaxeDamageMultiplier.SetAll(DefaultPickaxeDamageMultiplier(categoryConfig2.Key), DefaultPickaxeDamageMultiplier(categoryConfig2.Key)); } foreach (KeyValuePair categoryConfig3 in CategoryConfigs) { CategoryConfig value = categoryConfig3.Value; value.AirborneDamageMultiplier.SetAll(DefaultGooAirborneDamageMultiplier(categoryConfig3.Key), DefaultGooAirborneDamageMultiplier(categoryConfig3.Key)); value.AirborneStaggerMultiplier.SetAll(1f, 1f); value.AirborneRotationFactor.SetAll(1f, 1f); } array = new WeaponCategory[12] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, WeaponCategory.DualAxes, WeaponCategory.Atgeir, WeaponCategory.OneHandedSword, WeaponCategory.OneHandedAxe, WeaponCategory.Club, WeaponCategory.Knife, WeaponCategory.DualKnives, WeaponCategory.Spear, WeaponCategory.Unarmed, WeaponCategory.Other }; for (int i = 0; i < array.Length; i++) { CategoryConfig config3 = GetConfig(array[i]); config3.LungeDistanceMultiplier.SetAll(2f, 2f); config3.LungeApplicationMode.SetAll(LungeApplicationMode.BeforeHitbox, LungeApplicationMode.BeforeHitbox); } GetConfig(WeaponCategory.Atgeir).SecondaryLungeDistanceMultiplier.Value = 1f; GetConfig(WeaponCategory.DualAxes).SecondaryLungeDistanceMultiplier.Value = 0.8f; GetConfig(WeaponCategory.Knife).SecondaryLungeDistanceMultiplier.Value = 1.5f; GetConfig(WeaponCategory.DualKnives).SecondaryLungeDistanceMultiplier.Value = 1.5f; RoleSettings lungeDistanceMultiplier = GetConfig(WeaponCategory.Spear).LungeDistanceMultiplier; lungeDistanceMultiplier.Primary1.Value = 2f; lungeDistanceMultiplier.Primary2.Value = 2f; lungeDistanceMultiplier.Primary3.Value = 2f; lungeDistanceMultiplier.Primary4.Value = 2f; lungeDistanceMultiplier.Secondary.Value = 2f; GetConfig(WeaponCategory.OneHandedSword).LungeDistanceMultiplier.SetPrimary(1.8f); GetConfig(WeaponCategory.Club).LungeDistanceMultiplier.SetPrimary(1.8f); GetConfig(WeaponCategory.TwoHandedSword).LungeDistanceMultiplier.Primary1.Value = 2.4f; GetConfig(WeaponCategory.TwoHandedSword).LungeDistanceMultiplier.Primary2.Value = 2.4f; GetConfig(WeaponCategory.TwoHandedSword).LungeDistanceMultiplier.Primary3.Value = 2.5f; GetConfig(WeaponCategory.Unarmed).LungeDistanceMultiplier.Primary2.Value = 1.5f; SetGooMeleeHitboxDefaults(); GetConfig(WeaponCategory.Unarmed).AttackAnimationSpeedMultiplier.SetAll(1.2f, 1.3f); GetConfig(WeaponCategory.Unarmed).RecoveryAnimationSpeedMultiplier.SetAll(1f, 1f); SetRunningAttackPreset(enabled: true, 1f, 2.5f); if (RunningAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out RunningAttackConfig value2)) { value2.UsedAttack.Value = RunningAttackUsed.Primary3; value2.DamageMultiplier.Value = 0.7f; value2.MovementSpeedMultiplier.Value = 1f; value2.AnimationSpeedMultiplier.Value = 1.1f; value2.PvpDamageMultiplier.Value = 1f; value2.LungeDistanceMultiplier.Value = 2f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out RunningAttackConfig value3)) { value3.UsedAttack.Value = RunningAttackUsed.Primary2; value3.DamageMultiplier.Value = 1f; value3.MovementSpeedMultiplier.Value = 0.9f; value3.LungeDistanceMultiplier.Value = 0f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.OneHandedAxe, out RunningAttackConfig value4)) { value4.UsedAttack.Value = RunningAttackUsed.Primary3; value4.DamageMultiplier.Value = 0.7f; value4.AnimationSpeedMultiplier.Value = 1.4f; value4.RecoveryAnimationSpeedMultiplier.Value = 0.5f; value4.MovementSpeedMultiplier.Value = 1f; value4.HitboxHeightMultiplier.Value = 1.5f; value4.LungeDistanceMultiplier.Value = 3f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.OneHandedSword, out RunningAttackConfig value5)) { value5.UsedAttack.Value = RunningAttackUsed.Primary3; value5.DamageMultiplier.Value = 0.7f; value5.MovementSpeedMultiplier.Value = 3f; value5.LungeDistanceMultiplier.Value = 1.3f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Club, out RunningAttackConfig value6)) { value6.UsedAttack.Value = RunningAttackUsed.Primary3; value6.DamageMultiplier.Value = 0.7f; value6.MovementSpeedMultiplier.Value = 3f; value6.LungeDistanceMultiplier.Value = 1.3f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Atgeir, out RunningAttackConfig value7)) { value7.UsedAttack.Value = RunningAttackUsed.Primary2; value7.DamageMultiplier.Value = 1f; value7.AnimationSpeedMultiplier.Value = 1.1f; value7.MovementSpeedMultiplier.Value = 3f; value7.LungeDistanceMultiplier.Value = 3f; value7.RayWidthMultiplier.Value = 2f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Knife, out RunningAttackConfig value8)) { value8.Enabled.Value = false; value8.UsedAttack.Value = RunningAttackUsed.Primary3; value8.DamageMultiplier.Value = 0.7f; value8.MovementSpeedMultiplier.Value = 1f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.DualKnives, out RunningAttackConfig value9)) { value9.Enabled.Value = false; value9.UsedAttack.Value = RunningAttackUsed.Primary3; value9.DamageMultiplier.Value = 0.7f; value9.MovementSpeedMultiplier.Value = 1f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Unarmed, out RunningAttackConfig value10)) { value10.MovementSpeedMultiplier.Value = 2f; value10.LungeDistanceMultiplier.Value = 2f; } SetJumpAttackPreset(enabled: true); if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out JumpAttackConfig value11)) { value11.PvpDamageMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value12)) { value12.PvpDamageMultiplier.Value = 1f; value12.RecoveryAnimationSpeedMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value13)) { value13.PvpDamageMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Atgeir, out JumpAttackConfig value14)) { value14.RayWidthMultiplier.Value = 2f; } SetGooHitboxShapeDefaults(); ApplyGooHorizontalHitRayThicknessBonus(); CopyRunningHitRayDefaultsFromUsedAttacks(); CategoryConfig config4 = GetConfig(WeaponCategory.TwoHandedSword); SetStagger(config4, 1.5f, 1.5f); SetHyper(config4, HyperArmorMode.Balanced, HyperArmorMode.Balanced); SetHyperArmorProtection(config4, 0.7f, 0.7f, 0f, 0f, 0f, 0f); config4.HitStopDurationMultiplier.SetAll(0.6f, 0.6f); config4.BaseDamageMultiplier.Value = 1.25f; config4.DamageRateMultiplier.SetAll(1f, 1f); config4.StaminaRateMultiplier.SetAll(1.5f, 1.5f); config4.SecondaryCounterDamageEnabled.Value = true; config4.SecondaryAdrenalineMultiplier.Value = 6f; SetDurations(config4, 2.46f, 2.46f); CategoryConfig config5 = GetConfig(WeaponCategory.TwoHandedAxe); SetStagger(config5, 1.5f, 1.5f); SetHyper(config5, HyperArmorMode.Balanced, HyperArmorMode.Balanced); SetHyperArmorProtection(config5, 0.7f, 0.7f, 0f, 0f, 0f, 0f); config5.HitStopDurationMultiplier.SetAll(0.9f, 0.9f); config5.BaseDamageMultiplier.Value = 1.25f; config5.DamageRateMultiplier.SetAll(1f, 1f); config5.StaminaRateMultiplier.SetAll(1.5f, 1.5f); config5.DamageRateMultiplier.Primary1.Value = 2f; config5.DamageRateMultiplier.Primary2.Value = 2f; SetDurations(config5, 3.44f, 3.44f); CategoryConfig config6 = GetConfig(WeaponCategory.Sledge); SetStagger(config6, 2f, 2f); SetHyper(config6, HyperArmorMode.Balanced, HyperArmorMode.Balanced); SetHyperArmorProtection(config6, 0.7f, 0.7f, 0f, 0f, 0f, 0f); config6.BaseDamageMultiplier.Value = 1.5f; config6.StaminaRateMultiplier.SetAll(1.5f, 1.5f); config6.AttackAnimationSpeedMultiplier.SetAll(1.2f, 1.2f); config6.KnockbackForceMultiplier.SetAll(0.9f, 0.9f); config6.PvpKnockbackForceMultiplier.SetAll(1f, 1f); SetDurations(config6, 2.23f, 2.23f); CategoryConfig config7 = GetConfig(WeaponCategory.Pickaxe); config7.AttackAnimationSpeedMultiplier.SetPrimary(1.2f); config7.PickaxeDamageMultiplier.SetPrimary(1.5f); config7.PvpDamageMultiplier.SetAll(1f, 1f); config7.PvpStaggerMultiplier.SetPrimary(1f); config7.PvpKnockbackForceMultiplier.SetPrimary(1f); config7.KnockbackForceMultiplier.SetPrimary(1f); CategoryConfig config8 = GetConfig(WeaponCategory.DualAxes); SetStagger(config8, 1f, 2f); SetHyper(config8, HyperArmorMode.Off, HyperArmorMode.Off); config8.HitStopDurationMultiplier.SetAll(0.7f, 0.7f); config8.AttackMovementModifier.SetPrimary(0f); config8.AttackMovementModifier.Secondary.Value = 0f; config8.SecondaryLungeDistanceMultiplier.Value = 0.9f; config8.SecondaryAttackAnimationSpeedMultiplier.Value = 1.5f; SetDurations(config8, 2.4f, 2.4f); GetConfig(WeaponCategory.OneHandedAxe).PvpDamageMultiplier.Primary3.Value = 1f; GetConfig(WeaponCategory.Unarmed).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary1.Value = 1f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary2.Value = 1f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Secondary.Value = 1.3f; GetConfig(WeaponCategory.Knife).PvpDamageMultiplier.Primary3.Value = 1f; GetConfig(WeaponCategory.DualKnives).PvpDamageMultiplier.Primary3.Value = 1f; GetConfig(WeaponCategory.Knife).AttackMovementModifier.SetAll(1f, 1f); GetConfig(WeaponCategory.DualKnives).AttackMovementModifier.SetAll(1f, 1f); CategoryConfig config9 = GetConfig(WeaponCategory.Atgeir); SetStagger(config9, 1f, 1f); SetHyper(config9, HyperArmorMode.Off, HyperArmorMode.Balanced); SetHyperArmorProtection(config9, 1f, 0.7f, 1f, 0f, 1f, 0f); config9.HitStopDurationMultiplier.SetAll(0.8f, 0.8f); config9.CounterDamageEnabled.SetPrimary(value: true); config9.SecondaryAdrenalineMultiplier.Value = 6f; if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Atgeir, out RoleSettings value15)) { value15.SetAll(2f, 2f); } SetDurations(config9, 2.98f, 2.98f); CategoryConfig config10 = GetConfig(WeaponCategory.OneHandedSword); SetStagger(config10, 1f, 1f); SetHyper(config10, HyperArmorMode.Off, HyperArmorMode.Balanced); SetHyperArmorProtection(config10, 1f, 1f, 1f, 1f, 1f, 0f); config10.HitStopDurationMultiplier.SetAll(0.6f, 0.6f); config10.SecondaryCounterDamageEnabled.Value = true; config10.SecondaryAttackAnimationSpeedMultiplier.Value = 1.3f; SetDurations(config10, 2.46f, 2.46f); CategoryConfig config11 = GetConfig(WeaponCategory.OneHandedAxe); SetStagger(config11, 1.5f, 3f); SetHyper(config11, HyperArmorMode.Off, HyperArmorMode.Balanced); SetHyperArmorProtection(config11, 1f, 1f, 1f, 1f, 1f, 0f); config11.HitStopDurationMultiplier.SetAll(0.8f, 0.8f); config11.AttackMovementModifier.SetAll(1f, 1f); config11.AttackAnimationSpeedMultiplier.SetPrimary(1.4f); config11.SecondaryAttackAnimationSpeedMultiplier.Value = 1.5f; config11.RecoveryAnimationSpeedMultiplier.SetPrimary(0.8f); SetDurations(config11, 2.74f, 2.74f); CategoryConfig config12 = GetConfig(WeaponCategory.Club); SetStagger(config12, 1.5f, 1.5f); config12.SecondaryAttackAnimationSpeedMultiplier.Value = 1.3f; SetHyper(config12, HyperArmorMode.Off, HyperArmorMode.Balanced); SetHyperArmorProtection(config12, 1f, 1f, 1f, 1f, 1f, 0f); config12.KnockbackForceMultiplier.SetAll(0.7f, 0.7f); if (RunningAttackConfigs.TryGetValue(WeaponCategory.Club, out RunningAttackConfig value16)) { value16.KnockbackForceMultiplier.Value = 0.7f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value17)) { value17.KnockbackForceMultiplier.Value = 0.7f; } SetDurations(config12, 2.46f, 2.46f); CategoryConfig config13 = GetConfig(WeaponCategory.Spear); SetStagger(config13, 1f, 2f); config13.CounterDamageEnabled.SetPrimary(value: true); config13.HitStopDurationMultiplier.SetAll(0.8f, 0.8f); SetDurations(config13, 0.68f, 0.68f); SpearProjectileSpeedMultiplier.Value = 1.5f; if (SpearLoyaltyMode != null) { SpearLoyaltyMode.Value = false; } if (ThrowableStaminaConsumptionRate != null) { ThrowableStaminaConsumptionRate.Value = 1f; } if (ThrowableDamageMultiplier != null) { ThrowableDamageMultiplier.Value = 1f; } if (ThrowableProjectileSpeedMultiplier != null) { ThrowableProjectileSpeedMultiplier.Value = 1f; } if (ThrowableThrowAnimationSpeedMultiplier != null) { ThrowableThrowAnimationSpeedMultiplier.Value = 1f; } if (SledgeUseAlternateMoveset != null) { SledgeUseAlternateMoveset.Value = false; } if (HarpoonRopeStrengthMultiplier != null) { HarpoonRopeStrengthMultiplier.Value = 1f; } CategoryConfig config14 = GetConfig(WeaponCategory.Bow); config14.PvpDamageMultiplier.SetAll(1f, 1f); config14.PvpStaggerMultiplier.SetAll(1f, 1f); config14.PvpStaggerDurationSeconds.SetAll(0f, 0f); BowProjectileSpeedMultiplier.Value = 1f; CategoryConfig config15 = GetConfig(WeaponCategory.Crossbow); config15.PvpDamageMultiplier.SetAll(1f, 1f); config15.PvpStaggerMultiplier.SetAll(1f, 1f); config15.PvpStaggerDurationSeconds.SetAll(0f, 0f); SetGooPvpDefaults(); SetGooDualAxesJumpAttackDefaults(); SetGoo120HitStopDefaults(); SetGooMeleeBlockCancelAndAttackAdrenalineDefaults(); if (GlobalHitStopEnabled != null) { GlobalHitStopEnabled.Value = true; } SetGooStaffDefaults(); ApplyGooCanParryDefaults(); ApplyDesignedAlternateSledgePresetValues(); if (SledgeUseAlternateMoveset != null) { SledgeUseAlternateMoveset.Value = false; } if (SledgeSlamAoeRadiusMultiplier != null) { SledgeSlamAoeRadiusMultiplier.Value = 1f; } } private static void ResetShieldTypeBlockConfig(ShieldTypeBlockConfig cfg) { if (cfg != null) { cfg.CanParry.Value = true; cfg.BlockArmorMultiplier.Value = 1f; cfg.BlockForceMultiplier.Value = 1f; cfg.ParryBonusMultiplier.Value = 1f; cfg.ParryWindowTimeMultiplier.Value = 1f; cfg.ParryAdrenalineMultiplier.Value = 1f; cfg.BlockStaminaConsumptionRate.Value = 1f; cfg.BlockingStaminaRegenMultiplier.Value = 1f; cfg.BlockBreakThresholdMultiplier.Value = 1f; cfg.PvpBlockArmorMultiplier.Value = 1f; cfg.PvpBlockForceMultiplier.Value = 1f; cfg.PvpParryBonusMultiplier.Value = 1f; cfg.PvpBlockBreakThresholdMultiplier.Value = 1f; cfg.SoulsLikeBlockBreak.Value = false; cfg.SoulsLikeBlockStaminaMode.Value = false; cfg.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; cfg.KnockbackTakenWhileBlockingMultiplier.Value = 1f; cfg.MovementModifierPercent.Value = 0f; cfg.BlockingMovementSpeedMultiplier.Value = 1f; cfg.EnableRunningWhileBlocking.Value = false; cfg.RunningWhileBlockingMovementSpeedMultiplier.Value = 1f; } } private static void ResetStaffBlockConfig(StaffBlockConfig cfg) { if (cfg != null) { cfg.CanParry.Value = true; cfg.BlockArmorMultiplier.Value = 1f; cfg.BlockForceMultiplier.Value = 1f; cfg.ParryBonusMultiplier.Value = 1f; cfg.ParryWindowTimeMultiplier.Value = 1f; cfg.ParryAdrenalineMultiplier.Value = 1f; cfg.BlockStaminaConsumptionRate.Value = 1f; cfg.BlockingStaminaRegenMultiplier.Value = 1f; cfg.BlockBreakThresholdMultiplier.Value = 1f; cfg.PvpBlockArmorMultiplier.Value = 1f; cfg.PvpBlockForceMultiplier.Value = 1f; cfg.PvpParryBonusMultiplier.Value = 1f; cfg.PvpBlockBreakThresholdMultiplier.Value = 1f; cfg.SoulsLikeBlockBreak.Value = false; cfg.SoulsLikeBlockStaminaMode.Value = false; cfg.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; cfg.KnockbackTakenWhileBlockingMultiplier.Value = 1f; cfg.EnableRunningWhileBlocking.Value = false; cfg.RunningWhileBlockingMovementSpeedMultiplier.Value = 1f; cfg.AttackMovementModifier.Value = 1f; cfg.AttackRotationFactor.Value = -1f; } } private static void ResetStaffShieldActiveConfig(StaffShieldActiveConfig cfg) { if (cfg != null) { cfg.HealthConsumptionRate.Value = 1f; cfg.StaggerMitigationMultiplier.Value = 1f; cfg.PvpStaggerMitigationMultiplier.Value = 1f; cfg.KnockbackMitigationMultiplier.Value = 1f; cfg.PvpKnockbackMitigationMultiplier.Value = 1f; cfg.ActiveParryBonusMultiplier.Value = 1f; } } private static void SetStaffConfigsToVanilla() { foreach (StaffConfig value in StaffConfigs.Values) { if (value.DamageMultiplier != null) { value.DamageMultiplier.Value = 1f; } if (value.StaggerMultiplier != null) { value.StaggerMultiplier.Value = 1f; } value.AttackAnimationSpeedMultiplier.Value = 1f; value.EitrRateMultiplier.Value = 1f; value.AttackStartNoiseMultiplier.Value = 1f; value.AttackHitNoiseMultiplier.Value = 1f; value.BackstabDamageMultiplier.Value = 3f; if (value.PvpDamageMultiplier != null) { value.PvpDamageMultiplier.Value = 1f; } if (value.PvpStaggerMultiplier != null) { value.PvpStaggerMultiplier.Value = 1f; } if (value.PvpKnockbackForceMultiplier != null) { value.PvpKnockbackForceMultiplier.Value = 1f; } if (value.ProjectileSpeedMultiplier != null) { value.ProjectileSpeedMultiplier.Value = 1f; } if (value.AoeRadiusMultiplier != null) { value.AoeRadiusMultiplier.Value = 1f; } if (value.SpreadMultiplier != null) { value.SpreadMultiplier.Value = 1f; } if (value.ShieldHealthMultiplier != null) { value.ShieldHealthMultiplier.Value = 1f; } if (value.DamageTakenFromPlayersMultiplier != null) { value.DamageTakenFromPlayersMultiplier.Value = 1f; } } foreach (StaffBlockConfig value2 in StaffBlockConfigs.Values) { ResetStaffBlockConfig(value2); } if (StaffShieldActiveSettings != null) { ResetStaffShieldActiveConfig(StaffShieldActiveSettings); } if (StaffSkeletonHealthConsumptionRate != null) { StaffSkeletonHealthConsumptionRate.Value = 1f; } if (StaffGreenRootsVineLevel != null) { StaffGreenRootsVineLevel.Value = 0; } if (StaffSkeletonSizeMultiplier != null) { StaffSkeletonSizeMultiplier.Value = 1f; } if (StaffSkeletonAttackSpeedMultiplier != null) { StaffSkeletonAttackSpeedMultiplier.Value = 1f; } if (StaffSkeletonLevel != null) { StaffSkeletonLevel.Value = 0; } if (StaffGreenRootsAttackPlayersInPvp != null) { StaffGreenRootsAttackPlayersInPvp.Value = false; } if (StaffSkeletonAttackPlayersInPvp != null) { StaffSkeletonAttackPlayersInPvp.Value = false; } } private static void SetGooStaffDefaults() { foreach (StaffConfig value6 in StaffConfigs.Values) { if (value6.DamageMultiplier != null) { value6.DamageMultiplier.Value = 1f; } if (value6.StaggerMultiplier != null) { value6.StaggerMultiplier.Value = 1f; } value6.AttackAnimationSpeedMultiplier.Value = 1f; value6.EitrRateMultiplier.Value = 1f; value6.AttackStartNoiseMultiplier.Value = 1f; value6.AttackHitNoiseMultiplier.Value = 1f; value6.BackstabDamageMultiplier.Value = 1f; if (value6.PvpDamageMultiplier != null) { value6.PvpDamageMultiplier.Value = 1f; } if (value6.PvpStaggerMultiplier != null) { value6.PvpStaggerMultiplier.Value = 0f; } if (value6.PvpKnockbackForceMultiplier != null) { value6.PvpKnockbackForceMultiplier.Value = 0f; } if (value6.ProjectileSpeedMultiplier != null) { value6.ProjectileSpeedMultiplier.Value = 1f; } if (value6.AoeRadiusMultiplier != null) { value6.AoeRadiusMultiplier.Value = 1f; } if (value6.SpreadMultiplier != null) { value6.SpreadMultiplier.Value = 1f; } if (value6.ShieldHealthMultiplier != null) { value6.ShieldHealthMultiplier.Value = 1f; } if (value6.DamageTakenFromPlayersMultiplier != null) { value6.DamageTakenFromPlayersMultiplier.Value = 1f; } } if (StaffConfigs.TryGetValue("StaffFireball", out StaffConfig value) && value.PvpStaggerMultiplier != null) { value.PvpStaggerMultiplier.Value = 5f; } if (StaffConfigs.TryGetValue("StaffIceShards", out StaffConfig value2)) { if (value2.PvpDamageMultiplier != null) { value2.PvpDamageMultiplier.Value = 0.8f; } if (value2.SpreadMultiplier != null) { value2.SpreadMultiplier.Value = 0.5f; } } if (StaffConfigs.TryGetValue("StaffClusterbomb", out StaffConfig value3) && value3.PvpDamageMultiplier != null) { value3.PvpDamageMultiplier.Value = 1f; } if (StaffConfigs.TryGetValue("StaffLightning", out StaffConfig value4)) { if (value4.PvpDamageMultiplier != null) { value4.PvpDamageMultiplier.Value = 0.5f; } if (value4.StaggerMultiplier != null) { value4.StaggerMultiplier.Value = 2f; } } if (StaffConfigs.TryGetValue("StaffLightning", out StaffConfig value5) && value5.SpreadMultiplier != null) { value5.SpreadMultiplier.Value = 0.3f; } if (StaffLightningReloadSpeedMultiplier != null) { StaffLightningReloadSpeedMultiplier.Value = 1f; } if (StaffLightningProjectileCountMultiplier != null) { StaffLightningProjectileCountMultiplier.Value = 1f; } if (StaffGreenRootsVineDamageMultiplier != null) { StaffGreenRootsVineDamageMultiplier.Value = 1f; } if (StaffGreenRootsVineSizeMultiplier != null) { StaffGreenRootsVineSizeMultiplier.Value = 1f; } if (StaffGreenRootsVineAttackSpeedMultiplier != null) { StaffGreenRootsVineAttackSpeedMultiplier.Value = 1f; } if (StaffGreenRootsVineLevel != null) { StaffGreenRootsVineLevel.Value = 0; } if (StaffSkeletonSizeMultiplier != null) { StaffSkeletonSizeMultiplier.Value = 1f; } if (StaffSkeletonAttackSpeedMultiplier != null) { StaffSkeletonAttackSpeedMultiplier.Value = 1f; } if (StaffSkeletonLevel != null) { StaffSkeletonLevel.Value = 0; } if (StaffSkeletonHealthConsumptionRate != null) { StaffSkeletonHealthConsumptionRate.Value = 1f; } if (StaffGreenRootsAttackPlayersInPvp != null) { StaffGreenRootsAttackPlayersInPvp.Value = false; } if (StaffSkeletonAttackPlayersInPvp != null) { StaffSkeletonAttackPlayersInPvp.Value = false; } } private static void SetStagger(CategoryConfig cfg, float primary, float secondary) { cfg.BaseDamageMultiplier.Value = 1f; cfg.StaggerMode.SetAll(StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla); cfg.StaggerPowerValue.SetAll(primary, secondary); } private static void SetHyper(CategoryConfig cfg, HyperArmorMode primary, HyperArmorMode secondary) { cfg.HyperArmorMode.SetAll(primary, secondary); } private static void SetHyperArmorProtection(CategoryConfig cfg, float primaryDamage, float secondaryDamage, float primaryStagger, float secondaryStagger, float primaryKnockback, float secondaryKnockback) { cfg.DamageTakenMultiplier.SetAll(primaryDamage, secondaryDamage); cfg.StaggerTakenMultiplier.SetAll(primaryStagger, secondaryStagger); cfg.KnockbackTakenMultiplier.SetAll(primaryKnockback, secondaryKnockback); } private static void SetRotationPreset(CategoryConfig cfg, float primaryFactor, float secondaryFactor, bool primaryLock, bool secondaryLock) { cfg.AttackRotationFactor.SetAll(primaryFactor, secondaryFactor); cfg.LockRotationAfterAttackTrigger.SetAll(primaryLock, secondaryLock); } private static void SetGooSlowWeaponRotationDefaults() { GetConfig(WeaponCategory.TwoHandedSword).AttackRotationFactor.SetPrimary(1f); GetConfig(WeaponCategory.TwoHandedAxe).AttackRotationFactor.SetAll(1f, 1f); GetConfig(WeaponCategory.Atgeir).AttackRotationFactor.SetAll(1f, 1f); GetConfig(WeaponCategory.Sledge).AttackRotationFactor.SetPrimary(1f); GetConfig(WeaponCategory.OneHandedAxe).AttackRotationFactor.SetAll(1f, 1f); GetConfig(WeaponCategory.Club).SecondaryAttackRotationFactor.Value = 1f; } private static void SetDurations(CategoryConfig cfg, float primary, float secondary) { cfg.FullAttackDuration.SetAll(primary, secondary); } private void PatchCoreDamageAndStagger() { //IL_003e: 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_0060: Expected O, but got Unknown //IL_0060: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_00e9: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown //IL_022e: Expected O, but got Unknown //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Expected O, but got Unknown //IL_02d3: Expected O, but got Unknown //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Expected O, but got Unknown //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03c3: Expected O, but got Unknown //IL_046e: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Expected O, but got Unknown //IL_0490: Expected O, but got Unknown _harmony.Patch((MethodBase)AccessTools.Method(typeof(Character), "Damage", new Type[1] { typeof(HitData) }, (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterDamagePrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterDamagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Character.Damage for stagger/damage/PvP/counter/adrenaline calibration and hyperarmor damage mitigation."); MethodInfo methodInfo = AccessTools.Method(typeof(Character), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterRpcDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterRpcDamageFinalizer", (Type[])null), (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.RPC_Damage(long,HitData). Receiver-owned PvP stagger/knockback handling is unavailable on this Valheim build."); } int num = 0; foreach (MethodInfo item in from method in AccessTools.GetDeclaredMethods(typeof(EffectList)) where method.Name == "Create" select method) { _harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "EffectListCreateFlankPriorityPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} EffectList.Create overload(s) for exact flank-sound priority and alternate-sledge shared-effect filtering/hit sound."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find EffectList.Create. Exact flank-sound priority and alternate-sledge shared-effect filtering/hit sound are unavailable on this Valheim build."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Character), "RPC_Stagger", new Type[2] { typeof(long), typeof(Vector3) }, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterRpcStaggerPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterRpcStaggerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.RPC_Stagger(long,Vector3). Receiver-owned PvP stagger duration timing may fall back to Character.Stagger postfix only."); } MethodInfo methodInfo3 = AccessTools.Method(typeof(Character), "ApplyDamage", new Type[4] { typeof(HitData), typeof(bool), typeof(bool), typeof(DamageModifier) }, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterApplyDamagePrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterApplyDamagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.ApplyDamage(HitData,bool,bool,DamageModifier). PvP test dummy final damage normalization and depleted-health recovery are unavailable on this Valheim build."); } PatchNonCharacterDestructibleDamageMethods(); MethodInfo methodInfo4 = AccessTools.Method(typeof(Character), "FreezeFrame", new Type[1] { typeof(float) }, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterFreezeFramePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Character.FreezeFrame(float) for direct melee hit-stop control."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.FreezeFrame(float). Hit-stop control is unavailable on this Valheim build."); } MethodInfo methodInfo5 = AccessTools.Method(typeof(Character), "AddRootMotion", new Type[1] { typeof(Vector3) }, (Type[])null); if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterAddRootMotionPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Character.AddRootMotion(Vector3) for melee attack lunge-distance/root-motion scaling."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.AddRootMotion(Vector3). Melee lunge-distance scaling is unavailable on this Valheim build."); } int num2 = 0; foreach (MethodInfo item2 in from m in AccessTools.GetDeclaredMethods(typeof(Character)) where m.Name == "Stagger" select m) { ParameterInfo[] parameters = item2.GetParameters(); if (parameters.Length != 0 && parameters[0].ParameterType == typeof(Vector3)) { _harmony.Patch((MethodBase)item2, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterStaggerPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterStaggerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num2++; } } if (num2 > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num2} Character.Stagger overload(s) for hyperarmor stagger immunity."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find a Character.Stagger(Vector3...) overload. Hyperarmor stagger-immunity hooks may be unavailable on this Valheim build."); } } private void PatchNonCharacterDestructibleDamageMethods() { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown int num = 0; foreach (Type safeAssemblyType in GetSafeAssemblyTypes(typeof(IDestructible).Assembly)) { if (!IsSafeNonCharacterDestructibleType(safeAssemblyType)) { continue; } foreach (MethodInfo safeDeclaredInstanceMethod in GetSafeDeclaredInstanceMethods(safeAssemblyType)) { if (safeDeclaredInstanceMethod.Name != "Damage" || safeDeclaredInstanceMethod.IsAbstract || safeDeclaredInstanceMethod.ContainsGenericParameters) { continue; } ParameterInfo[] parameters = safeDeclaredInstanceMethod.GetParameters(); if (parameters.Length == 1 && !(parameters[0].ParameterType != typeof(HitData))) { try { _harmony.Patch((MethodBase)safeDeclaredInstanceMethod, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "DestructibleDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not patch " + safeAssemblyType.FullName + ".Damage(HitData) for non-character outgoing damage calibration: " + ex.Message)); } } } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} non-character IDestructible.Damage(HitData) method(s) so player damage modifiers also affect trees, rocks, pieces, and utility damage channels such as chop/pickaxe."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find any non-character IDestructible.Damage(HitData) methods. Chop/pickaxe/object damage calibration may be unavailable on this Valheim build."); } } private static IEnumerable GetSafeAssemblyTypes(Assembly assembly) { if (assembly == null) { return Array.Empty(); } try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null).Cast(); } catch { return Array.Empty(); } } private static bool IsSafeNonCharacterDestructibleType(Type? type) { if (type == null) { return false; } try { if (type.IsInterface || type.IsAbstract) { return false; } if (!typeof(IDestructible).IsAssignableFrom(type)) { return false; } if (typeof(Character).IsAssignableFrom(type)) { return false; } return true; } catch { return false; } } private static IEnumerable GetSafeDeclaredInstanceMethods(Type type) { try { return type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { return Array.Empty(); } } private void PatchAttackLifecycle() { Type type = AccessTools.TypeByName("Attack"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Attack type. Hyperarmor/counter timing patches are unavailable."); return; } PatchAttackStartMethods(type); PatchAttackMethod(type, "Update", "DurabilitySnapshotPrefix"); PatchAttackMethod(type, "Update", "AttackUpdatePostfix", postfix: true); PatchAttackMethod(type, "Update", "DurabilitySnapshotPostfix", postfix: true); PatchAttackMethodOptional(type, "OnAttackTrigger", "DurabilitySnapshotPrefix"); PatchAttackMethodOptional(type, "OnAttackTrigger", "AttackHitboxEventPrefix"); PatchAttackMethodOptional(type, "OnAttackTrigger", "AttackHitboxEventPostfix", postfix: true); PatchAttackMethodOptional(type, "OnAttackTrigger", "DurabilitySnapshotPostfix", postfix: true); PatchAttackMethod(type, "Stop", "AttackStopPostfix", postfix: true); PatchAttackMethod(type, "Abort", "AttackStopPostfix", postfix: true); PatchAttackMethodOptional(type, "OnAttackDone", "AttackStopPostfix", postfix: true); PatchAttackMethodOptional(type, "AttackDone", "AttackStopPostfix", postfix: true); PatchAttackMethodOptional(type, "Done", "AttackStopPostfix", postfix: true); PatchAttackMethod(type, "DoMeleeAttack", "AttackDoMeleeAttackPrefix"); PatchAttackMethodTranspiler(type, "DoMeleeAttack", "AttackDoMeleeAttackTranspiler"); PatchAttackMethod(type, "DoMeleeAttack", "AttackDoMeleeAttackPostfix", postfix: true); PatchAttackAreaMethods(type); PatchAttackMethodOptional(type, "UpdateAttach", "AttackUpdateAttachPrefix"); PatchAttackMethodOptional(type, "FireProjectileBurst", "AttackFireProjectileBurstPrefix"); PatchAttackMethodOptional(type, "FireProjectileBurst", "AttackFireProjectileBurstPostfix", postfix: true); PatchAttackMethodOptional(type, "UseAmmo", "AttackUseAmmoNoConsumptionPostfix", postfix: true); PatchAttackMethodOptional(type, "ModifyDamage", "AttackModifyDamagePrefix"); PatchHumanoidStartAttack(); PatchAttackStaminaMethods(type); PatchAttackEitrMethods(type); PatchAttackHealthMethods(type); } private void PatchPlayerAndSystemTuning() { //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_07cf: Unknown result type (might be due to invalid IL or missing references) //IL_07dd: Expected O, but got Unknown Type type = AccessTools.TypeByName("Player"); if (type != null) { PatchDeclaredMethods(type, "Update", "PlayerUpdatePrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "UpdateStats", "PlayerUpdateStatsPrefix", postfix: false); PatchDeclaredMethods(type, "HaveStamina", "PlayerHaveStaminaPostfix", postfix: true); PatchDeclaredMethods(type, "RPC_UseStamina", "PlayerRpcUseStaminaPrefix", postfix: false); PatchDeclaredMethods(type, "UpdateDodge", "PlayerUpdateDodgePostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "UpdateModifiers", "PlayerUpdateModifiersPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "AppendEquipmentModifierTooltips", "PlayerAppendEquipmentModifierTooltipsPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "SetMaxStamina", "PlayerSetMaxStaminaPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "SetMaxStamina", "PlayerSetMaxStaminaPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "SetMaxEitr", "PlayerSetMaxEitrPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "SetMaxEitr", "PlayerSetMaxEitrPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "AlwaysRotateCamera", "PlayerAlwaysRotateCameraPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "SetControls", "PlayerSetControlsPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "AddAdrenaline", "PlayerAddAdrenalinePrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "GetStamina", "PlayerGetStaminaHudClampPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "GetStaminaPercentage", "PlayerGetStaminaPercentageHudClampPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "GetRandomSkillFactor", "CharacterGetRandomSkillFactorPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "CanEat", "PlayerCanEatPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "EatFood", "PlayerEatFoodPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "ConsumeItem", "PlayerConsumeItemNoConsumptionPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "UpdateFood", "PlayerUpdateFoodPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "UpdateFood", "PlayerUpdateFoodPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "UpdatePlacement", "DurabilitySnapshotPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "UpdatePlacement", "DurabilitySnapshotPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "GetTotalFoodValue", "PlayerGetTotalFoodValuePostfix", postfix: true, optional: true); Type type2 = AccessTools.TypeByName("ZInput"); MethodInfo methodInfo = ((type2 == null) ? null : AccessTools.Method(type2, "GetButtonDown", new Type[1] { typeof(string) }, (Type[])null)); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "ZInputGetButtonDownPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ZInput.GetButtonDown(string). GCO roll key will not suppress overlapping vanilla button-down actions."); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Player stamina/trait/dodge/food/input hooks for negative stamina, one-button roll, and player-system tuning."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Player type. Negative stamina and player trait tuning are unavailable."); } Type type3 = AccessTools.TypeByName("Humanoid"); if (type3 != null) { PatchDeclaredMethods(type3, "GetAttackDrawPercentage", "PlayerBowDrawPercentagePostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "GetAttackSpeedFactorMovement", "HumanoidGetAttackSpeedFactorMovementPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "CheckRun", "HumanoidCheckRunWhileBlockingPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "UpdateBlock", "HumanoidUpdateBlockPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "UpdateEquipment", "DurabilitySnapshotPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "UpdateEquipment", "DurabilitySnapshotPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "SetupAnimationState", "HumanoidSetupAnimationStatePostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "DrainEquipedItemDurability", "HumanoidDrainEquippedItemDurabilityPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "BlockAttack", "DurabilitySnapshotPrefix", postfix: false, optional: true); PatchHumanoidBlockAttack(type3); PatchDeclaredMethods(type3, "BlockAttack", "DurabilitySnapshotPostfix", postfix: true, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Humanoid type. Bow draw-speed tuning is unavailable."); } Type type4 = AccessTools.TypeByName("Character"); if (type4 != null) { PatchDeclaredMethods(type4, "Awake", "CharacterAwakePostfix", postfix: true, optional: true); PatchDeclaredMethods(type4, "OnDestroy", "CharacterOnDestroyPostfix", postfix: true, optional: true); PatchDeclaredMethods(type4, "CustomFixedUpdate", "CharacterCustomFixedUpdatePrefix", postfix: false, optional: true); PatchDeclaredMethods(type4, "CustomFixedUpdate", "CharacterCustomFixedUpdatePostfix", postfix: true, optional: true); PatchDeclaredMethods(type4, "UpdateRotation", "CharacterUpdateRotationPrefix", postfix: false, optional: true); PatchDeclaredMethods(type4, "UpdateWalking", "CharacterUpdateWalkingBlockingMovementPrefix", postfix: false, optional: true); PatchDeclaredMethods(type4, "UpdateWalking", "CharacterUpdateWalkingBlockingMovementPostfix", postfix: true, optional: true); PatchDeclaredMethods(type4, "UpdateStagger", "CharacterUpdateStaggerPrefix", postfix: false, optional: true); PatchDeclaredMethods(type4, "GetRandomSkillFactor", "CharacterGetRandomSkillFactorPostfix", postfix: true, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character type. Enemy/player size, rotation, and stagger-decay tuning are unavailable."); } Type type5 = AccessTools.TypeByName("CharacterAnimEvent"); if (type5 != null) { PatchDeclaredMethods(type5, "CustomFixedUpdate", "CharacterAnimEventCustomFixedUpdatePostfix", postfix: true, optional: true); PatchDeclaredMethods(type5, "Speed", "CharacterAnimEventSpeedPrefix", postfix: false, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find CharacterAnimEvent type. Roll animation-speed tuning is unavailable."); } Type type6 = AccessTools.TypeByName("ItemDrop+ItemData") ?? AccessTools.TypeByName("ItemDrop.ItemData"); if (type6 != null) { PatchDeclaredMethods(type6, "GetWeaponLoadingTime", "ItemDataGetWeaponLoadingTimePostfix", postfix: true, optional: true); PatchSpecificMethod(type6, "GetMaxDurability", new Type[1] { typeof(int) }, "ItemDataGetMaxDurabilityPostfix", postfix: true, optional: true); PatchDeclaredMethods(type6, "GetTooltip", "ItemDataGetTooltipPostfix", postfix: true, optional: true); PatchSpecificMethod(type6, "GetBaseBlockPower", new Type[1] { typeof(int) }, "ItemDataGetBaseBlockPowerPostfix", postfix: true, optional: true); PatchSpecificMethod(type6, "GetDeflectionForce", new Type[1] { typeof(int) }, "ItemDataGetDeflectionForcePostfix", postfix: true, optional: true); PatchSpecificMethod(type6, "GetArmor", new Type[2] { typeof(int), typeof(float) }, "ItemDataGetArmorPostfix", postfix: true, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find ItemDrop.ItemData type. Crossbow reload-time tuning is unavailable."); } Type type7 = AccessTools.TypeByName("SEMan"); if (type7 != null) { PatchDeclaredMethods(type7, "ApplyArmorMods", "SEManApplyArmorModsPrefix", postfix: false, optional: true); PatchDeclaredMethods(type7, "ModifyStaminaRegen", "SEManModifyStaminaRegenPostfix", postfix: true, optional: true); PatchDeclaredMethods(type7, "ModifyHealthRegen", "SEManModifyHealthRegenPostfix", postfix: true, optional: true); PatchDeclaredMethods(type7, "ModifyFallDamage", "SEManModifyFallDamagePostfix", postfix: true, optional: true); PatchDeclaredMethods(type7, "ModifyRunStaminaDrain", "SEManModifyRunStaminaDrainPostfix", postfix: true, optional: true); PatchDeclaredMethods(type7, "ModifyBlockStaminaUsage", "SEManModifyBlockStaminaUsagePostfix", postfix: true, optional: true); PatchDeclaredMethods(type7, "ModifyTimedBlockBonus", "SEManModifyParryBonusPostfix", postfix: true, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find SEMan type. Player regen/healing/fall/block/base-armor tuning is partly unavailable."); } Type type8 = AccessTools.TypeByName("StatusEffect"); if (type8 != null) { PatchDeclaredMethods(type8, "UpdateStatusEffect", "StatusEffectUpdateStatusEffectPostfix", postfix: true, optional: true); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find StatusEffect type. Permanent rested/mead buff timers are unavailable."); } Type type9 = AccessTools.TypeByName("SE_Stats"); if (type9 != null) { PatchDeclaredMethods(type9, "ModifyStaminaRegen", "SEStatsModifyStaminaRegenPrefix", postfix: false, optional: true); PatchDeclaredMethods(type9, "ModifyStaminaRegen", "SEStatsModifyStaminaRegenPostfix", postfix: true, optional: true); PatchDeclaredMethods(type9, "ModifyEitrRegen", "SEStatsModifyEitrRegenPrefix", postfix: false, optional: true); PatchDeclaredMethods(type9, "ModifyEitrRegen", "SEStatsModifyEitrRegenPostfix", postfix: true, optional: true); PatchDeclaredMethods(type9, "ModifyHealthRegen", "SEStatsModifyHealthRegenPrefix", postfix: false, optional: true); PatchDeclaredMethods(type9, "ModifyHealthRegen", "SEStatsModifyHealthRegenPostfix", postfix: true, optional: true); } Type type10 = AccessTools.TypeByName("SE_Shield"); if (type10 != null) { PatchDeclaredMethods(type10, "SetLevel", "SEShieldSetLevelPostfix", postfix: true, optional: true); PatchDeclaredMethods(type10, "OnDamaged", "SEShieldOnDamagedPrefix", postfix: false, optional: true); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Character), "AddStaggerDamage", new Type[3] { typeof(float), typeof(Vector3), typeof(HitData) }, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterAddStaggerDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Character.AddStaggerDamage for blocking break-threshold tuning."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Character.AddStaggerDamage(float, Vector3, HitData). BlockBreakThresholdMultiplier is unavailable."); } } private void PatchStaffSpawnAndProjectileSystems() { Type type = AccessTools.TypeByName("SpawnAbility"); if (type != null) { PatchDeclaredMethods(type, "Setup", "SpawnAbilitySetupPrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "Setup", "SpawnAbilitySetupPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "Spawn", "SpawnAbilitySpawnPostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "SetupProjectile", "SpawnAbilitySetupProjectilePostfix", postfix: true, optional: true); PatchDeclaredMethods(type, "SetupAoe", "SpawnAbilitySetupAoePrefix", postfix: false, optional: true); PatchDeclaredMethods(type, "SetupAoe", "SpawnAbilitySetupAoePostfix", postfix: true, optional: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched SpawnAbility for direct staff-spawn origin propagation."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find SpawnAbility type. StaffGreenRoots/Dead Raiser spawn-origin propagation is unavailable."); } Type type2 = AccessTools.TypeByName("Projectile"); if (type2 != null) { PatchDeclaredMethods(type2, "Setup", "ProjectileSetupPostfix", postfix: true, optional: true); PatchDeclaredMethods(type2, "FindHitObject", "ProjectileFindHitObjectPostfix", postfix: true, optional: true); PatchDeclaredMethods(type2, "OnHit", "ProjectileOnHitPrefix", postfix: false, optional: true); PatchDeclaredMethods(type2, "OnHit", "ProjectileOnHitPostfix", postfix: true, optional: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Projectile for exact attack/staff origin propagation."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Projectile type. Delayed projectile attack-context propagation is unavailable."); } Type type3 = AccessTools.TypeByName("Aoe"); if (type3 != null) { PatchDeclaredMethods(type3, "Setup", "AoeSetupPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "OnHit", "AoeOnHitPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "OnHit", "AoeOnHitPostfix", postfix: true, optional: true); PatchDeclaredMethods(type3, "DoDamage", "AoeOnHitPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "DoDamage", "AoeOnHitPostfix", postfix: true, optional: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Aoe for staff-spawn origin propagation."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Aoe type. Staff spawned-AOE origin propagation is unavailable."); } Type type4 = AccessTools.TypeByName("BaseAI"); if (type4 != null) { PatchDeclaredMethods(type4, "FindEnemy", "BaseAIFindEnemyPostfix", postfix: true, optional: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched BaseAI.FindEnemy for marked Staff of the Wild / Dead Raiser PvP targeting."); } } private void PatchSpecificMethod(Type type, string methodName, Type[] parameters, string patchName, bool postfix, bool optional = false) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, methodName, parameters, (Type[])null); if (methodInfo == null) { if (!optional) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find " + type.Name + "." + methodName + ". Patch " + patchName + " is unavailable on this Valheim build.")); } return; } HarmonyMethod val = new HarmonyMethod(typeof(GooCombatOverhaulPlugin), patchName, (Type[])null); if (postfix) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + type.Name + "." + methodName + "(" + string.Join(", ", parameters.Select((Type p) => p.Name)) + ") with " + patchName + ".")); } private void PatchDeclaredMethods(Type type, string methodName, string patchName, bool postfix, bool optional = false) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == methodName select m).ToList(); if (list.Count == 0) { if (!optional) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find " + type.Name + "." + methodName + ". Patch " + patchName + " is unavailable on this Valheim build.")); } return; } foreach (MethodInfo item in list) { HarmonyMethod val = new HarmonyMethod(typeof(GooCombatOverhaulPlugin), patchName, (Type[])null); if (postfix) { _harmony.Patch((MethodBase)item, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)item, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} {type.Name}.{methodName} overload(s) with {patchName}."); } private static CharacterRuntimeDefaults GetOrCaptureCharacterRuntimeDefaults(Character character) { if (!CharacterRuntimeDefaultsByCharacter.TryGetValue(character, out CharacterRuntimeDefaults value)) { value = new CharacterRuntimeDefaults(character); CharacterRuntimeDefaultsByCharacter[character] = value; } return value; } private static void RestoreAllCharacterRuntimeTweaks() { foreach (KeyValuePair item in CharacterRuntimeDefaultsByCharacter.ToList()) { if ((Object)(object)item.Key != (Object)null) { RestoreCharacterRuntimeTweaks(item.Key, item.Value); } } } private static void RestoreCharacterRuntimeTweaks(Character character, CharacterRuntimeDefaults defaults) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)character == (Object)null)) { Vector3 val = ((Component)character).transform.localScale - defaults.LocalScale; if (((Vector3)(ref val)).sqrMagnitude > 1E-06f) { ((Component)character).transform.localScale = defaults.LocalScale; } character.m_speed = defaults.JogSpeed; character.m_walkSpeed = defaults.WalkSpeed; character.m_runSpeed = defaults.RunSpeed; character.m_turnSpeed = defaults.TurnSpeed; character.m_runTurnSpeed = defaults.RunTurnSpeed; TrySetCharacterAnimationSpeed(character, 1f); CharacterAnimationSpeedByCharacter.Remove(character); } } private static void TrySetCharacterAnimationSpeed(Character character, float speed) { if ((Object)(object)character == (Object)null) { return; } speed = Mathf.Clamp(speed, 0.05f, 5f); if (CharacterAnimationSpeedByCharacter.TryGetValue(character, out var value) && Mathf.Approximately(value, speed)) { return; } bool flag = false; try { ZSyncAnimation component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null) { component.SetSpeed(speed); flag = true; } } catch { } if (!flag) { try { Animator[] componentsInChildren = ((Component)character).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if ((Object)(object)val != (Object)null) { val.speed = speed; flag = true; } } } catch { } } CharacterAnimationSpeedByCharacter[character] = speed; } private static PlayerRuntimeDefaults GetOrCapturePlayerRuntimeDefaults(Player player) { if (!PlayerRuntimeDefaultsByPlayer.TryGetValue(player, out PlayerRuntimeDefaults value)) { value = new PlayerRuntimeDefaults(player); PlayerRuntimeDefaultsByPlayer[player] = value; if (Debug(DebugPlayerSystems)) { string key = "capture-player-defaults:" + ((Object)player).GetInstanceID(); string[] obj = new string[23] { "Captured player runtime defaults for ", SafeName((Character)(object)player), ": jumpForce=", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; float jumpForce = value.JumpForce; obj[3] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[4] = ", jumpStamina="; jumpForce = value.JumpStaminaUsage; obj[5] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[6] = ", runSpeed="; jumpForce = value.RunSpeed; obj[7] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[8] = ", jogSpeed="; jumpForce = value.JogSpeed; obj[9] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[10] = ", walkSpeed="; jumpForce = value.WalkSpeed; obj[11] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[12] = ", crouchSpeed="; jumpForce = value.CrouchSpeed; obj[13] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[14] = ", runDrain="; jumpForce = value.RunStaminaDrain; obj[15] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[16] = ", crouchDrain="; jumpForce = value.SneakStaminaDrain; obj[17] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[18] = ", dodgeCost="; jumpForce = value.DodgeStaminaUsage; obj[19] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[20] = ", staminaRegen="; jumpForce = value.StaminaRegen; obj[21] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[22] = "."; DebugLogThrottled(key, 10f, string.Concat(obj)); } } return value; } private static void ApplyConfiguredStaminaRegenCurve(Player player, PlayerRuntimeDefaults defaults) { float num = Mathf.Clamp(StaminaRecoveryRate.Value, 0f, 10f); float num2 = Mathf.Max(1f, ((Character)player).GetMaxStamina()); float num3 = Mathf.Clamp01(GetPlayerStamina(player) / num2); float num4 = Mathf.Max(0f, defaults.StaminaRegen); float staminaRegenTimeMultiplier = defaults.StaminaRegenTimeMultiplier; player.m_staminaRegen = StaminaRegenCurve.Value switch { StaminaRegenCurveMode.Constant => 9f, StaminaRegenCurveMode.Reverted => num4 + num3 * num4 * staminaRegenTimeMultiplier, _ => num4 + (1f - num3) * num4 * staminaRegenTimeMultiplier, } * num; player.m_staminaRegenTimeMultiplier = 0f; } public static void PlayerUpdatePrefix(Player __instance) { if (ModActive && !((Object)(object)__instance == (Object)null)) { ProcessOneButtonRollInput(__instance); } } private static void ProcessOneButtonRollInput(Player player) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || Roll == null || (int)Roll.Value == 0 || (Object)(object)player != (Object)(object)Player.m_localPlayer || !GcoPlayerCanTakeGameplayInput(player) || !IsConfiguredKeyDown(Roll, "roll")) { return; } RegisterRollInputSuppressionForCurrentFrame(Roll.Value); Vector3 val = ResolveOneButtonRollDirection(player); if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { return; } try { (GetCachedMethod(((object)player).GetType(), "Dodge", new Type[1] { typeof(Vector3) }) ?? GetCachedMethod(typeof(Player), "Dodge", new Type[1] { typeof(Vector3) }))?.Invoke(player, new object[1] { ((Vector3)(ref val)).normalized }); } catch (Exception ex) { try { Log.LogDebug((object)("Failed to queue GCO one-button roll: " + ex.Message)); } catch { } } } public static bool ZInputGetButtonDownPrefix(string name, ref bool __result) { if (string.IsNullOrWhiteSpace(name)) { return true; } if (SuppressedButtonDownFrame != Time.frameCount) { return true; } if (!SuppressedButtonDownNamesThisFrame.Contains(name)) { return true; } __result = false; return false; } private static Type? GetZInputRuntimeType() { if (CachedZInputLookedUp) { return CachedZInputType; } CachedZInputLookedUp = true; CachedZInputType = AccessTools.TypeByName("ZInput"); return CachedZInputType; } private static bool TryZInputGetKeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if ((int)key == 0) { return false; } try { Type zInputRuntimeType = GetZInputRuntimeType(); if (zInputRuntimeType == null) { return false; } if ((object)CachedZInputGetKeyDownMethod == null) { CachedZInputGetKeyDownMethod = AccessTools.Method(zInputRuntimeType, "GetKeyDown", new Type[2] { typeof(KeyCode), typeof(bool) }, (Type[])null); } if (CachedZInputGetKeyDownMethod == null) { return false; } object obj = CachedZInputGetKeyDownMethod.Invoke(null, new object[2] { key, false }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool TryZInputGetButton(string buttonName) { if (string.IsNullOrWhiteSpace(buttonName)) { return false; } try { Type zInputRuntimeType = GetZInputRuntimeType(); if (zInputRuntimeType == null) { return false; } if ((object)CachedZInputGetButtonMethod == null) { CachedZInputGetButtonMethod = AccessTools.Method(zInputRuntimeType, "GetButton", new Type[1] { typeof(string) }, (Type[])null); } if (CachedZInputGetButtonMethod == null) { return false; } object obj = CachedZInputGetButtonMethod.Invoke(null, new object[1] { buttonName }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static void RegisterRollInputSuppressionForCurrentFrame(KeyCode rollKey) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected I4, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 if (SuppressedButtonDownFrame != Time.frameCount) { SuppressedButtonDownFrame = Time.frameCount; SuppressedButtonDownNamesThisFrame.Clear(); } if ((int)rollKey <= 113) { if ((int)rollKey != 32) { switch (rollKey - 99) { case 0: SuppressedButtonDownNamesThisFrame.Add("ToggleWalk"); return; case 3: SuppressedButtonDownNamesThisFrame.Add("GP"); return; case 2: SuppressedButtonDownNamesThisFrame.Add("Use"); SuppressedButtonDownNamesThisFrame.Add("TabRight"); return; case 1: return; } if ((int)rollKey == 113) { SuppressedButtonDownNamesThisFrame.Add("AutoRun"); SuppressedButtonDownNamesThisFrame.Add("TabLeft"); } } else { SuppressedButtonDownNamesThisFrame.Add("Jump"); } } else if ((int)rollKey != 114) { if ((int)rollKey != 120) { if (rollKey - 305 <= 1) { SuppressedButtonDownNamesThisFrame.Add("Crouch"); } } else { SuppressedButtonDownNamesThisFrame.Add("Sit"); } } else { SuppressedButtonDownNamesThisFrame.Add("Hide"); } } private static Vector3 ResolveOneButtonRollDirection(Player player) { //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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_008d: 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_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Character)player).GetLookDir(); val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = ((Component)player).transform.forward; } val.y = 0f; ((Vector3)(ref val)).Normalize(); Vector3 val2 = Vector3.Cross(Vector3.up, val); if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { val2 = ((Component)player).transform.right; } val2.y = 0f; ((Vector3)(ref val2)).Normalize(); Vector3 val3 = Vector3.zero; try { if (TryZInputGetButton("Forward")) { val3 += val; } } catch { } try { if (TryZInputGetButton("Backward")) { val3 -= val; } } catch { } try { if (TryZInputGetButton("Left")) { val3 -= val2; } } catch { } try { if (TryZInputGetButton("Right")) { val3 += val2; } } catch { } if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { try { if (GetFieldValueRaw(player, "m_moveDir") is Vector3 val4) { val4.y = 0f; if (((Vector3)(ref val4)).sqrMagnitude >= 0.0001f) { val3 = val4; } } } catch { } } if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { val3 = -val; } val3.y = 0f; return ((Vector3)(ref val3)).normalized; } public static void PlayerUpdateStatsPrefix(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { PlayerRuntimeDefaults orCapturePlayerRuntimeDefaults = GetOrCapturePlayerRuntimeDefaults(__instance); bool modActive = ModActive; __instance.m_baseHP = (modActive ? Mathf.Max(1f, PlayerBaseHealth.Value) : 25f); __instance.m_baseStamina = (modActive ? Mathf.Max(1f, PlayerBaseStamina.Value) : 75f); __instance.m_maxCarryWeight = (modActive ? Mathf.Max(0f, PlayerBaseCarryWeight.Value) : 300f); __instance.m_eiterRegen = (modActive ? (5f * Mathf.Clamp(PlayerBaseEitrRegenRate.Value, 0f, 10f)) : 5f); if (modActive) { ((Character)__instance).m_jumpForce = orCapturePlayerRuntimeDefaults.JumpForce * Mathf.Clamp(PlayerJumpForceMultiplier.Value, 0.05f, 10f); ((Character)__instance).m_jumpStaminaUsage = orCapturePlayerRuntimeDefaults.JumpStaminaUsage * Mathf.Clamp(JumpStaminaConsumptionRate.Value, 0f, 10f); __instance.m_runStaminaDrain = orCapturePlayerRuntimeDefaults.RunStaminaDrain * Mathf.Clamp(RunStaminaConsumptionRate.Value, 0f, 10f); __instance.m_dodgeStaminaUsage = orCapturePlayerRuntimeDefaults.DodgeStaminaUsage * Mathf.Clamp(RollStaminaConsumptionRate.Value, 0f, 10f); __instance.m_sneakStaminaDrain = orCapturePlayerRuntimeDefaults.SneakStaminaDrain * Mathf.Clamp(CrouchStaminaConsumptionRate.Value, 0f, 10f); ((Character)__instance).m_runSpeed = orCapturePlayerRuntimeDefaults.RunSpeed * Mathf.Clamp(PlayerRunSpeedMultiplier.Value, 0.05f, 10f); ((Character)__instance).m_speed = orCapturePlayerRuntimeDefaults.JogSpeed * Mathf.Clamp(PlayerJogSpeedMultiplier.Value, 0.05f, 10f); ((Character)__instance).m_walkSpeed = orCapturePlayerRuntimeDefaults.WalkSpeed * Mathf.Clamp(PlayerWalkSpeedMultiplier.Value, 0.05f, 10f); ((Character)__instance).m_crouchSpeed = orCapturePlayerRuntimeDefaults.CrouchSpeed * Mathf.Clamp(PlayerCrouchSpeedMultiplier.Value, 0.05f, 10f); ApplyConfiguredStaminaRegenCurve(__instance, orCapturePlayerRuntimeDefaults); } else { ((Character)__instance).m_jumpForce = orCapturePlayerRuntimeDefaults.JumpForce; ((Character)__instance).m_jumpStaminaUsage = orCapturePlayerRuntimeDefaults.JumpStaminaUsage; __instance.m_runStaminaDrain = orCapturePlayerRuntimeDefaults.RunStaminaDrain; __instance.m_dodgeStaminaUsage = orCapturePlayerRuntimeDefaults.DodgeStaminaUsage; __instance.m_sneakStaminaDrain = orCapturePlayerRuntimeDefaults.SneakStaminaDrain; ((Character)__instance).m_runSpeed = orCapturePlayerRuntimeDefaults.RunSpeed; ((Character)__instance).m_speed = orCapturePlayerRuntimeDefaults.JogSpeed; ((Character)__instance).m_walkSpeed = orCapturePlayerRuntimeDefaults.WalkSpeed; ((Character)__instance).m_crouchSpeed = orCapturePlayerRuntimeDefaults.CrouchSpeed; __instance.m_staminaRegen = orCapturePlayerRuntimeDefaults.StaminaRegen; __instance.m_staminaRegenTimeMultiplier = orCapturePlayerRuntimeDefaults.StaminaRegenTimeMultiplier; } if ((!modActive || !EnableNegativeStamina.Value) && GetPlayerStamina(__instance) < 0f) { SetPlayerStamina(__instance, 0f); } if (Debug(DebugPlayerSystems)) { string key = "player-traits:" + ((Object)__instance).GetInstanceID(); object[] obj = new object[17] { modActive, SafeName((Character)(object)__instance), __instance.m_baseHP.ToString(CultureInfo.InvariantCulture), __instance.m_baseStamina.ToString(CultureInfo.InvariantCulture), __instance.m_maxCarryWeight.ToString(CultureInfo.InvariantCulture), ((Character)__instance).m_jumpForce.ToString(CultureInfo.InvariantCulture), null, null, null, null, null, null, null, null, null, null, null }; float jumpForce = orCapturePlayerRuntimeDefaults.JumpForce; obj[6] = jumpForce.ToString(CultureInfo.InvariantCulture); obj[7] = ((Character)__instance).m_jumpStaminaUsage.ToString(CultureInfo.InvariantCulture); obj[8] = ((Character)__instance).m_runSpeed.ToString(CultureInfo.InvariantCulture); obj[9] = ((Character)__instance).m_speed.ToString(CultureInfo.InvariantCulture); obj[10] = ((Character)__instance).m_walkSpeed.ToString(CultureInfo.InvariantCulture); obj[11] = ((Character)__instance).m_crouchSpeed.ToString(CultureInfo.InvariantCulture); obj[12] = __instance.m_runStaminaDrain.ToString(CultureInfo.InvariantCulture); obj[13] = __instance.m_sneakStaminaDrain.ToString(CultureInfo.InvariantCulture); obj[14] = __instance.m_dodgeStaminaUsage.ToString(CultureInfo.InvariantCulture); obj[15] = __instance.m_staminaRegen.ToString(CultureInfo.InvariantCulture); obj[16] = StaminaRegenCurve.Value; DebugLogThrottled(key, 10f, string.Format("Player traits active={0} for {1}: baseHP={2}, baseStamina={3}, carry={4}, jumpForce={5} (captured {6}), jumpStamina={7}, runSpeed={8}, jogSpeed={9}, walkSpeed={10}, crouchSpeed={11}, runDrain={12}, crouchDrain={13}, dodgeCost={14}, staminaRegen={15}, staminaCurve={16}.", obj)); } } } public static void PlayerUpdateModifiersPostfix(Player __instance) { if (ModActive && !((Object)(object)__instance == (Object)null)) { float num = 0f; num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_rightItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_leftItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_chestItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_legItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_helmetItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_shoulderItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_utilityItem")); num += GetConfiguredMovementModifier(GetEquippedItem(__instance, "m_trinketItem")); if (TrySetEquipmentModifierValue(__instance, 0, num) && Debug(DebugPlayerSystems)) { DebugLogThrottled("movement-modifier-override:" + ((Object)__instance).GetInstanceID(), 2f, "Movement modifier override for " + SafeName((Character)(object)__instance) + ": final equipment movement modifier=" + num.ToString(CultureInfo.InvariantCulture) + "."); } } } public static bool PlayerAppendEquipmentModifierTooltipsPrefix(Player __instance, ItemData item, StringBuilder sb) { if (!ModActive || (Object)(object)__instance == (Object)null || item == null || sb == null) { return true; } FieldInfo[] staticFieldValue = GetStaticFieldValue(typeof(Player), "s_equipmentModifierSourceFields"); string[] staticFieldValue2 = GetStaticFieldValue(typeof(Player), "s_equipmentModifierTooltips"); if (staticFieldValue == null || staticFieldValue2 == null || staticFieldValue.Length == 0 || staticFieldValue2.Length != staticFieldValue.Length) { return true; } for (int i = 0; i < staticFieldValue.Length; i++) { object obj = null; try { obj = staticFieldValue[i].GetValue(item.m_shared); } catch { continue; } if (!(obj is float configuredMovementModifier)) { continue; } if (i == 0) { configuredMovementModifier = GetConfiguredMovementModifier(item); if (Mathf.Approximately(configuredMovementModifier, 0f)) { continue; } } else if (Mathf.Approximately(configuredMovementModifier, 0f)) { continue; } float equipmentModifierPlusSE = GetEquipmentModifierPlusSE(__instance, i, configuredMovementModifier); if (i >= 10) { sb.AppendFormat($"\n{staticFieldValue2[i]}: {configuredMovementModifier}"); continue; } sb.AppendFormat("\n" + staticFieldValue2[i] + ": " + (configuredMovementModifier * 100f).ToString("+0;-0", CultureInfo.InvariantCulture) + "%"); if (!Mathf.Approximately(equipmentModifierPlusSE, configuredMovementModifier)) { sb.AppendFormat(" ($item_total:" + (equipmentModifierPlusSE * 100f).ToString("+0;-0", CultureInfo.InvariantCulture) + "%)"); } } return false; } public static void ItemDataGetBaseBlockPowerPostfix(object __instance, ref float __result) { if (!ModActive) { return; } ItemData val = (ItemData)((__instance is ItemData) ? __instance : null); if (val != null && !Mathf.Approximately(__result, 0f)) { float num = GetConfiguredBlockArmorMultiplier(val); if (CurrentPvpBlockArmorContextDepth > 0 && CurrentPvpBlockArmorContextBlocker == val) { num *= CurrentPvpBlockArmorContextMultiplier; } if (!Mathf.Approximately(num, 1f)) { __result *= num; } } } public static void ItemDataGetDeflectionForcePostfix(object __instance, ref float __result) { if (!ModActive) { return; } ItemData val = (ItemData)((__instance is ItemData) ? __instance : null); if (val != null && !Mathf.Approximately(__result, 0f)) { float configuredBlockForceMultiplier = GetConfiguredBlockForceMultiplier(val); if (!Mathf.Approximately(configuredBlockForceMultiplier, 1f)) { __result *= configuredBlockForceMultiplier; } } } public static void ItemDataGetArmorPostfix(object __instance, ref float __result) { if (!ModActive) { return; } ItemData val = (ItemData)((__instance is ItemData) ? __instance : null); if (val != null && !Mathf.Approximately(__result, 0f)) { float configuredArmorBaseMultiplier = GetConfiguredArmorBaseMultiplier(val); if (!Mathf.Approximately(configuredArmorBaseMultiplier, 1f)) { __result *= configuredArmorBaseMultiplier; } } } private static float GetConfiguredArmorBaseMultiplier(ItemData? item) { if (item == null || item.m_shared == null || !IsArmorLikeItem(item)) { return 1f; } float movementModifier = item.m_shared.m_movementModifier; if (movementModifier >= -0.005f) { return Mathf.Clamp(LightArmorBaseArmorMultiplier.Value, 0f, 20f); } if (movementModifier > -0.035f) { return Mathf.Clamp(MediumArmorBaseArmorMultiplier.Value, 0f, 20f); } return Mathf.Clamp(HeavyArmorBaseArmorMultiplier.Value, 0f, 20f); } private static bool IsArmorLikeItem(ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return false; } if ((int)item.m_shared.m_itemType != 6 && (int)item.m_shared.m_itemType != 7 && (int)item.m_shared.m_itemType != 11 && (int)item.m_shared.m_itemType != 17 && (int)item.m_shared.m_itemType != 18) { return (int)item.m_shared.m_itemType == 24; } return true; } private static float GetConfiguredBlockArmorMultiplier(ItemData? item) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (item == null || item.m_shared == null) { return 1f; } float num; StaffBlockConfig cfg; if ((int)item.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(item).BlockArmorMultiplier.Value, 0f, 20f); } else if (TryGetStaffBlockConfig(item, out cfg)) { num = Mathf.Clamp(cfg.BlockArmorMultiplier.Value, 0f, 20f); } else { WeaponCategory category = ClassifyWeapon(item); num = (IsFeatureApplied(category, ModFeature.Blocking) ? Mathf.Clamp(GetConfig(category).BlockArmorMultiplier.Value, 0f, 20f) : 1f); } return num * ((GlobalBlockArmorMultiplier != null) ? Mathf.Clamp(GlobalBlockArmorMultiplier.Value, 0f, 20f) : 1f); } private static float GetConfiguredBlockForceMultiplier(ItemData? item) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (item == null || item.m_shared == null) { return 1f; } StaffBlockConfig cfg; float num; if ((int)item.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(item).BlockForceMultiplier.Value, 0f, 20f); } else if (TryGetStaffBlockConfig(item, out cfg)) { num = Mathf.Clamp(cfg.BlockForceMultiplier.Value, 0f, 20f); } else { WeaponCategory category = ClassifyWeapon(item); num = (IsFeatureApplied(category, ModFeature.Blocking) ? Mathf.Clamp(GetConfig(category).BlockForceMultiplier.Value, 0f, 20f) : 1f); } num *= ((GlobalBlockForceMultiplier != null) ? Mathf.Clamp(GlobalBlockForceMultiplier.Value, 0f, 20f) : 1f); if (CurrentPvpBlockArmorContextDepth > 0 && CurrentPvpBlockArmorContextBlocker == item) { num *= Mathf.Clamp(CurrentPvpBlockForceContextMultiplier, 0f, 20f); } return num; } public static void ItemDataGetMaxDurabilityPostfix(object __instance, int quality, ref float __result) { if (!ModActive) { return; } ItemData val = (ItemData)((__instance is ItemData) ? __instance : null); if (val != null && val.m_shared != null && !(__result <= 0f)) { float baseDurabilityMultiplier = GetBaseDurabilityMultiplier(val); float durabilityMultiplier = GetDurabilityMultiplier(val); if (!Mathf.Approximately(baseDurabilityMultiplier, 1f) || !Mathf.Approximately(durabilityMultiplier, 1f)) { float num = val.m_shared.m_maxDurability * baseDurabilityMultiplier; float num2 = (float)Mathf.Max(0, quality - 1) * val.m_shared.m_durabilityPerLevel; __result = Mathf.Max(0f, (num + num2) * durabilityMultiplier); } } } public static void ItemDataGetTooltipPostfix(object[] __args, ref string __result) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Invalid comparison between Unknown and I4 //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || string.IsNullOrEmpty(__result) || __args == null || __args.Length < 4) { return; } object obj = __args[0]; ItemData val = (ItemData)((obj is ItemData) ? obj : null); if (val == null || val.m_shared == null) { return; } ItemType itemType = val.m_shared.m_itemType; bool flag = (int)itemType == 3 || (int)itemType == 4 || (int)itemType == 14 || (int)itemType == 15 || (int)itemType == 19 || (int)itemType == 22; int num = ((__args[1] is int num2) ? num2 : val.m_quality); float num3 = ((__args[3] is float num4) ? num4 : ((float)val.m_worldLevel)); if (flag) { WeaponCategory weaponCategory = ClassifyWeapon(val); if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(val)) { weaponCategory = WeaponCategory.SledgeAlt; } float num5 = Mathf.Clamp(GetConfig(weaponCategory).GetBaseDamageMultiplier(), 0f, 10f); bool flag2 = AlwaysDoMaxDamage != null && AlwaysDoMaxDamage.Value; if (!Mathf.Approximately(num5, 1f) || flag2) { DamageTypes damage = val.GetDamage(num, num3); string tooltipString = ((DamageTypes)(ref damage)).GetTooltipString(val.m_shared.m_skillType); if (!string.IsNullOrEmpty(tooltipString) && __result.Contains(tooltipString)) { string damageTooltipStringTruncated = GetDamageTooltipStringTruncated(damage, val.m_shared.m_skillType, num5, flag2); if (!string.IsNullOrEmpty(damageTooltipStringTruncated)) { __result = __result.Replace(tooltipString, damageTooltipStringTruncated); } } } ApplyBackstabTooltipOverride(val, weaponCategory, ref __result); ApplyFlankTooltipOverride(val, weaponCategory, ref __result); ApplyLightningChainTooltipOverride(val, ref __result); } if ((int)itemType == 5 || flag) { ApplyBlockTooltipOverrides(val, num, ref __result); } ApplyDurabilityTooltipOverride(val, ref __result); } private static void ApplyDurabilityTooltipOverride(ItemData item, ref string tooltip) { if (ModActive && item != null && item.m_shared != null && ItemUsesDurability(item)) { bool unbreakable; float durabilityConsumptionMultiplier = GetDurabilityConsumptionMultiplier(item, out unbreakable); if ((unbreakable || !(durabilityConsumptionMultiplier > 0.0001f)) && tooltip.IndexOf("Unbreakable", StringComparison.OrdinalIgnoreCase) < 0) { tooltip += "\nUnbreakable"; } } } private static void ApplyBackstabTooltipOverride(ItemData item, WeaponCategory category, ref string tooltip) { if (item?.m_shared != null && !IsAbyssalHarpoon(item) && TryGetTooltipBackstabDamageMultiplier(item, category, out var multiplier)) { multiplier = Mathf.Clamp(multiplier, 1f, 100f); string text = "\n$item_backstab: " + FormatMultiplierForTooltip(item.m_shared.m_backstabBonus) + "x"; string text2 = ((multiplier > 1f) ? ("\nSneak damage: " + FormatMultiplierForTooltip(multiplier) + "x") : string.Empty); tooltip = Regex.Replace(tooltip, "\\n(?:Backstab|Sneak) damage:[^\\n]*", string.Empty); if (tooltip.Contains(text)) { tooltip = tooltip.Replace(text, text2); } else if (!string.IsNullOrEmpty(text2)) { tooltip += text2; } } } private static void ApplyFlankTooltipOverride(ItemData item, WeaponCategory category, ref string tooltip) { if (item?.m_shared != null && !string.IsNullOrEmpty(tooltip)) { tooltip = Regex.Replace(tooltip, "\\nFlank damage:[^\\n]*", string.Empty); tooltip = Regex.Replace(tooltip, "\\nPvP flank damage:[^\\n]*", string.Empty); tooltip = Regex.Replace(tooltip, "\\nPvP backstab damage:[^\\n]*", string.Empty); if (!TryGetTooltipFlankDamageMultiplier(category, pvp: false, out var multiplier)) { multiplier = 1f; } if (!Mathf.Approximately(multiplier, 1f)) { tooltip = tooltip + "\nFlank damage: " + FormatMultiplierForTooltip(multiplier) + "x"; } } } private static bool TryGetTooltipFlankDamageMultiplier(WeaponCategory category, bool pvp, out float multiplier) { multiplier = 1f; if (EnableFlankingDamage == null || !EnableFlankingDamage.Value || category == WeaponCategory.Sledge) { return false; } if (!GetConfiguredFlankingDamageEnabled(category, AttackRole.Primary1)) { return false; } float num = ((!pvp) ? ((GlobalBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalBackstabDamageMultiplier.Value, 0f, 20f) : 1f) : ((GlobalPvpBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalPvpBackstabDamageMultiplier.Value, 0f, 20f) : 1f)); float configuredFlankingDamageMultiplier = GetConfiguredFlankingDamageMultiplier(category, AttackRole.Primary1, pvp); multiplier = Mathf.Clamp(num * configuredFlankingDamageMultiplier, 0f, 100f); return true; } private static void ApplyLightningChainTooltipOverride(ItemData item, ref string tooltip) { if (item?.m_shared == null || string.IsNullOrEmpty(tooltip)) { return; } if (IsHimminAflWeapon(item)) { float num = Mathf.Clamp01((HimminAflLightningChainChance != null) ? HimminAflLightningChainChance.Value : 0.25f); string text = "\nChance to apply effect: " + (num * 100f).ToString("0.#", CultureInfo.InvariantCulture) + "%\nLightning"; tooltip = Regex.Replace(tooltip, "\\nHimmin Afl chain lightning chance:[^\\n]*", string.Empty); tooltip = Regex.Replace(tooltip, "\\nChance to apply effect:[^\\n]*(?:\\nLightning)?", string.Empty); if (num > 0f) { tooltip += text; } } else if (IsDundrWeapon(item)) { float num2 = Mathf.Clamp01((DundrLightningChainChancePerPellet != null) ? DundrLightningChainChancePerPellet.Value : 0.02f); string text2 = "\nDundr chain lightning chance per pellet: " + (num2 * 100f).ToString("0.#", CultureInfo.InvariantCulture) + "%"; tooltip = Regex.Replace(tooltip, "\\nDundr chain lightning chance per pellet:[^\\n]*", string.Empty); if (num2 > 0f) { tooltip += text2; } } } private static void ApplyBlockTooltipOverrides(ItemData item, int qualityLevel, ref string tooltip) { if (item?.m_shared == null) { return; } if (tooltip.Contains("$item_blockarmor")) { try { int num = Mathf.RoundToInt(item.GetBaseBlockPower(qualityLevel)); int num2 = Mathf.RoundToInt(item.GetBlockPowerTooltip(qualityLevel)); string replacement = $"\n$item_blockarmor: {num} ({num2})"; tooltip = Regex.Replace(tooltip, "\\n\\$item_blockarmor:[^\\n]*", replacement); } catch { } } if (Mathf.Approximately(GetConfiguredBlockForceMultiplier(item), 0f)) { string pattern = "\\n\\$item_blockforce:[^\\n]*"; tooltip = Regex.Replace(tooltip, pattern, string.Empty); } tooltip = Regex.Replace(tooltip, "\\n\\$item_parrybonus:[^\\n]*", string.Empty); if (CanBlockerParry(item)) { float num3 = GetEffectiveBaseTimedBlockBonus(item) * GetConfiguredParryBonusMultiplier(item, pvp: false); if (num3 > 1f) { tooltip = tooltip + "\n$item_parrybonus: " + FormatMultiplierForTooltip(num3) + "x"; } } float configuredParryAdrenalineMultiplier = GetConfiguredParryAdrenalineMultiplier(item); if (!Mathf.Approximately(configuredParryAdrenalineMultiplier, 1f) && item.m_shared.m_perfectBlockAdrenaline > 0f) { string text = "\n$item_parryadrenaline: " + item.m_shared.m_perfectBlockAdrenaline.ToString(CultureInfo.InvariantCulture) + ""; string newValue = "\n$item_parryadrenaline: " + FormatMultiplierForTooltip(item.m_shared.m_perfectBlockAdrenaline * configuredParryAdrenalineMultiplier) + ""; if (tooltip.Contains(text)) { tooltip = tooltip.Replace(text, newValue); } } ApplyGuardBoostTooltipOverride(item, qualityLevel, ref tooltip); } private static void ApplyGuardBoostTooltipOverride(ItemData item, int qualityLevel, ref string tooltip) { if (item?.m_shared == null || !UseSoulsLikeBlockStaminaMode(item)) { tooltip = Regex.Replace(tooltip, "\\nGuard Boost:[^\\n]*", string.Empty); return; } float num = Mathf.Clamp(GetConfiguredSoulsLikeBlockStaminaBaseRate(item), 0f, 1f); num *= ((GlobalSoulsLikeBlockStaminaBaseRateMultiplier != null) ? Mathf.Clamp(GlobalSoulsLikeBlockStaminaBaseRateMultiplier.Value, 0f, 10f) : 1f); float num2 = 0f; try { num2 = Mathf.Max(0f, item.GetBlockPowerTooltip(qualityLevel)); } catch { } float num3 = Mathf.Clamp01(num + num2 / 1000f - 0.05f) * 100f; string text = "\nGuard Boost: " + Mathf.RoundToInt(num3).ToString(CultureInfo.InvariantCulture) + "%"; if (Regex.IsMatch(tooltip, "\\nGuard Boost:[^\\n]*")) { tooltip = Regex.Replace(tooltip, "\\nGuard Boost:[^\\n]*", text); } else { tooltip += text; } } private static string FormatMultiplierForTooltip(float value) { return value.ToString("0.###", CultureInfo.InvariantCulture); } private static string GetDamageTooltipStringTruncated(DamageTypes damage, SkillType skillType, float multiplier, bool showMaxOnly) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.m_localPlayer == (Object)null) { return string.Empty; } float skillMin = default(float); float skillMax = default(float); ((Character)Player.m_localPlayer).GetSkills().GetRandomSkillRange(ref skillMin, ref skillMax, skillType); StringBuilder stringBuilder = new StringBuilder(); AppendDamageTooltipLine(stringBuilder, "$inventory_damage", damage.m_damage, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_blunt", damage.m_blunt, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_slash", damage.m_slash, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_pierce", damage.m_pierce, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_fire", damage.m_fire, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_frost", damage.m_frost, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_lightning", damage.m_lightning, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_poison", damage.m_poison, skillMin, skillMax, multiplier, showMaxOnly); AppendDamageTooltipLine(stringBuilder, "$inventory_spirit", damage.m_spirit, skillMin, skillMax, multiplier, showMaxOnly); return stringBuilder.ToString(); } private static void AppendDamageTooltipLine(StringBuilder sb, string label, float value, float skillMin, float skillMax, float multiplier, bool showMaxOnly) { if (!Mathf.Approximately(value, 0f)) { float num = value * multiplier; int num2 = Mathf.RoundToInt(num); int num3 = Mathf.RoundToInt(num * skillMin); int num4 = Mathf.RoundToInt(num * skillMax); if (showMaxOnly) { sb.Append($"\n{label}: {num2} ({num4}) "); return; } sb.Append($"\n{label}: {num2} ({num3}-{num4}) "); } } public static void AttackModifyDamagePrefix(HitData hitData, float damageFactor) { if (hitData == null || damageFactor <= 0f || float.IsNaN(damageFactor) || float.IsInfinity(damageFactor)) { return; } try { HitSkillDamageFactors.Remove(hitData); HitSkillDamageFactors.Add(hitData, new SkillDamageFactorState(damageFactor)); } catch { } } public static void CharacterGetRandomSkillFactorPostfix(Character __instance, SkillType skill, ref float __result) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || AlwaysDoMaxDamage == null || !AlwaysDoMaxDamage.Value) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return; } try { float num = default(float); float num2 = default(float); ((Character)val).GetSkills().GetRandomSkillRange(ref num, ref num2, skill); __result = num2; } catch { } } public static void PlayerSetMaxStaminaPrefix(Player __instance) { if (ModActive && EnableNegativeStamina.Value && !((Object)(object)__instance == (Object)null)) { float playerStamina = GetPlayerStamina(__instance); if (playerStamina < 0f) { NegativeStaminaBeforeMaxClamp[__instance] = playerStamina; } } } public static void PlayerSetMaxStaminaPostfix(Player __instance) { if ((Object)(object)__instance == (Object)null || !NegativeStaminaBeforeMaxClamp.TryGetValue(__instance, out var value)) { return; } NegativeStaminaBeforeMaxClamp.Remove(__instance); if (ModActive && EnableNegativeStamina.Value && GetPlayerStamina(__instance) >= 0f) { SetPlayerStamina(__instance, value); if (Debug(DebugNegativeStamina)) { DebugLogThrottled("neg-stamina-preserve:" + ((Object)__instance).GetInstanceID(), 0.5f, "Preserved negative stamina through SetMaxStamina for " + SafeName((Character)(object)__instance) + ": " + value.ToString(CultureInfo.InvariantCulture) + "."); } } } public static void PlayerSetMaxEitrPrefix(Player __instance) { if (ModActive && !((Object)(object)__instance == (Object)null) && PlayerBaseEitr != null && !(Mathf.Max(0f, PlayerBaseEitr.Value) <= 0f)) { PlayerEitrBeforeMaxClamp[__instance] = __instance.GetEitr(); } } public static void PlayerSetMaxEitrPostfix(Player __instance) { if (!ModActive || (Object)(object)__instance == (Object)null || PlayerBaseEitr == null) { return; } float num = Mathf.Max(0f, PlayerBaseEitr.Value); if (num <= 0f) { PlayerEitrBeforeMaxClamp.Remove(__instance); return; } float num2 = ((Character)__instance).GetMaxEitr() + num; float eitr = __instance.GetEitr(); float num3 = eitr; if (PlayerEitrBeforeMaxClamp.TryGetValue(__instance, out var value)) { PlayerEitrBeforeMaxClamp.Remove(__instance); num3 = Mathf.Max(eitr, value); } SetPrivateFloat(__instance, "m_maxEitr", num2); SetPrivateFloat(__instance, "m_eitr", Mathf.Clamp(num3, 0f, num2)); } public static void PlayerGetStaminaHudClampPostfix(Player __instance, ref float __result) { if (ModActive && EnableNegativeStamina.Value && !((Object)(object)__instance == (Object)null) && !(__result >= 0f) && CallStackLooksLikeHud()) { __result = 0f; } } public static void PlayerGetStaminaPercentageHudClampPostfix(Player __instance, ref float __result) { if (ModActive && EnableNegativeStamina.Value && !((Object)(object)__instance == (Object)null) && !(__result >= 0f) && CallStackLooksLikeHud()) { __result = 0f; } } public static void PlayerGetTotalFoodValuePostfix(Player __instance, ref float hp, ref float stamina, ref float eitr) { if (!ModActive || (Object)(object)__instance == (Object)null) { return; } float num = ((FoodHealthMultiplier == null) ? 1f : Mathf.Clamp(FoodHealthMultiplier.Value, 0f, 10f)); float num2 = ((FoodStaminaMultiplier == null) ? 1f : Mathf.Clamp(FoodStaminaMultiplier.Value, 0f, 10f)); float num3 = ((FoodEitrMultiplier == null) ? 1f : Mathf.Clamp(FoodEitrMultiplier.Value, 0f, 10f)); if (Mathf.Approximately(num, 1f) && Mathf.Approximately(num2, 1f) && Mathf.Approximately(num3, 1f)) { return; } try { float baseHP = __instance.m_baseHP; float baseStamina = __instance.m_baseStamina; hp = baseHP + Mathf.Max(0f, hp - baseHP) * num; stamina = baseStamina + Mathf.Max(0f, stamina - baseStamina) * num2; eitr = Mathf.Max(0f, eitr) * num3; } catch { } } public static bool PlayerCanEatPrefix(Player __instance, ItemData item, bool showMessages, ref bool __result) { if (!ModActive || MustEatUniqueFood == null || MustEatUniqueFood.Value || (Object)(object)__instance == (Object)null || item == null || item.m_shared == null || item.m_shared.m_food <= 0f) { return true; } List playerFoodList = GetPlayerFoodList(__instance); if (playerFoodList == null) { return true; } if (playerFoodList.Count < 3 || playerFoodList.Any((Food food) => food != null && food.CanEatAgain())) { __result = true; return false; } if (showMessages) { ((Character)__instance).Message((MessageType)2, "$msg_isfull", 0, (Sprite)null); } __result = false; return false; } public static bool PlayerConsumeItemNoConsumptionPrefix(Player __instance, Inventory inventory, ItemData item, bool checkWorldLevel, ref bool __result) { if (!IsNoConsumptionActiveForPlayer(__instance) || inventory == null || item == null || item.m_shared == null) { return true; } if (!(item.m_shared.m_food > 0f) && !((Object)(object)item.m_shared.m_consumeStatusEffect != (Object)null)) { return true; } if (!((Humanoid)__instance).CanConsumeItem(item, checkWorldLevel)) { __result = false; return false; } if ((Object)(object)item.m_shared.m_consumeStatusEffect != (Object)null) { try { ((Character)__instance).GetSEMan().AddStatusEffect(item.m_shared.m_consumeStatusEffect, true, 0, 0f); } catch { } } if (item.m_shared.m_food > 0f) { try { __instance.EatFood(item); } catch { } } __result = true; return false; } public static void AttackUseAmmoNoConsumptionPostfix(object __instance, bool __result, ref ItemData ammoItem) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 if (!__result || ammoItem == null || ammoItem.m_shared == null) { return; } Character? fieldValue = GetFieldValue(__instance, "m_character"); Player val = (Player)(object)((fieldValue is Player) ? fieldValue : null); if (val == null || !IsNoConsumptionActiveForPlayer(val) || (int)ammoItem.m_shared.m_itemType == 2) { return; } try { Inventory inventory = ((Humanoid)val).GetInventory(); if (inventory != null) { RestoreConsumedInventoryItem(inventory, ammoItem, 1); } } catch { } } private static bool IsNoConsumptionActiveForPlayer(Player player) { if (ModActive && NoConsumption != null && NoConsumption.Value && (Object)(object)player != (Object)null) { return (Object)(object)Player.m_localPlayer == (Object)(object)player; } return false; } private static void RestoreConsumedInventoryItem(Inventory inventory, ItemData source, int amount) { if (inventory != null && source != null && source.m_shared != null && amount > 0) { ItemData val = source.Clone(); val.m_stack = amount; val.m_equipped = false; inventory.AddItem(val); } } public static bool PlayerEatFoodPrefix(Player __instance, ItemData item, ref bool __result) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown if (!ModActive || MustEatUniqueFood == null || MustEatUniqueFood.Value || (Object)(object)__instance == (Object)null || item == null || item.m_shared == null || item.m_shared.m_food <= 0f) { return true; } List playerFoodList = GetPlayerFoodList(__instance); if (playerFoodList == null) { return true; } Food val = null; bool flag = false; if (playerFoodList.Count < 3) { val = new Food(); flag = true; } else { val = (from food in playerFoodList where food != null && food.CanEatAgain() orderby food.m_time select food).FirstOrDefault(); } if (val == null) { __result = false; return false; } string text = ""; if (item.m_shared.m_food > 0f) { text = text + " +" + item.m_shared.m_food.ToString(CultureInfo.InvariantCulture) + " $item_food_health "; } if (item.m_shared.m_foodStamina > 0f) { text = text + " +" + item.m_shared.m_foodStamina.ToString(CultureInfo.InvariantCulture) + " $item_food_stamina "; } if (item.m_shared.m_foodEitr > 0f) { text = text + " +" + item.m_shared.m_foodEitr.ToString(CultureInfo.InvariantCulture) + " $item_food_eitr "; } ((Character)__instance).Message((MessageType)2, text, 0, (Sprite)null); val.m_name = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name); val.m_item = item; val.m_time = item.m_shared.m_foodBurnTime; val.m_health = item.m_shared.m_food; val.m_stamina = item.m_shared.m_foodStamina; val.m_eitr = item.m_shared.m_foodEitr; if (flag) { playerFoodList.Add(val); } InvokePlayerUpdateFood(__instance); try { Game instance = Game.instance; if (instance != null) { instance.IncrementPlayerStat((PlayerStatType)49, 1f); } } catch { } if (Debug(DebugPlayerSystems)) { string arg = item.m_shared.m_name ?? val.m_name; DebugLogThrottled($"food-unique-disabled:{((Object)__instance).GetInstanceID()}", 0.5f, $"MustEatUniqueFood=false allowed {SafeName((Character)(object)__instance)} to eat {arg}; food slots now {playerFoodList.Count}/3."); } __result = true; return false; } private static List? GetPlayerFoodList(Player player) { try { return GetFieldValueRaw(player, "m_foods") as List; } catch { return null; } } private static void InvokePlayerUpdateFood(Player player) { try { AccessTools.Method(typeof(Player), "UpdateFood", (Type[])null, (Type[])null)?.Invoke(player, new object[2] { 0f, true }); } catch (Exception ex) { if (Debug(DebugPlayerSystems)) { DebugLogThrottled($"food-update-failed:{((Object)player).GetInstanceID()}", 2f, "Could not invoke Player.UpdateFood after custom food-slot update: " + ex.Message); } } } private static void RecalculatePlayerFoodMaxValues(Player player, bool flashBars) { if ((Object)(object)player == (Object)null) { return; } List playerFoodList = GetPlayerFoodList(player); if (playerFoodList == null) { return; } float num = player.GetBaseFoodHP(); float num2 = GetPrivateFloat(player, "m_baseStamina", 75f); float num3 = 0f; foreach (Food item in playerFoodList) { if (item != null) { num += item.m_health; num2 += item.m_stamina; num3 += item.m_eitr; } } try { player.SetMaxHealth(num, flashBars); } catch { } try { player.SetMaxStamina(num2, flashBars); } catch { } try { AccessTools.Method(typeof(Player), "SetMaxEitr", new Type[2] { typeof(float), typeof(bool) }, (Type[])null)?.Invoke(player, new object[2] { num3, flashBars }); } catch { } } public static void CharacterAwakePostfix(Character __instance) { if (!((Object)(object)__instance == (Object)null)) { EnsurePvpTestDummyBehavior(__instance); if (CurrentSpawnAbilityRuntimeOrigin != null && IsLikelyStaffSpawnedObject(((Component)__instance).gameObject, CurrentSpawnAbilityRuntimeOrigin)) { TagStaffCharacter(__instance, CurrentSpawnAbilityRuntimeOrigin); } else { TryRegisterPersistedStaffGreenRoot(__instance); } } } public static void CharacterOnDestroyPostfix(Character __instance) { if (!((Object)(object)__instance == (Object)null)) { StaffSpawnCharacterOrigins.Remove(__instance); StaffGreenRootsRuntimeTweakedCharacters.Remove(__instance); StaffSkeletonRuntimeTweakedCharacters.Remove(__instance); UnregisterStaffGreenRoot(__instance); CharacterRuntimeDefaultsByCharacter.Remove(__instance); CharacterAnimationSpeedByCharacter.Remove(__instance); StaggerDecayCooldownUntil.Remove(__instance); } } public static void CharacterCustomFixedUpdatePrefix(Character __instance) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: 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) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } EnsurePvpTestDummyBehavior(__instance); bool flag = __instance is Player; float num = (flag ? Mathf.Clamp(PlayerSizeMultiplier.Value, 0.2f, 5f) : Mathf.Clamp(EnemySizeMultiplier.Value, 0.2f, 5f)); float num2 = ((!flag && EnemySpeedMultiplier != null) ? Mathf.Clamp(EnemySpeedMultiplier.Value, 0.05f, 10f) : 1f); bool num3 = ModActive && !Mathf.Approximately(num, 1f); bool flag2 = ModActive && !flag && !Mathf.Approximately(num2, 1f); if (!num3 && !flag2) { if (CharacterRuntimeDefaultsByCharacter.TryGetValue(__instance, out CharacterRuntimeDefaults value)) { RestoreCharacterRuntimeTweaks(__instance, value); CharacterRuntimeDefaultsByCharacter.Remove(__instance); } return; } CharacterRuntimeDefaults orCaptureCharacterRuntimeDefaults = GetOrCaptureCharacterRuntimeDefaults(__instance); Vector3 val = orCaptureCharacterRuntimeDefaults.LocalScale * num; Vector3 val2 = ((Component)__instance).transform.localScale - val; if (((Vector3)(ref val2)).sqrMagnitude > 1E-06f) { ((Component)__instance).transform.localScale = val; } if (!flag) { __instance.m_speed = orCaptureCharacterRuntimeDefaults.JogSpeed * num2; __instance.m_walkSpeed = orCaptureCharacterRuntimeDefaults.WalkSpeed * num2; __instance.m_runSpeed = orCaptureCharacterRuntimeDefaults.RunSpeed * num2; } } public static void CharacterCustomFixedUpdatePostfix(Character __instance) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || __instance is Player) { return; } bool flag = CharacterAnimationSpeedByCharacter.ContainsKey(__instance); if (!ModActive || IsCharacterDead(__instance) || __instance.IsStaggering()) { if (flag) { TrySetCharacterAnimationSpeed(__instance, 1f); } } else { if (CharacterIsInAttackFast(__instance)) { return; } float num = Mathf.Clamp(((EnemySpeedMultiplier != null) ? EnemySpeedMultiplier.Value : 1f) * EnemyMovementAnimationSpeedMultiplier.Value, 0.05f, 10f); if (Mathf.Approximately(num, 1f)) { if (flag) { TrySetCharacterAnimationSpeed(__instance, 1f); } return; } Vector3 val = ((GetFieldValueRaw(__instance, "m_currentVel") is Vector3 val2) ? val2 : Vector3.zero); if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { TrySetCharacterAnimationSpeed(__instance, num); } else if (flag) { TrySetCharacterAnimationSpeed(__instance, 1f); } } } private static bool IsEnemyRotationLockedAfterHitRay(Character character) { if (!ModActive || (Object)(object)character == (Object)null || character is Player || EnemyLockRotationAfterHitRay == null || !EnemyLockRotationAfterHitRay.Value) { return false; } object fieldValueRaw = GetFieldValueRaw(character, "m_currentAttack"); if (fieldValueRaw != null) { return EnemyRotationLockedAfterHitRayAttacks.Contains(fieldValueRaw); } return false; } private static void LockEnemyRotationAfterHitRayIfConfigured(object attackInstance, Character? character, string source) { if (ModActive && attackInstance != null && !((Object)(object)character == (Object)null) && !(character is Player) && EnemyLockRotationAfterHitRay != null && EnemyLockRotationAfterHitRay.Value && CharacterReportsInAttack(character) && EnemyRotationLockedAfterHitRayAttacks.Add(attackInstance)) { SetAttackRotationFactor(attackInstance, 0f, WeaponCategory.Other, AttackRole.Primary1, "enemy post-hit-ray rotation lock"); if (Debug(DebugMovement)) { DebugLogThrottled("enemy-lock-rotation-after-hitray:" + RuntimeHelpers.GetHashCode(attackInstance), 0.25f, "Enemy rotation locked after hit ray via " + source + " for " + SafeName(character) + "."); } } } public static void CharacterUpdateRotationPrefix(Character __instance, ref float turnSpeed) { if (!ModActive || (Object)(object)__instance == (Object)null) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { float num = Mathf.Clamp(PlayerBaseRotationFactor.Value, 0f, 10f); try { if (((Character)val).IsDrawingBow() && ClassifyWeapon(GetCurrentWeapon((Character)(object)val)) == WeaponCategory.Bow) { float num2 = Mathf.Clamp((BowDrawRotationFactor != null) ? BowDrawRotationFactor.Value : 1f, 0f, 999f); float num3 = Mathf.Clamp(num * num2, 0f, 9990f); if (!Mathf.Approximately(num3, 1f)) { turnSpeed *= num3; } return; } } catch { } if (!CharacterIsInAttackFast((Character)(object)val) && !Mathf.Approximately(num, 1f)) { turnSpeed *= num; } return; } if (IsEnemyRotationLockedAfterHitRay(__instance)) { turnSpeed = 0f; return; } float num4 = Mathf.Clamp(EnemyBaseRotationFactor.Value, 0f, 10f); float num5 = Mathf.Clamp(EnemyAttackRotationFactor.Value, 0f, 10f); if (!Mathf.Approximately(num4, 1f) || !Mathf.Approximately(num5, 1f)) { if (Mathf.Approximately(num4, num5)) { turnSpeed *= num4; return; } float num6 = (CharacterIsInAttackFast(__instance) ? num5 : num4); turnSpeed *= num6; } } public static bool CharacterUpdateStaggerPrefix(Character __instance, float dt) { if (!ModActive || (Object)(object)__instance == (Object)null) { return true; } float privateFloat = GetPrivateFloat(__instance, "m_staggerDamageFactor", 0f); if (privateFloat <= 0f && !__instance.IsPlayer()) { return false; } float num = __instance.GetMaxHealth() * privateFloat; float privateFloat2 = GetPrivateFloat(__instance, "m_staggerDamage", 0f); if (privateFloat2 <= 0f || num <= 0f) { if (privateFloat2 < 0f) { SetPrivateFloat(__instance, "m_staggerDamage", 0f); } return false; } if (__instance.IsStaggering()) { return false; } float num2 = (IsPvpLikeTarget(__instance) ? Mathf.Max(0f, PlayerStaggerDecayCooldownSeconds.Value) : Mathf.Max(0f, EnemyStaggerDecayCooldownSeconds.Value)); float num3 = (IsPvpLikeTarget(__instance) ? Mathf.Clamp(PlayerStaggerDecaySpeedMultiplier.Value, 0f, 20f) : Mathf.Clamp(EnemyStaggerDecaySpeedMultiplier.Value, 0f, 20f)); if (num2 > 0f && StaggerDecayCooldownUntil.TryGetValue(__instance, out var value) && Time.time < value) { return false; } float num4 = privateFloat2 - num / 5f * num3 * dt; if (num4 < 0f) { num4 = 0f; } SetPrivateFloat(__instance, "m_staggerDamage", num4); return false; } public static void CharacterAnimEventCustomFixedUpdatePostfix(object __instance) { if (!ModActive || __instance == null) { return; } float num = Mathf.Clamp(RollAnimationSpeed.Value, 0.05f, 5f); if (Mathf.Approximately(num, 1f)) { return; } Character? fieldValue = GetFieldValue(__instance, "m_character"); Player val = (Player)(object)((fieldValue is Player) ? fieldValue : null); if (val == null || !((Character)val).InDodge()) { return; } Animator fieldValue2 = GetFieldValue(__instance, "m_animator"); if (!((Object)(object)fieldValue2 == (Object)null)) { fieldValue2.speed = num; if (Debug(DebugPlayerSystems)) { DebugLogThrottled("roll-speed:" + ((Object)val).GetInstanceID(), 1f, "RollAnimationSpeed x" + num.ToString(CultureInfo.InvariantCulture) + " applied through CharacterAnimEvent for " + SafeName((Character)(object)val) + "."); } } } public static void CharacterAnimEventSpeedPrefix(object __instance, ref float speedScale) { if (!ModActive || __instance == null) { return; } Character? fieldValue = GetFieldValue(__instance, "m_character"); Player val = (Player)(object)((fieldValue is Player) ? fieldValue : null); if (val == null) { return; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); WeaponCategory category = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); AttackRole characterAnimEventAttackRole = GetCharacterAnimEventAttackRole(val, category); object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); category = ResolveAttackSettingsCategory(fieldValueRaw, category, characterAnimEventAttackRole); if (!UseCharacterAnimEventSpeedMultiplier(category, characterAnimEventAttackRole)) { return; } float num = GetResolvedAttackAnimationSpeedMultiplier(val, category, characterAnimEventAttackRole); if (fieldValueRaw != null && RecoveryAnimationSpeedAppliedAttacks.Contains(fieldValueRaw)) { num *= GetResolvedRecoveryAnimationSpeedMultiplier(val, category, characterAnimEventAttackRole); } if (fieldValueRaw != null && BlockCancelAnimationSpeedByAttack.TryGetValue(fieldValueRaw, out var value)) { num *= value; } if (!Mathf.Approximately(num, 1f)) { speedScale = Mathf.Clamp(speedScale * Mathf.Clamp(num, 0.05f, 5f), 0.0001f, 20f); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled("anim-event-speed:" + ((Object)val).GetInstanceID(), 0.5f, $"CharacterAnimEvent.Speed scaled for {SafeName((Character)(object)val)} {category} {characterAnimEventAttackRole}: vanilla event speed x internal {num.ToString(CultureInfo.InvariantCulture)}."); } } } private static float GetResolvedAttackAnimationSpeedMultiplier(Player player, WeaponCategory category, AttackRole role) { float num = (TryGetJumpAttackAnimationSpeedMultiplier(player, category, out var multiplier) ? multiplier : ((!TryGetRunningAttackAnimationSpeedMultiplier(player, category, out var multiplier2)) ? GetConfig(category).GetAttackAnimationSpeedMultiplier(role) : multiplier2)); if (category == WeaponCategory.Unarmed && Mathf.Approximately(num, 0f)) { return 0f; } num *= ((GlobalAttackAnimationSpeedMultiplier != null) ? Mathf.Clamp(GlobalAttackAnimationSpeedMultiplier.Value, 0.05f, 10f) : 1f); return Mathf.Clamp(num, 0.05f, 5f); } private static AttackRole GetCharacterAnimEventAttackRole(Player player, WeaponCategory category) { if ((Object)(object)player == (Object)null) { return AttackRole.Primary1; } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.Category == category && Time.time <= value.EndTime && !IsCharacterDead((Character)(object)player) && (value.AttackInstance == null || fieldValueRaw == null || value.AttackInstance == fieldValueRaw)) { return value.Role; } if (PendingAttackRoles.TryGetValue((Character)(object)player, out var value2) && Time.time <= value2.EndTime && value2.Role == AttackRole.Secondary) { return AttackRole.Secondary; } return GetCurrentAttackRole((Character)(object)player, category); } public static void PlayerHaveStaminaPostfix(Player __instance, float amount, ref bool __result) { if (ModActive && EnableNegativeStamina.Value && !((Object)(object)__instance == (Object)null) && !(amount < 0f)) { float playerStamina = GetPlayerStamina(__instance); float negativeStaminaFloor = GetNegativeStaminaFloor(); NegativeStaminaActionGateMode negativeStaminaActionGateMode = ((NegativeStaminaActionGate == null) ? NegativeStaminaActionGateMode.RequireCostWithinFloor : NegativeStaminaActionGate.Value); bool flag = IsNegativeStaminaActionAllowed(playerStamina, amount, negativeStaminaFloor, negativeStaminaActionGateMode); if (__result != flag && Debug(DebugNegativeStamina)) { DebugLogThrottled("neg-stamina-gate:" + ((Object)__instance).GetInstanceID(), 0.5f, $"Negative stamina gate for {SafeName((Character)(object)__instance)}: current={playerStamina.ToString(CultureInfo.InvariantCulture)}, requested={amount.ToString(CultureInfo.InvariantCulture)}, floor={negativeStaminaFloor.ToString(CultureInfo.InvariantCulture)}, mode={negativeStaminaActionGateMode}, vanilla={__result}, modded={flag}."); } __result = flag; } } public static bool PlayerRpcUseStaminaPrefix(Player __instance, float v) { if (!ModActive || !EnableNegativeStamina.Value || (Object)(object)__instance == (Object)null) { return true; } if (v == 0f || float.IsNaN(v)) { return false; } if (v < 0f) { return true; } float playerStamina = GetPlayerStamina(__instance); float negativeStaminaFloor = GetNegativeStaminaFloor(); float value = Mathf.Max(negativeStaminaFloor, playerStamina - v); SetPlayerStamina(__instance, value); SetPrivateFloat(__instance, "m_staminaRegenTimer", __instance.m_staminaRegenDelay); if (Debug(DebugNegativeStamina)) { DebugLogThrottled("neg-stamina-spend:" + ((Object)__instance).GetInstanceID(), 0.25f, "Negative stamina spend for " + SafeName((Character)(object)__instance) + ": " + playerStamina.ToString(CultureInfo.InvariantCulture) + " - " + v.ToString(CultureInfo.InvariantCulture) + " => " + value.ToString(CultureInfo.InvariantCulture) + "; floor=" + negativeStaminaFloor.ToString(CultureInfo.InvariantCulture) + "."); } return false; } public static void PlayerUpdateDodgePostfix(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { float num = (ModActive ? Mathf.Clamp(RollAnimationSpeed.Value, 0.05f, 5f) : 1f); if (!ModActive || Mathf.Approximately(num, 1f) || !((Character)__instance).InDodge()) { RestoreDodgeAnimationSpeedTweaks(__instance); } else { ApplyDodgeAnimationSpeedIfConfigured(__instance, num); } } } public static void PlayerBowDrawPercentagePostfix(Humanoid __instance, ref float __result) { if (!ModActive) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return; } float num = Mathf.Clamp(BowDrawSpeedMultiplier.Value, 0.05f, 10f); if (!Mathf.Approximately(num, 1f) && ClassifyWeapon(GetCurrentWeapon((Character)(object)val)) == WeaponCategory.Bow) { float num2 = __result; __result = Mathf.Clamp01(__result * num); if (Debug(DebugRanged) && !Mathf.Approximately(num2, __result)) { DebugLogThrottled("bow-draw:" + ((Object)val).GetInstanceID(), 0.5f, "Bow draw speed x" + num.ToString(CultureInfo.InvariantCulture) + ": " + num2.ToString(CultureInfo.InvariantCulture) + " -> " + __result.ToString(CultureInfo.InvariantCulture) + " for " + SafeName((Character)(object)val) + "."); } } } public static void HumanoidGetAttackSpeedFactorMovementPostfix(object __instance, ref float __result) { if (!ModActive) { return; } Player val = (Player)((__instance is Player) ? __instance : null); if (val == null) { return; } bool flag; try { flag = ((Character)val).InAttack(); } catch { flag = CharacterReportsInAttack((Character)(object)val); } object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); if (!flag || fieldValueRaw == null) { if (fieldValueRaw != null && DebugSledgeAlternate() && IsSledgeAlternateMovesetActiveForWeapon(GetCurrentWeapon((Character)(object)val))) { DebugSledgeAlternateLog($"movement-native-exit:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", $"Movement resolver preserved native 1x because InAttack={flag}; retained currentAttack was not allowed to affect post-swing movement. nativeResult={__result.ToString(CultureInfo.InvariantCulture)}.", 0.5f); } } else { if (!((Character)val).IsFlying() && !((Character)val).IsOnGround()) { return; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); WeaponCategory weaponCategory = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); AttackRole attackRole = DetermineAttackRole((Character?)(object)val, fieldValueRaw, currentWeapon); WeaponCategory weaponCategory2 = ResolveAttackSettingsCategory(fieldValueRaw, weaponCategory, attackRole); if (((currentWeapon == null || !TryGetStaffBlockConfig(currentWeapon, out StaffBlockConfig _)) && !IsMeleeWeaponCategory(weaponCategory2)) || (AttackMovementGroundingModeConfig.Value == AttackMovementGroundingMode.GroundOnly && !((Character)val).IsOnGround())) { return; } if (!ShouldApplyAttackMovementOverride(fieldValueRaw, weaponCategory2, attackRole)) { if (weaponCategory2 == WeaponCategory.SledgeAlt && DebugSledgeAlternate()) { ConfigEntry value; bool flag2 = FeatureGlobalToggles.TryGetValue(ModFeature.AttackMovement, out value); ConfigEntry value2; bool flag3 = FeatureWeaponToggles.TryGetValue(FeatureToggleKey(ModFeature.AttackMovement, WeaponCategory.Sledge), out value2); DebugSledgeAlternateLog($"movement-feature-reject:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", $"Alternate-sledge movement was rejected before resolution: role={attackRole}, running={RunningAttackInstances.Contains(fieldValueRaw)}, globalTogglePresent={flag2}, globalToggleValue={flag2 && value.Value}, sledgeTogglePresent={flag3}, sledgeToggleValue={flag3 && value2.Value}, publicCategory={weaponCategory}, settingsCategory={weaponCategory2}.", 0.25f); } return; } AttackMovementApplicationMode effectiveAttackMovementApplicationMode = GetEffectiveAttackMovementApplicationMode(fieldValueRaw, weaponCategory2, attackRole); bool flag4 = LungeHitboxTriggeredAttacks.Contains(fieldValueRaw); if (effectiveAttackMovementApplicationMode == AttackMovementApplicationMode.BeforeHitbox && flag4) { __result = 0f; if (weaponCategory2 == WeaponCategory.SledgeAlt && DebugSledgeAlternate()) { DebugSledgeAlternateLog($"movement-before-hitbox-ended:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", $"Alternate-sledge movement intentionally resolved to 0 after hitbox because applicationMode={effectiveAttackMovementApplicationMode}; role={attackRole}, running={RunningAttackInstances.Contains(fieldValueRaw)}.", 0.25f); } return; } float nativeResult = __result; float effectiveAttackMovementModifier = GetEffectiveAttackMovementModifier(fieldValueRaw, weaponCategory2, attackRole); bool flag5 = UsesConfiguredAttackMovementBaseFactor(fieldValueRaw, weaponCategory2, attackRole); bool flag6 = UsesAdditiveAttackMovementFactor(fieldValueRaw, weaponCategory2, attackRole); __result = ResolveAttackMovementResult(nativeResult, fieldValueRaw, weaponCategory2, attackRole); if (weaponCategory2 == WeaponCategory.SledgeAlt && DebugSledgeAlternate()) { DebugSledgeAlternateLog($"movement-live:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", $"Live alternate-sledge movement resolved from decompiled Humanoid path: attackHash={RuntimeHelpers.GetHashCode(fieldValueRaw)}, inAttack={flag}, publicCategory={weaponCategory}, settingsCategory={weaponCategory2}, role={attackRole}, running={RunningAttackInstances.Contains(fieldValueRaw)}, markedAlt={SledgeAlternateMovesetAttacks.Contains(fieldValueRaw)}, nativeResult={nativeResult.ToString(CultureInfo.InvariantCulture)}, configured={effectiveAttackMovementModifier.ToString(CultureInfo.InvariantCulture)}, configuredBaseMode={flag5}, additiveMode={flag6}, configuredLocal={GetConfiguredAttackMovementValue(fieldValueRaw, weaponCategory2, attackRole).ToString(CultureInfo.InvariantCulture)}, globalStack={GetGlobalAttackMovementStackMultiplier().ToString(CultureInfo.InvariantCulture)}, hitboxTriggered={flag4}, application={effectiveAttackMovementApplicationMode}, final={__result.ToString(CultureInfo.InvariantCulture)}."); } } } public static void ItemDataGetWeaponLoadingTimePostfix(ItemData __instance, ref float __result) { if (!ModActive || __instance == null) { return; } float num = 1f; string text = "weapon-reload"; string text2 = "weapon"; if (TryGetStaffConfig(__instance, out StaffConfig cfg) && IsStaffLightning(cfg)) { float num2 = Mathf.Clamp(StaffLightningReloadSpeedMultiplier.Value, 0.05f, 10f); num = 1f / num2; text = "staff-lightning-reload"; text2 = "StaffLightning"; } else { if (ClassifyWeapon(__instance) != WeaponCategory.Crossbow) { return; } num = Mathf.Clamp(CrossbowReloadTimeMultiplier.Value, 0.05f, 10f); text = "crossbow-reload"; text2 = "Crossbow"; } if (!Mathf.Approximately(num, 1f)) { float num3 = __result; __result *= num; if (Debug(DebugRanged)) { DebugLogThrottled(text, 1f, text2 + " reload time x" + num.ToString(CultureInfo.InvariantCulture) + ": " + num3.ToString(CultureInfo.InvariantCulture) + " -> " + __result.ToString(CultureInfo.InvariantCulture) + "."); } } } public static void SEManApplyArmorModsPrefix(object __instance, ref float armor) { if (!ModActive || __instance == null || !TryGetSEManCharacter(__instance, out Character character) || !(character is Player)) { return; } float num = Mathf.Max(0f, PlayerBaseArmor.Value); if (!(num <= 0f)) { armor += num; if (Debug(DebugPlayerSystems)) { DebugLogThrottled("base-armor:" + ((Object)character).GetInstanceID(), 5f, "Added base armor " + num.ToString(CultureInfo.InvariantCulture) + " before status armor modifiers for " + SafeName(character) + "."); } } } public static void SEManModifyStaminaRegenPostfix(object __instance, ref float staminaMultiplier) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Invalid comparison between Unknown and I4 if (!ModActive || __instance == null || !TryGetSEManCharacter(__instance, out Character character)) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null || !((Character)val).IsBlocking()) { return; } object fieldValueRaw = GetFieldValueRaw(val, "m_internalBlockingState"); bool flag = default(bool); int num; if (fieldValueRaw is bool) { flag = (bool)fieldValueRaw; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { float num2 = Mathf.Clamp(GlobalBlockingStaminaRegenMultiplier.Value, 0f, 10f); ItemData activeBlocker = GetActiveBlocker(val); if (activeBlocker?.m_shared != null) { num2 = (((int)activeBlocker.m_shared.m_itemType == 5) ? (num2 * Mathf.Clamp(GetShieldTypeBlockConfig(activeBlocker).BlockingStaminaRegenMultiplier.Value, 0f, 10f)) : ((!TryGetStaffBlockConfig(activeBlocker, out StaffBlockConfig cfg)) ? (num2 * Mathf.Clamp(GetConfig(ClassifyWeapon(activeBlocker)).GetBlockingStaminaRegenMultiplier(), 0f, 10f)) : (num2 * Mathf.Clamp(cfg.BlockingStaminaRegenMultiplier.Value, 0f, 10f)))); } if (!Mathf.Approximately(num2, 1f)) { staminaMultiplier *= num2; } } } public static void SEManModifyHealthRegenPostfix(object __instance, ref float regenMultiplier) { if (ModActive && __instance != null && TryGetSEManCharacter(__instance, out Character character) && character is Player) { float num = Mathf.Clamp(PlayerHealingRate.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { regenMultiplier *= num; } } } public static void SEManModifyFallDamagePostfix(object __instance, float baseDamage, ref float damage) { if (!ModActive || __instance == null || !TryGetSEManCharacter(__instance, out Character character) || !(character is Player) || damage <= 0f) { return; } float num = Mathf.Max(0f, FallDamageStartHeight.Value); float num2 = Mathf.Max(0f, FallDamagePerMeter.Value); float num3 = Mathf.Max(0f, FallDamageCap.Value); if (!Mathf.Approximately(num, 4f) || !Mathf.Approximately(num2, 6.25f) || !Mathf.Approximately(num3, 100f)) { float num4 = 4f + Mathf.Clamp(baseDamage, 0f, 100f) / 6.25f; float num5 = Mathf.Clamp((num4 - num) * num2, 0f, num3); damage = num5; if (Debug(DebugPlayerSystems)) { DebugLogThrottled("fall-damage:" + ((Object)character).GetInstanceID(), 0.5f, "Fall damage remap for " + SafeName(character) + ": baseDamage=" + baseDamage.ToString(CultureInfo.InvariantCulture) + ", inferredHeight=" + num4.ToString(CultureInfo.InvariantCulture) + ", final=" + damage.ToString(CultureInfo.InvariantCulture) + "."); } } } public static void SEManModifyRunStaminaDrainPostfix(object __instance, ref float drain) { if (!ModActive || DrainRunStaminaDuringAttacksAndAirborne.Value || __instance == null || drain <= 0f || !TryGetSEManCharacter(__instance, out Character character)) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val != null && (((Character)val).InAttack() || !((Character)val).IsOnGround())) { if (Debug(DebugPlayerSystems)) { DebugLogThrottled("run-stamina-suppressed:" + ((Object)val).GetInstanceID(), 1f, "Suppressed Shift/run stamina drain for " + SafeName((Character)(object)val) + " while " + (((Character)val).InAttack() ? "attacking" : "airborne") + ": drain=" + drain.ToString(CultureInfo.InvariantCulture) + "."); } drain = 0f; } } public static void PlayerAddAdrenalinePrefix(Player __instance, ref float v) { if (!ModActive || (Object)(object)__instance == (Object)null || v <= 0f) { return; } float num = v; if (CurrentBlockAdrenalineContextDepth > 0 && __instance == CurrentBlockAdrenalineContextPlayer) { ItemData currentBlockAdrenalineContextBlocker = CurrentBlockAdrenalineContextBlocker; if (currentBlockAdrenalineContextBlocker?.m_shared != null) { float perfectBlockAdrenaline = currentBlockAdrenalineContextBlocker.m_shared.m_perfectBlockAdrenaline; if (perfectBlockAdrenaline > 0f && Mathf.Approximately(num, perfectBlockAdrenaline)) { float num2 = Mathf.Clamp(GetConfiguredParryAdrenalineMultiplier(currentBlockAdrenalineContextBlocker), 0f, 20f); if (!Mathf.Approximately(num2, 1f)) { v *= num2; } } } } float num3 = ((GlobalAdrenalineRate != null) ? Mathf.Clamp(GlobalAdrenalineRate.Value, 0f, 20f) : 1f); if (!Mathf.Approximately(num3, 1f)) { v *= num3; } } public static void SEManModifyBlockStaminaUsagePostfix(object __instance, ref float staminaUse) { if (!ModActive || __instance == null || !TryGetSEManCharacter(__instance, out Character character)) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return; } ItemData activeBlocker = GetActiveBlocker(val); if (SoulsLikeBlockStaminaStates.TryGetValue(val, out SoulsLikeBlockStaminaState value) && value.ValidUntil >= Time.time && SameItem(value.Blocker, activeBlocker)) { float num = staminaUse; if (!value.Consumed) { staminaUse = Mathf.Max(0f, value.StaminaCost); value.Consumed = true; } else { staminaUse = 0f; } if (Debug(DebugBlocking)) { DebugLogThrottled("soulslike-block-stamina:" + ((Object)character).GetInstanceID(), 0.5f, "Souls-like block stamina override: " + num.ToString(CultureInfo.InvariantCulture) + " -> " + staminaUse.ToString(CultureInfo.InvariantCulture) + " for " + SafeName(character) + " using " + SafeItemName(activeBlocker) + "."); } return; } float num2 = Mathf.Clamp(GetConfiguredBlockStaminaMultiplier(activeBlocker), 0f, 10f) * ((GlobalBlockStaminaConsumptionRate != null) ? Mathf.Clamp(GlobalBlockStaminaConsumptionRate.Value, 0f, 10f) : 1f); if (!Mathf.Approximately(num2, 1f)) { float num3 = staminaUse; staminaUse *= num2; if (Debug(DebugBlocking)) { DebugLogThrottled("block-stamina:" + ((Object)character).GetInstanceID(), 0.5f, "Block stamina cost x" + num2.ToString(CultureInfo.InvariantCulture) + ": " + num3.ToString(CultureInfo.InvariantCulture) + " -> " + staminaUse.ToString(CultureInfo.InvariantCulture) + " for " + SafeName(character) + " using " + SafeItemName(activeBlocker) + "."); } } } public static void HumanoidBlockAttackPrefix(Humanoid __instance, HitData hit, Character attacker, ref ParryCapabilityPatchState? __state) { __state = null; ParryStaggerSuppressionState state = PushParryStaggerSuppressionFrame(); if (!ModActive) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || hit == null) { return; } ItemData activeBlocker = GetActiveBlocker(val); if (activeBlocker?.m_shared != null) { float timedBlockBonus = activeBlocker.m_shared.m_timedBlockBonus; float num = (CanBlockerParry(activeBlocker) ? GetEffectiveBaseTimedBlockBonus(activeBlocker) : 1f); if (!Mathf.Approximately(timedBlockBonus, num)) { __state = new ParryCapabilityPatchState(activeBlocker, timedBlockBonus); activeBlocker.m_shared.m_timedBlockBonus = num; if (Debug(DebugBlocking)) { DebugLogThrottled($"can-parry-native-gate:{((Object)val).GetInstanceID()}:{SafeItemName(activeBlocker)}", 0.25f, $"Can Parry={CanBlockerParry(activeBlocker)}: temporarily changed native timedBlockBonus {timedBlockBonus.ToString(CultureInfo.InvariantCulture)} -> {num.ToString(CultureInfo.InvariantCulture)} for {SafeItemName(activeBlocker)}; native Humanoid.BlockAttack will evaluate the real >1 gate, then GCO restores the prefab value."); } } } ApplyParryWindowTimeMultiplier(val, activeBlocker); if (activeBlocker?.m_shared != null) { ApplyEffectiveBlockAngleMultiplier(val, activeBlocker, hit); CurrentBlockAdrenalineContextPlayer = val; CurrentBlockAdrenalineContextBlocker = activeBlocker; CurrentBlockAdrenalineContextDepth++; if (EnablePvpDamageModifiers.Value && attacker is Player && (Object)(object)attacker != (Object)(object)val && activeBlocker.m_shared != null) { CurrentPvpBlockArmorContextBlocker = activeBlocker; CurrentPvpBlockArmorContextMultiplier = GetConfiguredPvpBlockArmorMultiplier(activeBlocker); CurrentPvpBlockForceContextMultiplier = GetConfiguredPvpBlockForceMultiplier(activeBlocker); CurrentPvpParryBonusContextMultiplier = GetConfiguredPvpParryBonusMultiplier(activeBlocker); CurrentPvpBlockArmorContextDepth++; } float playerStamina = GetPlayerStamina(val); float num2 = (UseSoulsLikeBlockStaminaMode(activeBlocker) ? ComputeSoulsLikeBlockStaminaCostForIncomingHit(val, activeBlocker, hit, attacker) : ComputeBlockStaminaCostForIncomingHit(val, activeBlocker, hit)); if (UseSoulsLikeBlockStaminaMode(activeBlocker)) { SoulsLikeBlockStaminaStates[val] = new SoulsLikeBlockStaminaState(activeBlocker, num2, Time.time + 0.25f); } if (UseSoulsLikeBlockBreak(activeBlocker)) { bool staminaWillBeCleared = num2 >= playerStamina && num2 > 0f; SoulsLikeBlockBreakStates[val] = new SoulsLikeBlockBreakState(activeBlocker, playerStamina, num2, staminaWillBeCleared, Time.time + 0.25f); } SuppressParryStaggerIfConfigured(val, hit, attacker, state); } } public static void HumanoidBlockAttackPostfix(object __instance, HitData hit, Character attacker, bool __result, ParryCapabilityPatchState? __state) { __state?.Restore(); Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { RestoreEffectiveBlockAngleHitDir(hit); RestoreParryWindowBlockTimer(val); ItemData activeBlocker = GetActiveBlocker(val); bool flag = !(GetPlayerStamina(val) > 0f) || ((Character)val).IsStaggering(); if (__result && !flag && hit != null) { ApplyDamageTakenOnBlockedHitMultipliers(activeBlocker, hit); float knockbackTakenWhileBlockingMultiplier = GetKnockbackTakenWhileBlockingMultiplier(activeBlocker); if (!Mathf.Approximately(knockbackTakenWhileBlockingMultiplier, 1f)) { hit.m_pushForce *= knockbackTakenWhileBlockingMultiplier; } } SoulsLikeBlockBreakStates.Remove(val); SoulsLikeBlockStaminaStates.Remove(val); } if (CurrentPvpBlockArmorContextDepth > 0) { CurrentPvpBlockArmorContextDepth--; if (CurrentPvpBlockArmorContextDepth <= 0) { ResetPvpBlockArmorContext(); } } if (CurrentBlockAdrenalineContextDepth > 0) { CurrentBlockAdrenalineContextDepth--; if (CurrentBlockAdrenalineContextDepth <= 0) { CurrentBlockAdrenalineContextDepth = 0; CurrentBlockAdrenalineContextPlayer = null; CurrentBlockAdrenalineContextBlocker = null; } } RestoreParryStaggerSuppressionFrame(); } public static Exception? HumanoidBlockAttackFinalizer(Exception? __exception, ParryCapabilityPatchState? __state) { __state?.Restore(); return __exception; } private static ParryStaggerSuppressionState PushParryStaggerSuppressionFrame() { if (CurrentParryStaggerSuppressionFrames == null) { CurrentParryStaggerSuppressionFrames = new Stack(); } ParryStaggerSuppressionState parryStaggerSuppressionState = new ParryStaggerSuppressionState(); CurrentParryStaggerSuppressionFrames.Push(parryStaggerSuppressionState); return parryStaggerSuppressionState; } private static void RestoreParryStaggerSuppressionFrame() { if (CurrentParryStaggerSuppressionFrames != null && CurrentParryStaggerSuppressionFrames.Count != 0) { CurrentParryStaggerSuppressionFrames.Pop().Restore(); } } private static void SuppressParryStaggerIfConfigured(Player blocker, HitData hit, Character? attacker, ParryStaggerSuppressionState state) { if ((Object)(object)blocker == (Object)null || hit == null || (Object)(object)attacker == (Object)null || state == null) { return; } IncomingParryStaggerSource incomingParryStaggerSource = GetIncomingParryStaggerSource(hit); if (incomingParryStaggerSource == IncomingParryStaggerSource.None) { return; } bool flag = attacker.IsPlayer() && (object)attacker != blocker; if (!CanStaggerAttackerWithParry(incomingParryStaggerSource, flag) && attacker.m_staggerWhenBlocked) { state.Suppress(attacker); attacker.m_staggerWhenBlocked = false; if (Debug(DebugBlocking)) { string text = (flag ? "PvP" : "PvE"); string text2 = ((incomingParryStaggerSource == IncomingParryStaggerSource.Aoe) ? "AOE" : "ranged"); DebugLogThrottled($"parry-stagger-suppressed:{((Object)blocker).GetInstanceID()}:{((Object)attacker).GetInstanceID()}:{incomingParryStaggerSource}:{text}", 0.5f, "Suppressed parry stagger against " + SafeName(attacker) + " for " + text2 + " " + text + " hit blocked by " + SafeName((Character)(object)blocker) + "."); } } } private static bool CanStaggerAttackerWithParry(IncomingParryStaggerSource source, bool pvp) { return ((ConfigEntry)(source switch { IncomingParryStaggerSource.Ranged => pvp ? CanStaggerRangedAttacksWithParryPvp : CanStaggerRangedAttacksWithParryPve, IncomingParryStaggerSource.Aoe => pvp ? CanStaggerAoeAttacksWithParryPvp : CanStaggerAoeAttacksWithParryPve, _ => null, }))?.Value ?? true; } private static IncomingParryStaggerSource GetIncomingParryStaggerSource(HitData hit) { IncomingParryStaggerSource currentIncomingParryStaggerSource = GetCurrentIncomingParryStaggerSource(); if (currentIncomingParryStaggerSource != IncomingParryStaggerSource.None) { return currentIncomingParryStaggerSource; } if (hit == null || !hit.m_ranged) { return IncomingParryStaggerSource.None; } if (!(hit.m_radius > 0.0001f)) { return IncomingParryStaggerSource.Ranged; } return IncomingParryStaggerSource.Aoe; } private static void ResetPvpBlockArmorContext() { CurrentPvpBlockArmorContextDepth = 0; CurrentPvpBlockArmorContextBlocker = null; CurrentPvpBlockArmorContextMultiplier = 1f; CurrentPvpBlockForceContextMultiplier = 1f; CurrentPvpParryBonusContextMultiplier = 1f; } private static float GetConfiguredPvpBlockArmorMultiplier(ItemData blocker) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } float num; StaffBlockConfig cfg; if ((int)blocker.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(blocker).PvpBlockArmorMultiplier.Value, 0f, 5000f); } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = Mathf.Clamp(cfg.PvpBlockArmorMultiplier.Value, 0f, 5000f); } else { WeaponCategory category = ClassifyWeapon(blocker); num = (IsFeatureApplied(category, ModFeature.Pvp) ? Mathf.Clamp(GetConfig(category).PvpBlockArmorMultiplier.Value, 0f, 999f) : 1f); } return num * ((GlobalPvpBlockArmorMultiplier != null) ? Mathf.Clamp(GlobalPvpBlockArmorMultiplier.Value, 0f, 5000f) : 1f); } private static void ApplyParryWindowTimeMultiplier(Player player, ItemData? blocker) { if ((Object)(object)player == (Object)null) { return; } bool flag = CanBlockerParry(blocker); float configuredParryWindowTimeMultiplier = GetConfiguredParryWindowTimeMultiplier(blocker); if (flag && Mathf.Approximately(configuredParryWindowTimeMultiplier, 1f)) { return; } float privateFloat = GetPrivateFloat(player, "m_blockTimer", 9999f); if (!Mathf.Approximately(privateFloat, -1f)) { if (!ParryWindowBlockTimerOriginals.ContainsKey(player)) { ParryWindowBlockTimerOriginals[player] = privateFloat; } if (!flag || configuredParryWindowTimeMultiplier <= 0f) { SetPrivateFloat(player, "m_blockTimer", 9999f); } else { SetPrivateFloat(player, "m_blockTimer", privateFloat / configuredParryWindowTimeMultiplier); } } } private static float GetEffectiveBaseTimedBlockBonus(ItemData? blocker) { if (blocker?.m_shared == null) { return 1.5f; } float timedBlockBonus = blocker.m_shared.m_timedBlockBonus; if (!(timedBlockBonus > 1f)) { return 1.5f; } return timedBlockBonus; } private static bool CanBlockerParry(ItemData? blocker) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { if (!WeaponBlockExtras.TryGetValue(WeaponCategory.Unarmed, out WeaponBlockExtraConfig value)) { return true; } return value.CanParry.Value; } if ((int)blocker.m_shared.m_itemType == 5) { return GetShieldTypeBlockConfig(blocker).CanParry.Value; } if (TryGetStaffBlockConfig(blocker, out StaffBlockConfig cfg)) { return cfg.CanParry.Value; } WeaponCategory key = ClassifyWeapon(blocker); if (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value2)) { return value2.CanParry.Value; } return true; } private static void RestoreParryWindowBlockTimer(Player player) { if (!((Object)(object)player == (Object)null) && ParryWindowBlockTimerOriginals.TryGetValue(player, out var value)) { SetPrivateFloat(player, "m_blockTimer", value); ParryWindowBlockTimerOriginals.Remove(player); } } private static void RestoreParryWindowBlockTimers() { foreach (KeyValuePair item in ParryWindowBlockTimerOriginals.ToList()) { if ((Object)(object)item.Key != (Object)null) { SetPrivateFloat(item.Key, "m_blockTimer", item.Value); } } ParryWindowBlockTimerOriginals.Clear(); } private static void ResetParryWindowMultipliersToVanilla() { foreach (WeaponBlockExtraConfig value in WeaponBlockExtras.Values) { value.CanParry.Value = true; value.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.CanParry.Value = true; ShieldBucklerBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.CanParry.Value = true; ShieldMediumBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.CanParry.Value = true; ShieldTowerBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } foreach (StaffBlockConfig value2 in StaffBlockConfigs.Values) { value2.CanParry.Value = true; value2.ParryWindowTimeMultiplier.Value = 1f; } } private static void ApplyGooCanParryDefaults() { ResetParryWindowMultipliersToVanilla(); if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.CanParry.Value = false; } } private static float GetConfiguredParryWindowTimeMultiplier(ItemData? blocker) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 float num = ((GlobalParryWindowTimeMultiplier != null) ? Mathf.Clamp(GlobalParryWindowTimeMultiplier.Value, 0f, 20f) : 1f); if (blocker?.m_shared == null) { return num; } StaffBlockConfig cfg; if ((int)blocker.m_shared.m_itemType == 5) { num *= Mathf.Clamp(GetShieldTypeBlockConfig(blocker).ParryWindowTimeMultiplier.Value, 0f, 20f); } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num *= Mathf.Clamp(cfg.ParryWindowTimeMultiplier.Value, 0f, 20f); } else { WeaponCategory key = ClassifyWeapon(blocker); if (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value)) { num *= Mathf.Clamp(value.ParryWindowTimeMultiplier.Value, 0f, 20f); } } return Mathf.Clamp(num, 0f, 20f); } private static float GetConfiguredPvpBlockForceMultiplier(ItemData blocker) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } float num; StaffBlockConfig cfg; if ((int)blocker.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(blocker).PvpBlockForceMultiplier.Value, 0f, 5000f); } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = Mathf.Clamp(cfg.PvpBlockForceMultiplier.Value, 0f, 5000f); } else { WeaponCategory weaponCategory = ClassifyWeapon(blocker); num = ((IsFeatureApplied(weaponCategory, ModFeature.Pvp) && WeaponBlockExtras.TryGetValue(weaponCategory, out WeaponBlockExtraConfig value)) ? Mathf.Clamp(value.PvpBlockForceMultiplier.Value, 0f, 5000f) : 1f); } return num * ((GlobalPvpBlockForceMultiplier != null) ? Mathf.Clamp(GlobalPvpBlockForceMultiplier.Value, 0f, 5000f) : 1f); } private static float GetConfiguredPvpParryBonusMultiplier(ItemData blocker) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } float num; StaffBlockConfig cfg; if ((int)blocker.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(blocker).PvpParryBonusMultiplier.Value, 0f, 100f); } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = Mathf.Clamp(cfg.PvpParryBonusMultiplier.Value, 0f, 100f); } else { WeaponCategory key = ClassifyWeapon(blocker); num = (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value) ? Mathf.Clamp(value.PvpParryBonusMultiplier.Value, 0f, 100f) : 1f); } return num * ((GlobalPvpParryBonusMultiplier != null) ? Mathf.Clamp(GlobalPvpParryBonusMultiplier.Value, 0f, 100f) : 1f); } private static float GetConfiguredParryBonusMultiplier(ItemData blocker, bool pvp) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } StaffBlockConfig cfg; float num; if ((int)blocker.m_shared.m_itemType == 5) { num = GetShieldTypeBlockConfig(blocker).ParryBonusMultiplier.Value; } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = cfg.ParryBonusMultiplier.Value; } else { WeaponCategory key = ClassifyWeapon(blocker); num = (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value) ? value.ParryBonusMultiplier.Value : 1f); } num *= ((GlobalParryBonusMultiplier != null) ? Mathf.Clamp(GlobalParryBonusMultiplier.Value, 0f, 100f) : 1f); if (pvp) { num *= GetConfiguredPvpParryBonusMultiplier(blocker); } return Mathf.Clamp(num, 0f, 100f); } private static float GetConfiguredParryAdrenalineMultiplier(ItemData blocker) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { if (GlobalParryAdrenalineMultiplier == null) { return 1f; } return Mathf.Clamp(GlobalParryAdrenalineMultiplier.Value, 0f, 20f); } StaffBlockConfig cfg; float num = (((int)blocker.m_shared.m_itemType == 5) ? Mathf.Clamp(GetShieldTypeBlockConfig(blocker).ParryAdrenalineMultiplier.Value, 0f, 20f) : ((!TryGetStaffBlockConfig(blocker, out cfg)) ? 1f : Mathf.Clamp(cfg.ParryAdrenalineMultiplier.Value, 0f, 20f))); return num * ((GlobalParryAdrenalineMultiplier != null) ? Mathf.Clamp(GlobalParryAdrenalineMultiplier.Value, 0f, 20f) : 1f); } private static ShieldTypeBlockConfig GetShieldTypeBlockConfig(ItemData item) { if (IsTowerShield(item)) { return ShieldTowerBlockConfig; } if (IsBuckler(item)) { return ShieldBucklerBlockConfig; } return ShieldMediumBlockConfig; } private static bool IsTowerShield(ItemData item) { return GetShieldNameBlob(item).Contains("tower"); } private static bool IsBuckler(ItemData item) { return GetShieldNameBlob(item).Contains("buckler"); } private static string GetShieldNameBlob(ItemData item) { if (item?.m_shared == null) { return string.Empty; } return SafeLower(item.m_shared.m_name) + " " + SafeLower(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty) + " " + SafeLower(((object)Unsafe.As(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString()); } private static bool TryGetStaffBlockConfig(ItemData? item, out StaffBlockConfig cfg) { cfg = null; if (TryGetStaffConfig(item, out StaffConfig cfg2)) { return StaffBlockConfigs.TryGetValue(cfg2.PrefabName, out cfg); } return false; } public static void SEManModifyParryBonusPostfix(object __instance, ref float timedBlockBonus) { if (!ModActive || __instance == null || !TryGetSEManCharacter(__instance, out Character character)) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return; } ItemData activeBlocker = GetActiveBlocker(val); if (activeBlocker?.m_shared != null) { float num = GetConfiguredParryBonusMultiplier(activeBlocker, pvp: false); if (CurrentPvpBlockArmorContextDepth > 0 && CurrentPvpBlockArmorContextBlocker == activeBlocker) { num *= Mathf.Clamp(CurrentPvpParryBonusContextMultiplier, 0f, 20f); } if (HasStaffShieldActive((Character)(object)val) && StaffShieldActiveSettings != null) { num *= Mathf.Clamp(StaffShieldActiveSettings.ActiveParryBonusMultiplier.Value, 0f, 20f); } num = Mathf.Clamp(num, 0f, 100f); if (!Mathf.Approximately(num, 1f)) { timedBlockBonus *= num; } } } private static void CapturePendingPvpStaggerDuration(Character victim, Character? attacker, HitData? hit = null) { if (CapturePendingPvpStaggerDurationFromIncomingHit(victim, hit) || (Object)(object)victim == (Object)null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && IsPvpLikeTarget(victim) && !((Object)(object)attacker == (Object)(object)victim)) { ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, category); float configuredPvpStaggerDuration = GetConfiguredPvpStaggerDuration(val, category, currentAttackRole); if (configuredPvpStaggerDuration > 0f) { PendingPvpStaggerDurations[victim] = configuredPvpStaggerDuration; } } } private static bool CapturePendingPvpStaggerDurationFromIncomingHit(Character? victim, HitData? hit) { if (!EnablePvpDamageModifiers.Value || (Object)(object)victim == (Object)null || hit == null) { return false; } Character attacker = GetAttacker(hit); if (!(attacker is Player) || !IsPvpLikeTarget(victim) || (Object)(object)attacker == (Object)(object)victim) { return false; } if (PvpStaggerDurationsByHit.TryGetValue(hit, out PvpStaggerDurationState value) && value.DurationSeconds > 0f) { PendingPvpStaggerDurations[victim] = value.DurationSeconds; return true; } float num = InferPvpStaggerDurationFromFinalHit(hit); if (num <= 0f) { return false; } PendingPvpStaggerDurations[victim] = num; return true; } private static void StoreConfiguredPvpStaggerDuration(HitData hit, float durationSeconds) { if (hit == null || durationSeconds <= 0f) { return; } try { PvpStaggerDurationsByHit.Remove(hit); PvpStaggerDurationsByHit.Add(hit, new PvpStaggerDurationState(durationSeconds)); } catch { } } private static float InferPvpStaggerDurationFromFinalHit(HitData hit) { if (hit == null) { return 0f; } float num = Mathf.Abs(hit.m_staggerMultiplier); if (num >= 19.9f) { return 1.17f; } if (num >= 9.9f) { return 0.83f; } if (num >= 4.9f) { return 0.33f; } return 0f; } private static void StopAllPvpStaggerSpeedCoroutines() { if ((Object)(object)Instance == (Object)null) { PvpStaggerSpeedCoroutines.Clear(); return; } foreach (Coroutine value in PvpStaggerSpeedCoroutines.Values) { if (value != null) { ((MonoBehaviour)Instance).StopCoroutine(value); } } PvpStaggerSpeedCoroutines.Clear(); } private static void ApplyPendingPvpStaggerDuration(Character character) { if ((Object)(object)character == (Object)null) { return; } if (ClearStaggerMeterWhenStaggered != null && ClearStaggerMeterWhenStaggered.Value && IsPvpLikeTarget(character)) { SetPrivateFloat(character, "m_staggerDamage", 0f); } if (ClearEnemyStaggerMeterWhenStaggered != null && ClearEnemyStaggerMeterWhenStaggered.Value && !IsPvpLikeTarget(character)) { SetPrivateFloat(character, "m_staggerDamage", 0f); } if (PendingPvpStaggerDurations.TryGetValue(character, out var value)) { PendingPvpStaggerDurations.Remove(character); value = Mathf.Clamp(value, 0.05f, 10f); if (DebugPvpTestDummyOutput != null && DebugPvpTestDummyOutput.Value && IsPvpTestDummy(character)) { Log.LogInfo((object)("GCO PvP dummy staggered: configured PvP stagger duration " + value.ToString("0.###", CultureInfo.InvariantCulture) + "s.")); } float speed = Mathf.Clamp(1.5f / value, 0.05f, 5f); if (PvpStaggerSpeedCoroutines.TryGetValue(character, out Coroutine value2) && value2 != null) { ((MonoBehaviour)Instance).StopCoroutine(value2); } PvpStaggerSpeedCoroutines[character] = ((MonoBehaviour)Instance).StartCoroutine(PvpStaggerSpeedRoutine(character, speed, value)); } } private static IEnumerator PvpStaggerSpeedRoutine(Character character, float speed, float duration) { if ((Object)(object)character == (Object)null) { yield break; } TrySetCharacterAnimationSpeed(character, speed); float waitUntil = Time.time + 0.25f; while ((Object)(object)character != (Object)null && !character.IsStaggering() && Time.time < waitUntil) { TrySetCharacterAnimationSpeed(character, speed); yield return null; } float end = Time.time + duration; while ((Object)(object)character != (Object)null && Time.time < end) { TrySetCharacterAnimationSpeed(character, speed); if (!character.IsStaggering() && Time.time > waitUntil) { break; } yield return null; } if ((Object)(object)character != (Object)null) { TrySetCharacterAnimationSpeed(character, 1f); } if ((Object)(object)character != (Object)null) { PvpStaggerSpeedCoroutines.Remove(character); } } public static void CharacterAddStaggerDamagePrefix(Character __instance, ref float damage, HitData hit) { if (!ModActive || (Object)(object)__instance == (Object)null || damage <= 0f) { return; } Character val = ((hit != null) ? GetAttacker(hit) : null); float num = damage; if ((Object)(object)val != (Object)null && !val.IsPlayer()) { damage *= Mathf.Clamp(EnemyStaggerDamageDealtMultiplier.Value, 0f, 20f); } float num2 = (IsPvpLikeTarget(__instance) ? Mathf.Clamp(PlayerPoiseMultiplier.Value, 0.05f, 20f) : Mathf.Clamp(EnemyPoiseMultiplier.Value, 0.05f, 20f)); if (!Mathf.Approximately(num2, 1f)) { damage /= num2; } if (IsPvpTestDummy(__instance)) { float pvpTestDummyStaggerThresholdMultiplier = GetPvpTestDummyStaggerThresholdMultiplier(__instance); if (!Mathf.Approximately(pvpTestDummyStaggerThresholdMultiplier, 1f)) { damage /= pvpTestDummyStaggerThresholdMultiplier; } } Player val2 = (Player)(object)((__instance is Player) ? __instance : null); if (val2 != null && ((Character)val2).IsBlocking()) { ItemData activeBlocker = GetActiveBlocker(val2); if (UseSoulsLikeBlockBreak(activeBlocker) && SoulsLikeBlockBreakStates.TryGetValue(val2, out SoulsLikeBlockBreakState value) && value.ValidUntil >= Time.time) { float num3 = damage; if (value.StaminaWillBeCleared) { float privateFloat = GetPrivateFloat(val2, "m_staggerDamageFactor", 0f); float num4 = ((Character)val2).GetMaxHealth() * privateFloat; float privateFloat2 = GetPrivateFloat(val2, "m_staggerDamage", 0f); float num5 = Mathf.Max(0f, num4 - privateFloat2); damage = Mathf.Max(damage * 999f, num5); if (Debug(DebugBlocking)) { string key = "soulslike-block-break:" + ((Object)val2).GetInstanceID(); string[] obj = new string[13] { "SoulsLikeBlockBreak forced block break because this blocked hit cleared stamina: stamina ", null, null, null, null, null, null, null, null, null, null, null, null }; float staminaBefore = value.StaminaBefore; obj[1] = staminaBefore.ToString(CultureInfo.InvariantCulture); obj[2] = " cost "; staminaBefore = value.StaminaCost; obj[3] = staminaBefore.ToString(CultureInfo.InvariantCulture); obj[4] = ", stagger "; obj[5] = num3.ToString(CultureInfo.InvariantCulture); obj[6] = " -> "; obj[7] = damage.ToString(CultureInfo.InvariantCulture); obj[8] = " for "; obj[9] = SafeName((Character)(object)val2); obj[10] = " using "; obj[11] = SafeItemName(activeBlocker); obj[12] = "."; DebugLogThrottled(key, 0.5f, string.Concat(obj)); } } else { damage /= 999f; if (Debug(DebugBlocking)) { string key2 = "soulslike-block-break:" + ((Object)val2).GetInstanceID(); string[] obj2 = new string[13] { "SoulsLikeBlockBreak preserved block because stamina survived this blocked hit: stamina ", null, null, null, null, null, null, null, null, null, null, null, null }; float staminaBefore = value.StaminaBefore; obj2[1] = staminaBefore.ToString(CultureInfo.InvariantCulture); obj2[2] = " cost "; staminaBefore = value.StaminaCost; obj2[3] = staminaBefore.ToString(CultureInfo.InvariantCulture); obj2[4] = ", stagger "; obj2[5] = num3.ToString(CultureInfo.InvariantCulture); obj2[6] = " -> "; obj2[7] = damage.ToString(CultureInfo.InvariantCulture); obj2[8] = " for "; obj2[9] = SafeName((Character)(object)val2); obj2[10] = " using "; obj2[11] = SafeItemName(activeBlocker); obj2[12] = "."; DebugLogThrottled(key2, 0.5f, string.Concat(obj2)); } } } else { float num6 = Mathf.Clamp(GetConfiguredBlockBreakThresholdMultiplier(activeBlocker, val, __instance), 0.05f, 999f); if (!Mathf.Approximately(num6, 1f)) { float num7 = damage; damage /= num6; if (Debug(DebugBlocking)) { DebugLogThrottled("block-break:" + ((Object)val2).GetInstanceID(), 0.5f, "Block break threshold x" + num6.ToString(CultureInfo.InvariantCulture) + ": stagger contribution " + num7.ToString(CultureInfo.InvariantCulture) + " -> " + damage.ToString(CultureInfo.InvariantCulture) + " for " + SafeName((Character)(object)val2) + " using " + SafeItemName(activeBlocker) + "."); } } } } if (damage > 0f && HasStaffShieldActive(__instance) && StaffShieldActiveSettings != null) { float num8 = Mathf.Clamp(StaffShieldActiveSettings.StaggerMitigationMultiplier.Value, 0f, 20f); if (val is Player && IsPvpLikeTarget(__instance) && (Object)(object)val != (Object)(object)__instance) { num8 *= Mathf.Clamp(StaffShieldActiveSettings.PvpStaggerMitigationMultiplier.Value, 0f, 20f); } damage *= num8; } int num10; int num11; if (damage > 0f) { float privateFloat3 = GetPrivateFloat(__instance, "m_staggerDamageFactor", 0f); float num9 = __instance.GetMaxHealth() * privateFloat3; float privateFloat4 = GetPrivateFloat(__instance, "m_staggerDamage", 0f); if (privateFloat3 > 0f) { num10 = ((num9 > 0f) ? 1 : 0); if (num10 != 0) { num11 = ((privateFloat4 + damage >= num9) ? 1 : 0); goto IL_0508; } } else { num10 = 0; } num11 = 0; goto IL_0508; } goto IL_056c; IL_056c: if (Debug(DebugPlayerSystems) && !Mathf.Approximately(num, damage)) { DebugLogThrottled("stagger-poise:" + ((Object)__instance).GetInstanceID(), 0.5f, "Stagger/poise tuning for " + SafeName(__instance) + ": " + num.ToString(CultureInfo.InvariantCulture) + " -> " + damage.ToString(CultureInfo.InvariantCulture) + "."); } return; IL_0508: bool flag = (byte)num11 != 0; if (flag) { CapturePendingPvpStaggerDuration(__instance, val, hit); } if (num10 != 0 && !flag) { float num12 = (IsPvpLikeTarget(__instance) ? Mathf.Max(0f, PlayerStaggerDecayCooldownSeconds.Value) : Mathf.Max(0f, EnemyStaggerDecayCooldownSeconds.Value)); if (num12 > 0f) { StaggerDecayCooldownUntil[__instance] = Time.time + num12; } } goto IL_056c; } private static bool SameItem(ItemData? a, ItemData? b) { if (a != b) { if (a != null && b != null && a.m_shared == b.m_shared) { return (Object)(object)a.m_dropPrefab == (Object)(object)b.m_dropPrefab; } return false; } return true; } private static float GetConfiguredBlockStaminaMultiplier(ItemData? blocker) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } if ((int)blocker.m_shared.m_itemType == 5) { return GetShieldTypeBlockConfig(blocker).BlockStaminaConsumptionRate.Value; } if (TryGetStaffBlockConfig(blocker, out StaffBlockConfig cfg)) { return cfg.BlockStaminaConsumptionRate.Value; } return GetConfig(ClassifyWeapon(blocker)).GetBlockStaminaConsumptionRate(); } private static float GetConfiguredBlockBreakThresholdMultiplier(ItemData? blocker, Character? attacker = null, Character? victim = null) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } float num = 1f; StaffBlockConfig cfg; float num2; if ((int)blocker.m_shared.m_itemType == 5) { ShieldTypeBlockConfig shieldTypeBlockConfig = GetShieldTypeBlockConfig(blocker); num2 = shieldTypeBlockConfig.BlockBreakThresholdMultiplier.Value; num = shieldTypeBlockConfig.PvpBlockBreakThresholdMultiplier.Value; } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num2 = cfg.BlockBreakThresholdMultiplier.Value; num = cfg.PvpBlockBreakThresholdMultiplier.Value; } else { WeaponCategory weaponCategory = ClassifyWeapon(blocker); num2 = GetConfig(weaponCategory).GetBlockBreakThresholdMultiplier(); if (IsFeatureApplied(weaponCategory, ModFeature.Pvp) && WeaponBlockExtras.TryGetValue(weaponCategory, out WeaponBlockExtraConfig value)) { num = value.PvpBlockBreakThresholdMultiplier.Value; } } num2 *= ((GlobalBlockBreakThresholdMultiplier != null) ? Mathf.Clamp(GlobalBlockBreakThresholdMultiplier.Value, 0.05f, 999f) : 1f); if (EnablePvpDamageModifiers.Value && attacker is Player && (Object)(object)victim != (Object)null && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { num2 *= num; num2 *= ((GlobalPvpBlockBreakThresholdMultiplier != null) ? Mathf.Clamp(GlobalPvpBlockBreakThresholdMultiplier.Value, 0.05f, 999f) : 1f); } return Mathf.Clamp(num2, 0.05f, 999f); } private static float ComputeBlockStaminaCostForIncomingHit(Player player, ItemData blocker, HitData hit) { //IL_0097: 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) if ((Object)(object)player == (Object)null || blocker?.m_shared == null || hit == null) { return 0f; } bool num = blocker.m_shared.m_timedBlockBonus > 1f && GetPrivateFloat(player, "m_blockTimer", 9999f) != -1f && GetPrivateFloat(player, "m_blockTimer", 9999f) < 0.25f; float skillFactor = ((Character)player).GetSkillFactor((SkillType)6); float num2 = blocker.GetBlockPower(skillFactor); if (num) { num2 *= blocker.m_shared.m_timedBlockBonus; ((Character)player).GetSEMan().ModifyTimedBlockBonus(ref num2); } DamageTypes val = ((DamageTypes)(ref hit.m_damage)).Clone(); ((DamageTypes)(ref val)).ApplyArmor(num2); float totalBlockableDamage = hit.GetTotalBlockableDamage(); float totalBlockableDamage2 = ((DamageTypes)(ref val)).GetTotalBlockableDamage(); float num3 = totalBlockableDamage - totalBlockableDamage2; float num4 = ((num2 > 0f) ? Mathf.Clamp01(num3 / num2) : 0f); float num5 = (num ? ((Humanoid)player).m_perfectBlockStaminaDrain : (((Humanoid)player).m_blockStaminaDrain * num4)); num5 += num5 * ((Character)player).GetEquipmentBlockStaminaModifier(); ((Character)player).GetSEMan().ModifyBlockStaminaUsage(num5, ref num5, false); return Mathf.Max(0f, num5); } private static float ComputeSoulsLikeBlockStaminaCostForIncomingHit(Player player, ItemData blocker, HitData hit, Character? attacker) { if ((Object)(object)player == (Object)null || blocker?.m_shared == null || hit == null) { return 0f; } float num = Mathf.Max(0f, ((DamageTypes)(ref hit.m_damage)).GetTotalStaggerDamage() * Mathf.Max(0f, hit.m_staggerMultiplier)); if (num <= 0f) { return 0f; } float num2 = Mathf.Clamp(GetConfiguredSoulsLikeBlockStaminaBaseRate(blocker), 0f, 1f); num2 *= ((GlobalSoulsLikeBlockStaminaBaseRateMultiplier != null) ? Mathf.Clamp(GlobalSoulsLikeBlockStaminaBaseRateMultiplier.Value, 0f, 10f) : 1f); float num3 = Mathf.Max(0f, GetEffectiveBlockArmorForSoulsLikeBlockStamina(player, blocker)); float configuredBlockBreakThresholdMultiplier = GetConfiguredBlockBreakThresholdMultiplier(blocker, attacker, (Character?)(object)player); float num4 = Mathf.Max(0f, 1f - Mathf.Clamp01(num2 + num3 / 1000f - 0.05f)); return Mathf.Max(0f, num * num4 / Mathf.Max(0.05f, configuredBlockBreakThresholdMultiplier)); } private static float GetEffectiveBlockArmorForSoulsLikeBlockStamina(Player player, ItemData blocker) { if ((Object)(object)player == (Object)null || blocker?.m_shared == null) { return 0f; } float skillFactor = ((Character)player).GetSkillFactor((SkillType)6); float num = blocker.GetBlockPower(skillFactor); if (blocker.m_shared.m_timedBlockBonus > 1f && GetPrivateFloat(player, "m_blockTimer", 9999f) != -1f && GetPrivateFloat(player, "m_blockTimer", 9999f) < 0.25f) { num *= blocker.m_shared.m_timedBlockBonus; ((Character)player).GetSEMan().ModifyTimedBlockBonus(ref num); } return Mathf.Max(0f, num); } private static bool UseSoulsLikeBlockStaminaMode(ItemData? blocker) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return false; } if ((int)blocker.m_shared.m_itemType == 5) { return GetShieldTypeBlockConfig(blocker).SoulsLikeBlockStaminaMode.Value; } if (TryGetStaffBlockConfig(blocker, out StaffBlockConfig cfg)) { return cfg.SoulsLikeBlockStaminaMode.Value; } WeaponCategory key = ClassifyWeapon(blocker); if (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value)) { return value.SoulsLikeBlockStaminaMode.Value; } return false; } private static float GetConfiguredSoulsLikeBlockStaminaBaseRate(ItemData? blocker) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return 1f; } float num; StaffBlockConfig cfg; if ((int)blocker.m_shared.m_itemType == 5) { num = GetShieldTypeBlockConfig(blocker).SoulsLikeBlockStaminaBaseRate.Value; } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = cfg.SoulsLikeBlockStaminaBaseRate.Value; } else { WeaponCategory key = ClassifyWeapon(blocker); num = (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value) ? value.SoulsLikeBlockStaminaBaseRate.Value : 1f); } return Mathf.Clamp(num, 0f, 1f); } private static bool UseSoulsLikeBlockBreak(ItemData? blocker) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return false; } if ((int)blocker.m_shared.m_itemType == 5) { return GetShieldTypeBlockConfig(blocker).SoulsLikeBlockBreak.Value; } if (TryGetStaffBlockConfig(blocker, out StaffBlockConfig cfg)) { return cfg.SoulsLikeBlockBreak.Value; } WeaponCategory key = ClassifyWeapon(blocker); if (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value)) { return value.SoulsLikeBlockBreak.Value; } return false; } private static BlockedHitDamageTakenConfig? GetLocalDamageTakenOnBlockedHitConfig(ItemData? blocker) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { return null; } if ((int)blocker.m_shared.m_itemType == 5) { ShieldTypeBlockConfig shieldTypeBlockConfig = GetShieldTypeBlockConfig(blocker); if (shieldTypeBlockConfig == ShieldTowerBlockConfig) { return ShieldTowerDamageTakenOnBlockedHit; } if (shieldTypeBlockConfig == ShieldMediumBlockConfig) { return ShieldMediumDamageTakenOnBlockedHit; } return ShieldBucklerDamageTakenOnBlockedHit; } if (TryGetStaffConfig(blocker, out StaffConfig cfg) && StaffDamageTakenOnBlockedHitConfigs.TryGetValue(cfg.PrefabName, out BlockedHitDamageTakenConfig value)) { return value; } WeaponCategory key = ClassifyWeapon(blocker); if (!WeaponDamageTakenOnBlockedHitConfigs.TryGetValue(key, out BlockedHitDamageTakenConfig value2)) { return null; } return value2; } private static float ResolveBlockedHitDamageTakenMultiplier(ConfigEntry? global, ConfigEntry? local) { return Mathf.Clamp(((global != null) ? Mathf.Clamp(global.Value, 0f, 20f) : 1f) * ((local != null) ? Mathf.Clamp(local.Value, 0f, 20f) : 1f), 0f, 20f); } private static void ApplyDamageTakenOnBlockedHitMultipliers(ItemData? blocker, HitData hit) { if (hit != null) { BlockedHitDamageTakenConfig localDamageTakenOnBlockedHitConfig = GetLocalDamageTakenOnBlockedHitConfig(blocker); BlockedHitDamageTakenConfig globalDamageTakenOnBlockedHit = GlobalDamageTakenOnBlockedHit; if (globalDamageTakenOnBlockedHit != null || localDamageTakenOnBlockedHitConfig != null) { hit.m_damage.m_damage *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Generic, localDamageTakenOnBlockedHitConfig?.Generic); hit.m_damage.m_blunt *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Blunt, localDamageTakenOnBlockedHitConfig?.Blunt); hit.m_damage.m_slash *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Slash, localDamageTakenOnBlockedHitConfig?.Slash); hit.m_damage.m_pierce *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Pierce, localDamageTakenOnBlockedHitConfig?.Pierce); hit.m_damage.m_chop *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Chop, localDamageTakenOnBlockedHitConfig?.Chop); hit.m_damage.m_pickaxe *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Pickaxe, localDamageTakenOnBlockedHitConfig?.Pickaxe); hit.m_damage.m_fire *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Fire, localDamageTakenOnBlockedHitConfig?.Fire); hit.m_damage.m_frost *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Frost, localDamageTakenOnBlockedHitConfig?.Frost); hit.m_damage.m_lightning *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Lightning, localDamageTakenOnBlockedHitConfig?.Lightning); hit.m_damage.m_poison *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Poison, localDamageTakenOnBlockedHitConfig?.Poison); hit.m_damage.m_spirit *= ResolveBlockedHitDamageTakenMultiplier(globalDamageTakenOnBlockedHit?.Spirit, localDamageTakenOnBlockedHitConfig?.Spirit); } } } private static float GetKnockbackTakenWhileBlockingMultiplier(ItemData? blocker) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 if (blocker?.m_shared == null) { if (GlobalKnockbackTakenWhileBlockingMultiplier == null) { return 1f; } return Mathf.Clamp(GlobalKnockbackTakenWhileBlockingMultiplier.Value, 0f, 20f); } StaffBlockConfig cfg; float num; if ((int)blocker.m_shared.m_itemType == 5) { num = Mathf.Clamp(GetShieldTypeBlockConfig(blocker).KnockbackTakenWhileBlockingMultiplier.Value, 0f, 20f); } else if (TryGetStaffBlockConfig(blocker, out cfg)) { num = Mathf.Clamp(cfg.KnockbackTakenWhileBlockingMultiplier.Value, 0f, 20f); } else { WeaponCategory key = ClassifyWeapon(blocker); num = (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value) ? Mathf.Clamp(value.KnockbackTakenWhileBlockingMultiplier.Value, 0f, 20f) : 1f); } num *= ((GlobalKnockbackTakenWhileBlockingMultiplier != null) ? Mathf.Clamp(GlobalKnockbackTakenWhileBlockingMultiplier.Value, 0f, 20f) : 1f); return Mathf.Clamp(num, 0f, 20f); } private static ItemData? GetActiveBlocker(Player player) { if ((Object)(object)player == (Object)null) { return null; } ItemData fieldValue = GetFieldValue(player, "m_leftItem"); if (fieldValue != null) { return fieldValue; } return GetCurrentWeapon((Character)(object)player); } private static string SafeItemName(ItemData? item) { if (item?.m_shared == null) { return "none"; } return item.m_shared.m_name ?? "unnamed item"; } private void PatchAttackStartMethods(Type attackType) { List list = (from m in AccessTools.GetDeclaredMethods(attackType) where m.Name == "Start" && m.ReturnType == typeof(bool) select m).ToList(); if (list.Count == 0) { int num = AccessTools.GetDeclaredMethods(attackType).Count((MethodInfo m) => m.Name == "Start"); ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not find a bool-returning Attack.Start overload. Found {num} Attack.Start overload(s), but successful-attack gating is unavailable on this Valheim build."); } else { PatchAttackMethods(list, "AttackStartPrefix", postfix: false); PatchAttackMethods(list, "AttackStartPostfix", postfix: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} successful Attack.Start overload(s) for valid-attack gated attack state, attack movement timing, animation speed, and FullAttackAnimation hyperarmor. Balanced hyperarmor and counter vulnerability are gated by Attack.Update/InAttack."); } } private void PatchAttackAreaMethods(Type attackType) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_008e: Expected O, but got Unknown //IL_008e: Expected O, but got Unknown List list = (from m in AccessTools.GetDeclaredMethods(attackType) where m.Name == "DoAreaAttack" select m).ToList(); foreach (MethodInfo item in list) { _harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackDoAreaAttackPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackDoAreaAttackPostfix", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackDoAreaAttackFinalizer", (Type[])null), (HarmonyMethod)null); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} Attack.DoAreaAttack overload(s) for alternate-sledge routing plus guaranteed sledge radius restoration."); } } private void PatchAttackMethod(Type attackType, string methodName, string patchName, bool postfix = false) { List list = (from m in AccessTools.GetDeclaredMethods(attackType) where m.Name == methodName select m).ToList(); if (list.Count == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find Attack." + methodName + ". Some timing modes may use fallback behavior only.")); return; } PatchAttackMethods(list, patchName, postfix); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} Attack.{methodName} overload(s)."); } private void PatchAttackMethodOptional(Type attackType, string methodName, string patchName, bool postfix = false) { List list = (from m in AccessTools.GetDeclaredMethods(attackType) where m.Name == methodName select m).ToList(); if (list.Count != 0) { PatchAttackMethods(list, patchName, postfix); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched optional {list.Count} Attack.{methodName} overload(s)."); } } private void PatchAttackMethodTranspiler(Type attackType, string methodName, string patchName) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown List list = (from m in AccessTools.GetDeclaredMethods(attackType) where m.Name == methodName select m).ToList(); if (list.Count == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find Attack." + methodName + ". Patch " + patchName + " is unavailable.")); return; } foreach (MethodInfo item in list) { _harmony.Patch((MethodBase)item, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), patchName, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} Attack.{methodName} overload(s) with {patchName} transpiler."); } private void PatchAttackMethods(List methods, string patchName, bool postfix) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown foreach (MethodInfo method in methods) { HarmonyMethod val = new HarmonyMethod(typeof(GooCombatOverhaulPlugin), patchName, (Type[])null); if (postfix) { _harmony.Patch((MethodBase)method, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private void PatchHumanoidBlockAttack(Type humanoidType) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_008e: Expected O, but got Unknown //IL_008e: Expected O, but got Unknown List list = (from method in AccessTools.GetDeclaredMethods(humanoidType) where method.Name == "BlockAttack" && method.ReturnType == typeof(bool) && method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(HitData) && typeof(Character).IsAssignableFrom(method.GetParameters()[1].ParameterType) select method).ToList(); foreach (MethodInfo item in list) { _harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidBlockAttackPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidBlockAttackPostfix", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidBlockAttackFinalizer", (Type[])null), (HarmonyMethod)null); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} Humanoid.BlockAttack overload(s) with native timed-block capability suppression and guaranteed restoration."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find Humanoid.BlockAttack(HitData,Character). Can Parry control is unavailable on this Valheim build."); } } private void PatchHumanoidStartAttack() { //IL_006d: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00a3: Expected O, but got Unknown //IL_00a3: Expected O, but got Unknown Type type = AccessTools.TypeByName("Humanoid"); if (type == null) { return; } List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "StartAttack" && m.ReturnType == typeof(bool) select m).ToList(); foreach (MethodInfo item in list) { _harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidStartAttackPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidStartAttackPostfix", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "HumanoidStartAttackFinalizer", (Type[])null), (HarmonyMethod)null); } if (list.Count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {list.Count} bool-returning Humanoid.StartAttack overload(s) for exact primary/secondary attack-role detection and failed-start cleanup."); } } private void PatchAttackStaminaMethods(Type attackType) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(attackType)) { if (SafeLower(declaredMethod.Name).Contains("stamina") && !(declaredMethod.ReturnType != typeof(float))) { _harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackStaminaCostPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} Attack stamina-return method(s) for stamina-rate multipliers."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find an Attack stamina-return method. StaminaRateMultiplier config may be unavailable on this Valheim build."); } } private void PatchAttackEitrMethods(Type attackType) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(attackType)) { if (SafeLower(declaredMethod.Name).Contains("eitr") && !(declaredMethod.ReturnType != typeof(float))) { _harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackEitrCostPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} Attack eitr-return method(s) for eitr-rate multipliers."); } } private void PatchAttackHealthMethods(Type attackType) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(attackType)) { if (SafeLower(declaredMethod.Name).Contains("health") && !(declaredMethod.ReturnType != typeof(float))) { _harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "AttackHealthCostPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} Attack health-return method(s) for StaffShield health consumption rate."); } } public static bool HumanoidStartAttackPrefix(object __instance, object[] __args, ref bool __result, ref bool __1, ref AlternateSledgeSecondarySwapState? __state) { __state = null; if (ModActive) { Character val = (Character)((__instance is Character) ? __instance : null); if (val != null) { if (val is Player && CharacterReportsInAttack(val) && CharacterIsInGcoSpecialAttack(val)) { __result = false; PendingAttackRoles.Remove(val); ClearHumanoidActionQueueIfPossible(val); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"special-attack-chain-block:{((Object)val).GetInstanceID()}", 0.25f, "Blocked queued/chained attack during active GCO jump/running attack for " + SafeName(val) + "."); } return false; } bool flag = __1; AttackRole role = (flag ? AttackRole.Secondary : AttackRole.Primary1); Player val2 = (Player)(object)((val is Player) ? val : null); if (val2 != null && IsSledgeAlternateMovesetActiveForWeapon(GetCurrentWeapon(val))) { float num = -1f; bool flag2 = false; try { num = val2.GetStamina(); } catch { } try { flag2 = ((Character)val2).IsOnGround(); } catch { } DebugSledgeAlternateLog($"humanoid-request:{((Object)val).GetInstanceID()}", string.Format("Humanoid.StartAttack request received: requestedSecondary={0}, grounded={1}, stamina={2}, inAttack={3}, currentAttack={4}.", flag, flag2, num.ToString(CultureInfo.InvariantCulture), CharacterReportsInAttack(val), DescribeAttackForSledgeDebug(GetFieldValueRaw(val, "m_currentAttack"))), 0.02f); } if (flag) { Player val3 = (Player)(object)((val is Player) ? val : null); if (val3 != null && IsSledgeAlternateMovesetActiveForWeapon(GetCurrentWeapon(val))) { ItemData currentWeapon = GetCurrentWeapon(val); if (currentWeapon?.m_shared == null || !TryGetTwoHandedAxeSecondaryAttackTemplate(out Attack attack) || attack == null) { __result = false; PendingAttackRoles.Remove(val); AlternateSledgeSecondaryRequests.Remove(val); DebugSledgeAlternateLog($"secondary-template-missing:{((Object)val).GetInstanceID()}", "Blocked alternate-sledge Secondary because the real two-handed-axe secondary Attack template could not be resolved. No primary/AOE fallback was used.", 0.25f); return false; } Attack secondaryAttack = currentWeapon.m_shared.m_secondaryAttack; currentWeapon.m_shared.m_secondaryAttack = attack; __state = new AlternateSledgeSecondarySwapState(val, currentWeapon, secondaryAttack, attack); AlternateSledgeSecondaryRequests.Add(val); string text = GetFieldValue(attack, "m_attackAnimation") ?? string.Empty; string text2 = GetFieldValueRaw(attack, "m_attackType")?.ToString() ?? ""; DebugSledgeAlternateLog($"secondary-template-swap:{((Object)val3).GetInstanceID()}", string.Format("Temporarily supplied the real two-handed-axe secondary template before native Humanoid.StartAttack clones it. requestMarkerNow={0}, pendingBeforeAssignment={1}, animation={2}, attackType={3}, speedFactor={4}, range={5}, rayWidth={6}, template={7}.", AlternateSledgeSecondaryRequests.Contains(val), IsPendingSecondaryAttack(val), text, text2, GetPrivateFloat(attack, "m_speedFactor", -1f).ToString(CultureInfo.InvariantCulture), GetPrivateFloat(attack, "m_attackRange", -1f).ToString(CultureInfo.InvariantCulture), GetPrivateFloat(attack, "m_attackRayWidth", -1f).ToString(CultureInfo.InvariantCulture), DescribeAttackForSledgeDebug(attack)), 0.02f); } } PendingAttackRoles[val] = new PendingAttackRoleState(role, Time.time + 0.75f); return true; } } return true; } public static void HumanoidStartAttackPostfix(object __instance, object[] __args, bool __result, AlternateSledgeSecondarySwapState? __state) { __state?.Restore(); if (!ModActive) { return; } Character val = (Character)((__instance is Character) ? __instance : null); if (val == null) { return; } if (!__result) { if (AlternateSledgeSecondaryRequests.Contains(val) || IsSledgeAlternateMovesetActiveForWeapon(GetCurrentWeapon(val))) { DebugSledgeAlternateLog($"humanoid-result-false:{((Object)val).GetInstanceID()}", string.Format("Native Humanoid.StartAttack returned false. requestMarker={0}, pendingSecondary={1}, currentAttack={2}. Cleaning request state without fallback.", AlternateSledgeSecondaryRequests.Contains(val), IsPendingSecondaryAttack(val), DescribeAttackForSledgeDebug(GetFieldValueRaw(val, "m_currentAttack"))), 0.02f); } PendingAttackRoles.Remove(val); AlternateSledgeSecondaryRequests.Remove(val); return; } object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); if (fieldValueRaw == null) { PendingAttackRoles.Remove(val); AlternateSledgeSecondaryRequests.Remove(val); return; } ItemData currentWeapon = GetCurrentWeapon(val); WeaponCategory weaponCategory = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); object fieldValueRaw2 = GetFieldValueRaw(val, "m_currentAttackIsSecondary"); bool flag = default(bool); int num; if (fieldValueRaw2 is bool) { flag = (bool)fieldValueRaw2; num = 1; } else { num = 0; } AttackRole attackRole = ((((uint)num & (flag ? 1u : 0u)) != 0) ? AttackRole.Secondary : ResolvePrimaryChainRole(fieldValueRaw)); bool flag2 = AlternateSledgeSecondaryRequests.Remove(val); if (flag2 && weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(currentWeapon)) { attackRole = AttackRole.Secondary; SledgeAlternateMovesetAttacks.Add(fieldValueRaw); } if ((RunningAttackInstances.Contains(fieldValueRaw) || JumpAttackInstances.Contains(fieldValueRaw)) && AttackInstanceRoles.TryGetValue(fieldValueRaw, out var value)) { attackRole = value; } AttackRole value2; bool flag3 = AttackInstanceRoles.TryGetValue(fieldValueRaw, out value2); AttackInstanceOwners[fieldValueRaw] = val; if (currentWeapon != null) { AttackInstanceWeapons[fieldValueRaw] = currentWeapon; } AttackInstanceRoles[fieldValueRaw] = attackRole; WeaponCategory weaponCategory2 = ResolveAttackSettingsCategory(fieldValueRaw, weaponCategory, attackRole); SetAttackState(val, weaponCategory2, attackRole, fieldValueRaw); ReapplyAuthoritativeAttackTweaksAfterRoleCorrection(fieldValueRaw, val, currentWeapon, weaponCategory, attackRole, flag3, value2); PendingAttackRoles.Remove(val); if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(currentWeapon)) { DebugSledgeAlternateLog($"humanoid-authoritative:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", string.Format("Humanoid.StartAttack completed: nativeResult={0}, consumedAltSecondaryRequest={1}, nativeSecondaryFlag={2}, authoritativeRole={3}, previousEarlyRole={4}, settingsCategory={5}, attack={6}.", __result, flag2, fieldValueRaw2, attackRole, flag3 ? value2.ToString() : "", weaponCategory2, DescribeAttackForSledgeDebug(fieldValueRaw)), 0.02f); } if (Debug(DebugAttackLifecycle)) { string text = ((flag3 && value2 != attackRole) ? $" corrected from early {value2}" : string.Empty); int hashCode = RuntimeHelpers.GetHashCode(fieldValueRaw); DebugLogThrottled($"humanoid-authoritative-role:{((Object)val).GetInstanceID()}:{hashCode}", 0.05f, $"Authoritative Humanoid.StartAttack role for {SafeName(val)} set to {weaponCategory} {attackRole}{text}; attackHash={hashCode}."); } } public static Exception? HumanoidStartAttackFinalizer(Exception? __exception, AlternateSledgeSecondarySwapState? __state) { __state?.Restore(__exception != null); if (__exception != null) { DebugSledgeAlternateLog("humanoid-exception", "Humanoid.StartAttack threw " + __exception.GetType().Name + ": " + __exception.Message + ". Restored the temporary secondary template and cleared pending alternate-sledge request state.", 0.02f); } return __exception; } private static bool CharacterIsInGcoSpecialAttack(Character character) { if ((Object)(object)character == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(character, "m_currentAttack"); if (fieldValueRaw != null && (RunningAttackInstances.Contains(fieldValueRaw) || JumpAttackInstances.Contains(fieldValueRaw))) { return true; } if (AttackStates.TryGetValue(character, out var value) && value.AttackInstance != null && (RunningAttackInstances.Contains(value.AttackInstance) || JumpAttackInstances.Contains(value.AttackInstance))) { return true; } return false; } private static void ClearHumanoidActionQueueIfPossible(Character character) { if (!((Object)(object)character == (Object)null)) { try { (GetCachedMethod(((object)character).GetType(), "ClearActionQueue") ?? GetCachedMethod(typeof(Humanoid), "ClearActionQueue"))?.Invoke(character, Array.Empty()); } catch { } SetFieldValueRaw(character, "m_attack", false); SetFieldValueRaw(character, "m_secondaryAttack", false); SetFieldValueRaw(character, "m_queuedAttackTimer", 0f); SetFieldValueRaw(character, "m_queuedSecondAttackTimer", 0f); } } private static bool TryGetNativeAttackTowardsLookDirectionState(Player player, out bool enabled, out bool runtimeProperty, out int persistedPreference) { enabled = false; runtimeProperty = false; persistedPreference = 1; if ((Object)(object)player == (Object)null) { return false; } Player val = (Player)(((Object)(object)Player.m_localPlayer != (Object)null) ? ((object)Player.m_localPlayer) : ((object)player)); runtimeProperty = val.AttackTowardsPlayerLookDir; try { persistedPreference = PlatformPrefs.GetInt("AttackTowardsPlayerLookDir", 1); } catch { persistedPreference = (runtimeProperty ? 1 : 0); } enabled = runtimeProperty; return true; } private static bool BetterFreeAimCanRun(Player player) { if (!ModActive || BetterFreeAim == null || !BetterFreeAim.Value || (Object)(object)player == (Object)null) { return false; } TryGetNativeAttackTowardsLookDirectionState(player, out var enabled, out var runtimeProperty, out var persistedPreference); if (Debug()) { DebugLogThrottled($"better-free-aim-native-state:{((Object)player).GetInstanceID()}", 1f, $"BetterFreeAim native-toggle state for {SafeName((Character)(object)player)}: runtime AttackTowardsPlayerLookDir={runtimeProperty}, persisted PlatformPrefs value={persistedPreference}. Native enabled=true bypasses BetterFreeAim; native enabled=false allows BetterFreeAim."); } if (enabled) { BetterFreeAimActiveBlocks.Remove(player); return false; } return true; } private static void TryApplyBetterFreeAimSnap(Player player) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!BetterFreeAimCanRun(player)) { return; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); if (!IsMeleeWeaponCategory((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed)) { return; } Vector3 moveDir = ((Character)player).GetMoveDir(); moveDir.y = 0f; if (((Vector3)(ref moveDir)).sqrMagnitude <= 0.0004f) { return; } ((Vector3)(ref moveDir)).Normalize(); try { TryApplyBetterFreeAimSnapUnchecked(player, "attack-start"); } catch { } } private static bool HasMeaningfulMovementInput(Player player) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return false; } Vector3 moveDir = ((Character)player).GetMoveDir(); moveDir.y = 0f; return ((Vector3)(ref moveDir)).sqrMagnitude > 0.0004f; } private static void TryApplyBetterFreeAimBlockSnap(Player player) { if (!BetterFreeAimCanRun(player) || !HasMeaningfulMovementInput(player)) { return; } try { TryApplyBetterFreeAimSnapUnchecked(player, "block-start"); } catch { } } private static void TryApplyBetterFreeAimSnapUnchecked(Player player, string reason) { //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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) Vector3 moveDir = ((Character)player).GetMoveDir(); moveDir.y = 0f; if (((Vector3)(ref moveDir)).sqrMagnitude <= 0.0004f) { return; } ((Vector3)(ref moveDir)).Normalize(); Quaternion val = Quaternion.LookRotation(moveDir); ((Component)player).transform.rotation = val; object obj = GetFieldValueRaw(player, "m_body") ?? ((Component)player).GetComponent("Rigidbody"); if (obj != null) { try { obj.GetType().GetProperty("rotation")?.SetValue(obj, val, null); } catch { } } if (Debug(DebugMovement)) { DebugLogThrottled($"better-free-aim:{reason}:{((Object)player).GetInstanceID()}", 0.25f, "BetterFreeAim snapped " + reason + " body direction toward movement input for " + SafeName((Character)(object)player) + " without changing look/crosshair direction."); } } public static bool PlayerAlwaysRotateCameraPrefix(object __instance, ref bool __result) { Player val = (Player)((__instance is Player) ? __instance : null); if (val == null || !BetterFreeAimCanRun(val)) { return true; } if (!((Character)val).IsBlocking()) { object fieldValueRaw = GetFieldValueRaw(val, "m_blocking"); bool flag = default(bool); int num; if (fieldValueRaw is bool) { flag = (bool)fieldValueRaw; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { goto IL_004f; } } if (HasMeaningfulMovementInput(val)) { __result = false; return false; } goto IL_004f; IL_004f: return true; } public static void PlayerSetControlsPrefix(Player __instance, bool block) { if (!ModActive || (Object)(object)__instance == (Object)null || !block) { return; } object fieldValueRaw = GetFieldValueRaw(__instance, "m_currentAttack"); if (fieldValueRaw != null && (CharacterReportsInAttack((Character)(object)__instance) || IsInAttackState((Character)(object)__instance)) && !BlockCanceledAttacks.Contains(fieldValueRaw)) { BlockCancelRequestedAttacks.Add(fieldValueRaw); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"block-cancel-request:{((Object)__instance).GetInstanceID()}:{RuntimeHelpers.GetHashCode(fieldValueRaw)}", 0.1f, $"Fresh block press registered block-cancel request for {SafeName((Character)(object)__instance)} attackHash={RuntimeHelpers.GetHashCode(fieldValueRaw)}."); } } } public static void HumanoidUpdateBlockPostfix(object __instance) { if (!ModActive) { return; } Player val = (Player)((__instance is Player) ? __instance : null); if (val == null) { return; } if (!((Character)val).IsBlocking()) { BetterFreeAimActiveBlocks.Remove(val); return; } bool flag = BetterFreeAimCanRun(val); if (flag && BetterFreeAimActiveBlocks.Add(val)) { TryApplyBetterFreeAimBlockSnap(val); } else if (!flag) { BetterFreeAimActiveBlocks.Remove(val); } } private static bool PlayerIsHoldingRunInput(Player player) { if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_run"); bool flag = default(bool); int num; if (fieldValueRaw is bool) { flag = (bool)fieldValueRaw; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool TryGetRunWhileBlockingConfig(Player player, out bool enabled, out float movementMultiplier) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 enabled = false; movementMultiplier = 1f; if ((Object)(object)player == (Object)null) { return false; } ItemData val = GetActiveBlocker(player); if (val?.m_shared == null) { val = GetCurrentWeapon((Character)(object)player); } if (val?.m_shared == null) { return false; } if ((int)val.m_shared.m_itemType == 5) { ShieldTypeBlockConfig shieldTypeBlockConfig = GetShieldTypeBlockConfig(val); enabled = shieldTypeBlockConfig.EnableRunningWhileBlocking.Value; movementMultiplier = shieldTypeBlockConfig.RunningWhileBlockingMovementSpeedMultiplier.Value; return true; } if (TryGetStaffBlockConfig(val, out StaffBlockConfig cfg)) { enabled = cfg.EnableRunningWhileBlocking.Value; movementMultiplier = cfg.RunningWhileBlockingMovementSpeedMultiplier.Value; return true; } WeaponCategory key = ClassifyWeapon(val); if (!WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value)) { return false; } enabled = value.EnableRunningWhileBlocking.Value; movementMultiplier = value.RunningWhileBlockingMovementSpeedMultiplier.Value; return true; } private static bool IsRunWhileBlockingEnabled(Player player) { if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_internalBlockingState"); bool flag = default(bool); int num; if (fieldValueRaw is bool) { flag = (bool)fieldValueRaw; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0 && !((Character)player).IsBlocking()) { return false; } if (!PlayerIsHoldingRunInput(player)) { return false; } if (!TryGetRunWhileBlockingConfig(player, out var enabled, out var _)) { return false; } return enabled; } public static void HumanoidCheckRunWhileBlockingPostfix(object __instance, Vector3 moveDir, float dt, ref bool __result) { if (!(!ModActive | __result)) { Player val = (Player)((__instance is Player) ? __instance : null); if (val != null && IsRunWhileBlockingEnabled(val) && !(((Vector3)(ref moveDir)).magnitude < 0.1f) && !((Character)val).IsCrouching() && !((Character)val).IsEncumbered() && !((Character)val).InDodge() && !((Character)val).IsDrawingBow()) { __result = true; } } } private static float GetBlockingMovementSpeedMultiplier(Player player) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 if ((Object)(object)player == (Object)null) { return 1f; } object fieldValueRaw = GetFieldValueRaw(player, "m_internalBlockingState"); bool flag = default(bool); int num; if (fieldValueRaw is bool) { flag = (bool)fieldValueRaw; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0 && !((Character)player).IsBlocking()) { return 1f; } ItemData activeBlocker = GetActiveBlocker(player); if (activeBlocker?.m_shared == null) { return 1f; } float num2 = 1f; StaffBlockConfig cfg; if ((int)activeBlocker.m_shared.m_itemType == 5) { num2 = Mathf.Clamp(GetShieldTypeBlockConfig(activeBlocker).BlockingMovementSpeedMultiplier.Value, 0.05f, 5f); } else if (!TryGetStaffBlockConfig(activeBlocker, out cfg)) { WeaponCategory key = ClassifyWeapon(activeBlocker); if (WeaponBlockExtras.TryGetValue(key, out WeaponBlockExtraConfig value)) { num2 = Mathf.Clamp(value.BlockingMovementSpeedMultiplier.Value, 0.05f, 5f); } } float num3 = ((GlobalBlockingMovementSpeedMultiplier != null) ? Mathf.Clamp(GlobalBlockingMovementSpeedMultiplier.Value, 0.05f, 5f) : 1f) * num2; if (IsRunWhileBlockingEnabled(player) && TryGetRunWhileBlockingConfig(player, out var _, out var movementMultiplier)) { num3 *= Mathf.Clamp(movementMultiplier, 0.05f, 5f); } return Mathf.Clamp(num3, 0.05f, 25f); } public static void CharacterUpdateWalkingBlockingMovementPrefix(Character __instance, ref BlockingMovementSpeedPatchState? __state) { __state = null; if (!ModActive) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { float blockingMovementSpeedMultiplier = GetBlockingMovementSpeedMultiplier(val); if (!Mathf.Approximately(blockingMovementSpeedMultiplier, 1f)) { __state = new BlockingMovementSpeedPatchState(val, ((Character)val).m_speed, ((Character)val).m_walkSpeed, ((Character)val).m_runSpeed, ((Character)val).m_crouchSpeed); ((Character)val).m_speed = ((Character)val).m_speed * blockingMovementSpeedMultiplier; ((Character)val).m_walkSpeed = ((Character)val).m_walkSpeed * blockingMovementSpeedMultiplier; ((Character)val).m_runSpeed = ((Character)val).m_runSpeed * blockingMovementSpeedMultiplier; ((Character)val).m_crouchSpeed = ((Character)val).m_crouchSpeed * blockingMovementSpeedMultiplier; } } } public static void CharacterUpdateWalkingBlockingMovementPostfix(Character __instance, BlockingMovementSpeedPatchState? __state) { __state?.Restore(); } public static void AttackStaminaCostPostfix(object __instance, object[] __args, ref float __result) { if (!ModActive || Mathf.Approximately(__result, 0f) || !TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon) || weapon == null) { return; } WeaponCategory category = ClassifyWeapon(weapon); AttackRole role = DetermineAttackRole(character, __instance, weapon); category = ResolveAttackSettingsCategory(__instance, category, role); JumpAttackConfig config; RunningAttackConfig config2; float num = ((character is Player && __instance != null && JumpAttackInstances.Contains(__instance) && TryGetJumpAttackConfig(category, out config)) ? Mathf.Clamp(config.StaminaRateMultiplier.Value, 0f, 10f) : ((!(character is Player) || __instance == null || !RunningAttackInstances.Contains(__instance) || !TryGetRunningAttackConfig(category, out config2)) ? Mathf.Clamp(GetConfig(category).GetStaminaRateMultiplier(role), 0f, 10f) : Mathf.Clamp(config2.StaminaRateMultiplier.Value, 0f, 10f))); if (IsThrowableAttack(__instance, weapon)) { float num2 = ((ThrowableStaminaConsumptionRate != null) ? Mathf.Clamp(ThrowableStaminaConsumptionRate.Value, 0f, 20f) : 1f); num *= num2; if (Debug(DebugRanged)) { DebugLogThrottled($"throwable-stamina:{RuntimeHelpers.GetHashCode(__instance)}", 0.25f, "Throwable stamina multiplier resolved: category/role multiplier x throwable " + num2.ToString(CultureInfo.InvariantCulture) + " = " + num.ToString(CultureInfo.InvariantCulture) + "."); } } if (!Mathf.Approximately(num, 1f)) { __result *= num; } } public static void AttackEitrCostPostfix(object __instance, object[] __args, ref float __result) { if (ModActive && !Mathf.Approximately(__result, 0f) && TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon) && weapon != null) { WeaponCategory category = ClassifyWeapon(weapon); AttackRole role = DetermineAttackRole(character, __instance, weapon); float num = Mathf.Clamp(GetConfig(category).GetEitrRateMultiplier(role), 0f, 10f); if (TryGetStaffConfig(weapon, out StaffConfig cfg)) { num *= Mathf.Clamp(cfg.EitrRateMultiplier.Value, 0f, 10f); } if (!Mathf.Approximately(num, 1f)) { __result *= num; } } } public static void AttackHealthCostPostfix(object __instance, object[] __args, ref float __result) { if (!ModActive || Mathf.Approximately(__result, 0f) || !TryGetAttackContext(__instance, __args, out Character _, out ItemData weapon) || weapon == null || !TryGetStaffConfig(weapon, out StaffConfig cfg)) { return; } float num = 1f; if (IsStaffShield(cfg) && StaffShieldActiveSettings != null) { num = Mathf.Clamp(StaffShieldActiveSettings.HealthConsumptionRate.Value, 0f, 10f); } else { if (!IsStaffSkeleton(cfg) || StaffSkeletonHealthConsumptionRate == null) { return; } num = Mathf.Clamp(StaffSkeletonHealthConsumptionRate.Value, 0f, 10f); } if (!Mathf.Approximately(num, 1f)) { __result *= num; } } private static bool IsPendingSecondaryAttack(Character character) { if ((Object)(object)character != (Object)null && PendingAttackRoles.TryGetValue(character, out var value) && value.Role == AttackRole.Secondary) { return Time.time <= value.EndTime; } return false; } private static bool IsAlternateSledgeTunedAttack(object? attackInstance, WeaponCategory category, AttackRole role) { if (category == WeaponCategory.SledgeAlt) { return true; } if (attackInstance == null || category != WeaponCategory.Sledge) { return false; } if (!SledgeAlternateMovesetAttacks.Contains(attackInstance) || JumpAttackInstances.Contains(attackInstance)) { return false; } if (role == AttackRole.Secondary || RunningAttackInstances.Contains(attackInstance)) { return true; } AttackRole attackRole = ResolvePrimaryChainRole(attackInstance); if (attackRole != AttackRole.Primary1) { return attackRole == AttackRole.Primary2; } return true; } private static WeaponCategory ResolveAttackSettingsCategory(object? attackInstance, WeaponCategory category, AttackRole role) { if (!IsAlternateSledgeTunedAttack(attackInstance, category, role)) { return category; } return WeaponCategory.SledgeAlt; } private static WeaponCategory PublicFeatureCategory(WeaponCategory category) { if (category != WeaponCategory.SledgeAlt) { return category; } return WeaponCategory.Sledge; } private static bool SamePublicWeaponCategory(WeaponCategory left, WeaponCategory right) { return PublicFeatureCategory(left) == PublicFeatureCategory(right); } private static bool IsSledgeEligibleForAlternateMoveset(ItemData? weapon) { if (weapon?.m_shared != null) { return ClassifyWeapon(weapon) == WeaponCategory.Sledge; } return false; } private static bool TryApplyAlternateMovesetToSledgeAttack(object attackInstance, ItemData? weapon) { if (attackInstance == null || !IsSledgeAlternateMovesetActiveForWeapon(weapon)) { return false; } if (!TryGetTwoHandedAxePrimaryAttackTemplate(out Attack attack) || attack == null) { return false; } string value = GetFieldValue(attack, "m_attackAnimation") ?? string.Empty; if (string.IsNullOrWhiteSpace(value)) { return false; } SetFieldValueRaw(attackInstance, "m_attackAnimation", value); if (GetFieldValueRaw(attack, "m_attackChainLevels") is int num && num > 0) { SetFieldValueRaw(attackInstance, "m_attackChainLevels", num); } string value2 = GetFieldValue(attack, "m_drawAnimationState") ?? string.Empty; SetFieldValueRaw(attackInstance, "m_drawAnimationState", value2); SledgeAlternateMovesetAttacks.Add(attackInstance); if (weapon != null) { AttackInstanceWeapons[attackInstance] = weapon; } return true; } private static bool IsSledgeAlternateMovesetActiveForWeapon(ItemData? weapon) { if (SledgeUseAlternateMoveset != null && SledgeUseAlternateMoveset.Value) { return IsSledgeEligibleForAlternateMoveset(weapon); } return false; } private static void ApplyAlternateSledgeTriggerShapeIfNeeded(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { if (!TryGetAlternateSledgeDirectHitRaySettings(attackInstance, character, weapon, category, role, out var height, out var range, out var angle, out var thickness)) { RestoreAlternateSledgeHitRayTweaks(attackInstance); return; } TrySetAlternateSledgeAttackType(attackInstance, "Horizontal"); TrySetAlternateSledgeFloatField(attackInstance, "m_attackHeight", height); TrySetAlternateSledgeFloatField(attackInstance, "m_attackHeightChar1", 0f); TrySetAlternateSledgeFloatField(attackInstance, "m_attackHeightChar2", 0f); TrySetAlternateSledgeFloatField(attackInstance, "m_attackRange", range); TrySetAlternateSledgeFloatField(attackInstance, "m_attackAngle", angle); TrySetAlternateSledgeFloatField(attackInstance, "m_attackRayWidth", thickness); TrySetAlternateSledgeFloatField(attackInstance, "m_attackRayWidthCharExtra", 0f); TrySetAlternateSledgeBoolField(attackInstance, "m_multiHit", GetEffectiveAttackMultiHitEnabled(attackInstance, category, role)); TrySetAlternateSledgeBoolField(attackInstance, "m_hitThroughWalls", GetEffectiveAttackHitThroughWallsEnabled(attackInstance, category, role)); TrySetAlternateSledgeBoolField(attackInstance, "m_lowerDamagePerHit", GetEffectiveMultiTargetDamagePenaltyMode(attackInstance, category, role) == AttackMultiTargetDamagePenaltyMode.Enabled); TrySetAlternateSledgeObjectField(attackInstance, "m_spawnOnTrigger", null); TrySetAlternateSledgeDefaultValueField(attackInstance, "m_triggerEffect"); } private static bool TryGetAlternateSledgeDirectHitRaySettings(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role, out float height, out float range, out float angle, out float thickness) { height = 0f; range = 0f; angle = 0f; thickness = 0f; if (attackInstance == null || !(character is Player) || category != WeaponCategory.Sledge || !IsSledgeAlternateMovesetActiveForWeapon(weapon)) { return false; } if (!IsFeatureApplied(WeaponCategory.Sledge, ModFeature.HitRay)) { DebugSledgeAlternateLog($"settings-reject-feature:{RuntimeHelpers.GetHashCode(attackInstance)}", "Direct hit-ray settings rejected because the public Sledge Hit Ray feature toggle is disabled.", 0.25f); return false; } if (role == AttackRole.Secondary || JumpAttackInstances.Contains(attackInstance)) { DebugSledgeAlternateLog($"settings-reject-role:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Direct hit-ray settings rejected by role: role={role}, jump={JumpAttackInstances.Contains(attackInstance)}.", 0.25f); return false; } bool flag = RunningAttackInstances.Contains(attackInstance); AttackRole attackRole = ResolvePrimaryChainRole(attackInstance); if (!flag && (!SledgeAlternateMovesetAttacks.Contains(attackInstance) || (attackRole != AttackRole.Primary1 && attackRole != AttackRole.Primary2))) { DebugSledgeAlternateLog($"settings-reject-direct:{RuntimeHelpers.GetHashCode(attackInstance)}", string.Format("Direct hit-ray settings rejected: running={0}, chainRole={1}, markedAlt={2}, currentChain={3}, attackType={4}.", flag, attackRole, SledgeAlternateMovesetAttacks.Contains(attackInstance), GetPrivateInt(attackInstance, "m_currentAttackCainLevel", -1), GetFieldValueRaw(attackInstance, "m_attackType")?.ToString() ?? ""), 0.25f); return false; } AttackRole role2 = (flag ? role : attackRole); float num; float num2; float num3; float num4; if (flag) { num = ((SledgeRunningDirectHitRayStartHeightMeters != null) ? SledgeRunningDirectHitRayStartHeightMeters.Value : 1.3f); num2 = ((SledgeRunningDirectHitRayRangeMeters != null) ? SledgeRunningDirectHitRayRangeMeters.Value : 2.4f); num3 = ((SledgeRunningDirectHitRayAngleDegrees != null) ? SledgeRunningDirectHitRayAngleDegrees.Value : 135f); num4 = ((SledgeRunningDirectHitRayThicknessMeters != null) ? SledgeRunningDirectHitRayThicknessMeters.Value : 0.65f); } else if (attackRole == AttackRole.Primary2) { num = ((SledgePrimary2DirectHitRayStartHeightMeters != null) ? SledgePrimary2DirectHitRayStartHeightMeters.Value : 1.3f); num2 = ((SledgePrimary2DirectHitRayRangeMeters != null) ? SledgePrimary2DirectHitRayRangeMeters.Value : 2.4f); num3 = ((SledgePrimary2DirectHitRayAngleDegrees != null) ? SledgePrimary2DirectHitRayAngleDegrees.Value : 135f); num4 = ((SledgePrimary2DirectHitRayThicknessMeters != null) ? SledgePrimary2DirectHitRayThicknessMeters.Value : 0.65f); } else { num = ((SledgePrimary1DirectHitRayStartHeightMeters != null) ? SledgePrimary1DirectHitRayStartHeightMeters.Value : 1.3f); num2 = ((SledgePrimary1DirectHitRayRangeMeters != null) ? SledgePrimary1DirectHitRayRangeMeters.Value : 2.4f); num3 = ((SledgePrimary1DirectHitRayAngleDegrees != null) ? SledgePrimary1DirectHitRayAngleDegrees.Value : 135f); num4 = ((SledgePrimary1DirectHitRayThicknessMeters != null) ? SledgePrimary1DirectHitRayThicknessMeters.Value : 0.65f); } float num5 = Mathf.Clamp(GetEffectiveAttackHitboxHeightMultiplier(attackInstance, category, role2), 0f, 20f); float num6 = Mathf.Clamp(GetEffectiveAttackRangeMultiplier(attackInstance, category, role2), 0f, 20f); float num7 = Mathf.Clamp(GetEffectiveAttackAngleMultiplier(attackInstance, category, role2), 0f, 20f); float num8 = Mathf.Clamp(GetEffectiveAttackRayWidthMultiplier(attackInstance, category, role2), 0f, 20f); height = ClampAbsoluteMeters(num * num5, 0f, 10f); range = ClampAbsoluteMeters(num2 * num6, 0f, 20f); angle = Mathf.Clamp(num3 * num7, 0f, 360f); thickness = ClampAbsoluteMeters(num4 * num8, 0f, 10f); DebugSledgeAlternateLog($"direct-shape:{RuntimeHelpers.GetHashCode(attackInstance)}", "Direct sledge shape resolved: baseHeight=" + num.ToString(CultureInfo.InvariantCulture) + ", heightMultiplier=" + num5.ToString(CultureInfo.InvariantCulture) + ", baseRange=" + num2.ToString(CultureInfo.InvariantCulture) + ", rangeMultiplier=" + num6.ToString(CultureInfo.InvariantCulture) + ", baseAngle=" + num3.ToString(CultureInfo.InvariantCulture) + ", angleMultiplier=" + num7.ToString(CultureInfo.InvariantCulture) + ", baseThickness=" + num4.ToString(CultureInfo.InvariantCulture) + ", thicknessMultiplier=" + num8.ToString(CultureInfo.InvariantCulture) + ".", 0.05f); return true; } private static float ClampAbsoluteMeters(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } return Mathf.Clamp(value, min, max); } private static bool TrySetAlternateSledgeAttackType(object attackInstance, string enumName) { if (attackInstance == null || string.IsNullOrWhiteSpace(enumName)) { return false; } FieldInfo cachedField = GetCachedField(attackInstance.GetType(), "m_attackType"); if (cachedField == null || !cachedField.FieldType.IsEnum) { return false; } try { object value = Enum.Parse(cachedField.FieldType, enumName); CaptureAlternateSledgeFieldOriginal(attackInstance, cachedField); cachedField.SetValue(attackInstance, value); return true; } catch { return false; } } private static bool TrySetAlternateSledgeFloatField(object attackInstance, string fieldName, float value) { if (attackInstance == null || string.IsNullOrWhiteSpace(fieldName)) { return false; } FieldInfo cachedField = GetCachedField(attackInstance.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(float)) { return false; } try { CaptureAlternateSledgeFieldOriginal(attackInstance, cachedField); cachedField.SetValue(attackInstance, value); return true; } catch { return false; } } private static bool TrySetAlternateSledgeBoolField(object attackInstance, string fieldName, bool value) { if (attackInstance == null || string.IsNullOrWhiteSpace(fieldName)) { return false; } FieldInfo cachedField = GetCachedField(attackInstance.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(bool)) { return false; } try { CaptureAlternateSledgeFieldOriginal(attackInstance, cachedField); cachedField.SetValue(attackInstance, value); return true; } catch { return false; } } private static bool TrySetAlternateSledgeObjectField(object attackInstance, string fieldName, object? value) { if (attackInstance == null || string.IsNullOrWhiteSpace(fieldName)) { return false; } FieldInfo cachedField = GetCachedField(attackInstance.GetType(), fieldName); if (cachedField == null) { return false; } if (value == null && cachedField.FieldType.IsValueType) { return false; } if (value != null && !cachedField.FieldType.IsInstanceOfType(value)) { return false; } try { CaptureAlternateSledgeFieldOriginal(attackInstance, cachedField); cachedField.SetValue(attackInstance, value); return true; } catch { return false; } } private static bool TrySetAlternateSledgeDefaultValueField(object attackInstance, string fieldName) { if (attackInstance == null || string.IsNullOrWhiteSpace(fieldName)) { return false; } FieldInfo cachedField = GetCachedField(attackInstance.GetType(), fieldName); if (cachedField == null) { return false; } try { object value = (cachedField.FieldType.IsValueType ? Activator.CreateInstance(cachedField.FieldType) : null); CaptureAlternateSledgeFieldOriginal(attackInstance, cachedField); cachedField.SetValue(attackInstance, value); return true; } catch { return false; } } private static void CaptureAlternateSledgeFieldOriginal(object attackInstance, FieldInfo field) { if (attackInstance == null || field == null) { return; } if (!AlternateSledgeHitRayContexts.TryGetValue(attackInstance, out List value)) { value = new List(); AlternateSledgeHitRayContexts[attackInstance] = value; } foreach (SuppressedFieldValue item in value) { if (item.Target == attackInstance && item.Field == field) { return; } } value.Add(new SuppressedFieldValue(attackInstance, field, field.GetValue(attackInstance))); } private static void RestoreAlternateSledgeHitRayTweaks(object? attackInstance) { if (attackInstance == null || !AlternateSledgeHitRayContexts.TryGetValue(attackInstance, out List value)) { return; } AlternateSledgeHitRayContexts.Remove(attackInstance); for (int num = value.Count - 1; num >= 0; num--) { SuppressedFieldValue suppressedFieldValue = value[num]; try { suppressedFieldValue.Field.SetValue(suppressedFieldValue.Target, suppressedFieldValue.OriginalValue); } catch { } } } private static void RestoreAllAlternateSledgeHitRayTweaks() { if (AlternateSledgeHitRayContexts.Count == 0) { return; } foreach (object item in AlternateSledgeHitRayContexts.Keys.ToList()) { RestoreAlternateSledgeHitRayTweaks(item); } } private static void TryApplyBetterLightningEffectsToAttack(object attackInstance, ItemData? weapon) { if (attackInstance != null && weapon?.m_shared != null && (IsHimminAflWeapon(weapon) || IsDundrWeapon(weapon)) && TryGetLightningChainSpawnOnHitTemplate(out GameObject template) && !((Object)(object)template == (Object)null)) { if (IsHimminAflWeapon(weapon)) { ApplyBetterLightningSpawnOnHit(attackInstance, template, Mathf.Clamp01((HimminAflLightningChainChance != null) ? HimminAflLightningChainChance.Value : 0.25f)); } else if (IsDundrWeapon(weapon)) { ApplyBetterLightningSpawnOnHit(attackInstance, template, Mathf.Clamp01((DundrLightningChainChancePerPellet != null) ? DundrLightningChainChancePerPellet.Value : 0.02f)); } } } private static void ApplyBetterLightningSpawnOnHit(object attackInstance, GameObject spawnTemplate, float chance) { if (attackInstance != null && !((Object)(object)spawnTemplate == (Object)null) && !(chance <= 0f)) { if (!BetterLightningOriginalAttackSpawnStates.ContainsKey(attackInstance)) { Dictionary betterLightningOriginalAttackSpawnStates = BetterLightningOriginalAttackSpawnStates; object? fieldValueRaw = GetFieldValueRaw(attackInstance, "m_spawnOnHit"); betterLightningOriginalAttackSpawnStates[attackInstance] = new LightningAttackSpawnState((GameObject?)((fieldValueRaw is GameObject) ? fieldValueRaw : null), (GetFieldValueRaw(attackInstance, "m_spawnOnHitChance") is float num) ? num : 0f); } SetFieldValueRaw(attackInstance, "m_spawnOnHit", spawnTemplate); SetFieldValueRaw(attackInstance, "m_spawnOnHitChance", Mathf.Clamp01(chance)); } } private static void RestoreBetterLightningAttackSpawnTweaks(object? attackInstance) { if (attackInstance != null && BetterLightningOriginalAttackSpawnStates.TryGetValue(attackInstance, out LightningAttackSpawnState value)) { BetterLightningOriginalAttackSpawnStates.Remove(attackInstance); SetFieldValueRaw(attackInstance, "m_spawnOnHit", value.SpawnOnHit); SetFieldValueRaw(attackInstance, "m_spawnOnHitChance", value.SpawnOnHitChance); } } private static bool WeaponLooksLightningCapable(ItemData? weapon) { if (weapon?.m_shared == null) { return false; } if (IsDundrWeapon(weapon) || IsHimminAflWeapon(weapon)) { return true; } string text = SafeLower(GetItemPrefabId(weapon) + " " + weapon.m_shared.m_name); if (text.Contains("lightning") || text.Contains("thunder") || text.Contains("himmin") || text.Contains("himin") || text.Contains("dundr")) { return true; } try { if (weapon.m_shared.m_damages.m_lightning > 0f || weapon.m_shared.m_damagesPerLevel.m_lightning > 0f) { return true; } } catch { } return false; } private static bool IsDundrWeapon(ItemData? weapon) { if (weapon?.m_shared == null) { return false; } string text = SafeLower(GetItemPrefabId(weapon) + " " + weapon.m_shared.m_name); if (!text.Contains("stafflightning")) { return text.Contains("dundr"); } return true; } private static bool IsHimminAflWeapon(ItemData? weapon) { if (weapon?.m_shared == null) { return false; } string text = SafeLower(GetItemPrefabId(weapon) + " " + weapon.m_shared.m_name); if (!text.Contains("atgeirhimmin") && !text.Contains("himmin")) { return text.Contains("himin"); } return true; } private static bool TryGetLightningChainSpawnOnHitTemplate(out GameObject? template) { template = null; if ((Object)(object)CachedLightningChainSpawnOnHitTemplate != (Object)null) { template = CachedLightningChainSpawnOnHitTemplate; return true; } if (LightningChainSpawnOnHitLookupAttempted || (Object)(object)ObjectDB.instance == (Object)null) { return false; } LightningChainSpawnOnHitLookupAttempted = true; try { foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemData val = item.GetComponent()?.m_itemData; if (val?.m_shared != null && WeaponLooksLightningCapable(val) && !IsHimminAflWeapon(val)) { GameObject bestSpawnOnHitTemplate = GetBestSpawnOnHitTemplate(val, preferChainAoe: true); if ((Object)(object)bestSpawnOnHitTemplate != (Object)null) { template = bestSpawnOnHitTemplate; CachedLightningChainSpawnOnHitTemplate = bestSpawnOnHitTemplate; return true; } } } } catch { } return false; } private static GameObject? GetBestSpawnOnHitTemplate(ItemData? item, bool preferChainAoe) { if (item?.m_shared == null) { return null; } object? fieldValueRaw = GetFieldValueRaw(item.m_shared.m_attack, "m_spawnOnHit"); GameObject val = (GameObject)((fieldValueRaw is GameObject) ? fieldValueRaw : null); object? fieldValueRaw2 = GetFieldValueRaw(item.m_shared.m_secondaryAttack, "m_spawnOnHit"); GameObject val2 = (GameObject)((fieldValueRaw2 is GameObject) ? fieldValueRaw2 : null); GameObject spawnOnHit = item.m_shared.m_spawnOnHit; if (preferChainAoe) { if (IsChainAoeTemplate(val)) { return val; } if (IsChainAoeTemplate(val2)) { return val2; } if (IsChainAoeTemplate(spawnOnHit)) { return spawnOnHit; } } return val ?? val2 ?? spawnOnHit; } private static bool IsChainAoeTemplate(GameObject? prefab) { if ((Object)(object)prefab == (Object)null) { return false; } try { Aoe componentInChildren = prefab.GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(componentInChildren, "m_chainObj"); object fieldValueRaw2 = GetFieldValueRaw(componentInChildren, "m_chainStartChance"); object fieldValueRaw3 = GetFieldValueRaw(componentInChildren, "m_chainChancePerTarget"); return fieldValueRaw is GameObject || (fieldValueRaw2 is float num && num > 0f) || (fieldValueRaw3 is float num2 && num2 > 0f); } catch { return false; } } private static bool TryGetPrimaryAttackFromItemPrefab(string prefabName, out Attack? attack) { attack = null; if (string.IsNullOrWhiteSpace(prefabName) || (Object)(object)ObjectDB.instance == (Object)null) { return false; } try { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); attack = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null)?.m_itemData?.m_shared?.m_attack; return attack != null; } catch { attack = null; return false; } } private static bool TryGetTwoHandedAxePrimaryAttackTemplate(out Attack? attack) { attack = null; if (CachedTwoHandedAxePrimaryAttackTemplate != null) { attack = CachedTwoHandedAxePrimaryAttackTemplate; return true; } if (TwoHandedAxePrimaryAttackTemplateLookupAttempted) { return false; } if ((Object)(object)ObjectDB.instance == (Object)null) { return false; } TwoHandedAxePrimaryAttackTemplateLookupAttempted = true; string[] array = new string[3] { "BattleaxeCrystal", "Battleaxe", "AxeBerzerkr" }; for (int i = 0; i < array.Length; i++) { if (TryGetPrimaryAttackFromItemPrefab(array[i], out attack) && attack != null) { CachedTwoHandedAxePrimaryAttackTemplate = attack; return true; } } try { if ((Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items != null) { foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemData val = item.GetComponent()?.m_itemData; if (val?.m_shared != null && ClassifyWeapon(val) == WeaponCategory.TwoHandedAxe) { Attack attack2 = val.m_shared.m_attack; string value = ((attack2 != null) ? (GetFieldValue(attack2, "m_attackAnimation") ?? string.Empty) : string.Empty); if (attack2 != null && !string.IsNullOrWhiteSpace(value)) { attack = attack2; CachedTwoHandedAxePrimaryAttackTemplate = attack2; return true; } } } } } catch { } return false; } private static bool TryGetSecondaryAttackFromItemPrefab(string prefabName, out Attack? attack) { attack = null; if (string.IsNullOrWhiteSpace(prefabName) || (Object)(object)ObjectDB.instance == (Object)null) { return false; } try { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); attack = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null)?.m_itemData?.m_shared?.m_secondaryAttack; return attack != null && !string.IsNullOrWhiteSpace(GetFieldValue(attack, "m_attackAnimation") ?? string.Empty); } catch { attack = null; return false; } } private static bool TryGetTwoHandedAxeSecondaryAttackTemplate(out Attack? attack) { attack = null; if (CachedTwoHandedAxeSecondaryAttackTemplate != null) { attack = CachedTwoHandedAxeSecondaryAttackTemplate; return true; } if (TwoHandedAxeSecondaryAttackTemplateLookupAttempted || (Object)(object)ObjectDB.instance == (Object)null) { return false; } TwoHandedAxeSecondaryAttackTemplateLookupAttempted = true; string[] array = new string[2] { "BattleaxeCrystal", "Battleaxe" }; for (int i = 0; i < array.Length; i++) { if (TryGetSecondaryAttackFromItemPrefab(array[i], out attack) && attack != null) { CachedTwoHandedAxeSecondaryAttackTemplate = attack; return true; } } try { foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemData val = item.GetComponent()?.m_itemData; if (val?.m_shared != null && ClassifyWeapon(val) == WeaponCategory.TwoHandedAxe) { Attack secondaryAttack = val.m_shared.m_secondaryAttack; string value = ((secondaryAttack != null) ? (GetFieldValue(secondaryAttack, "m_attackAnimation") ?? string.Empty) : string.Empty); if (secondaryAttack != null && !string.IsNullOrWhiteSpace(value)) { attack = secondaryAttack; CachedTwoHandedAxeSecondaryAttackTemplate = secondaryAttack; return true; } } } } catch { } return false; } private static bool TryCopyAttackFields(object targetAttack, object sourceAttack) { if (targetAttack == null || sourceAttack == null) { return false; } Type type = targetAttack.GetType(); Type type2 = sourceAttack.GetType(); bool result = false; foreach (FieldInfo allInstanceField in GetAllInstanceFields(type)) { if (allInstanceField.IsInitOnly || allInstanceField.IsLiteral) { continue; } FieldInfo cachedField = GetCachedField(type2, allInstanceField.Name); if (!(cachedField == null) && !(cachedField.FieldType != allInstanceField.FieldType)) { try { allInstanceField.SetValue(targetAttack, cachedField.GetValue(sourceAttack)); result = true; } catch { } } } return result; } private static bool TryNormalizeAlternateSledgeSecondaryAttack(object attackInstance, out string failureReason) { failureReason = string.Empty; if (attackInstance == null) { failureReason = "attack instance was null"; return false; } if (!TryGetTwoHandedAxeSecondaryAttackTemplate(out Attack attack) || attack == null) { failureReason = "two-handed-axe secondary template was unavailable"; return false; } string text = GetFieldValue(attack, "m_attackAnimation") ?? string.Empty; string text2 = GetFieldValueRaw(attack, "m_attackType")?.ToString() ?? string.Empty; if (string.IsNullOrWhiteSpace(text)) { failureReason = "two-handed-axe secondary template had no attack animation"; return false; } if (string.Equals(text2, "Area", StringComparison.OrdinalIgnoreCase)) { failureReason = "resolved two-handed-axe secondary template was unexpectedly an Area attack"; return false; } if (!TryCopyAttackFields(attackInstance, attack)) { failureReason = "no compatible Attack fields could be copied from the two-handed-axe secondary template"; return false; } string text3 = GetFieldValue(attackInstance, "m_attackAnimation") ?? string.Empty; string text4 = GetFieldValueRaw(attackInstance, "m_attackType")?.ToString() ?? string.Empty; if (!string.Equals(text3, text, StringComparison.Ordinal)) { failureReason = "normalized animation mismatch: expected '" + text + "', got '" + text3 + "'"; return false; } if (string.Equals(text4, "Area", StringComparison.OrdinalIgnoreCase)) { failureReason = "normalized attack still had AttackType.Area"; return false; } if (!string.Equals(text4, text2, StringComparison.OrdinalIgnoreCase)) { failureReason = "normalized attack type mismatch: expected '" + text2 + "', got '" + text4 + "'"; return false; } return true; } private static bool TryValidateAlternateSledgeSecondaryAttack(object attackInstance, out string failureReason) { failureReason = string.Empty; if (attackInstance == null) { failureReason = "attack instance was null"; return false; } if (!TryGetTwoHandedAxeSecondaryAttackTemplate(out Attack attack) || attack == null) { failureReason = "two-handed-axe secondary template was unavailable"; return false; } string text = GetFieldValue(attack, "m_attackAnimation") ?? string.Empty; string text2 = GetFieldValueRaw(attack, "m_attackType")?.ToString() ?? string.Empty; string text3 = GetFieldValue(attackInstance, "m_attackAnimation") ?? string.Empty; string text4 = GetFieldValueRaw(attackInstance, "m_attackType")?.ToString() ?? string.Empty; if (string.IsNullOrWhiteSpace(text) || string.Equals(text2, "Area", StringComparison.OrdinalIgnoreCase)) { failureReason = "invalid two-handed-axe secondary template: animation='" + text + "', type='" + text2 + "'"; return false; } if (!string.Equals(text3, text, StringComparison.Ordinal)) { failureReason = "animation drifted before hitbox: expected '" + text + "', got '" + text3 + "'"; return false; } if (string.Equals(text4, "Area", StringComparison.OrdinalIgnoreCase)) { failureReason = "attack type drifted to Area before hitbox"; return false; } if (!string.Equals(text4, text2, StringComparison.OrdinalIgnoreCase)) { failureReason = "attack type drifted before hitbox: expected '" + text2 + "', got '" + text4 + "'"; return false; } return true; } private static string DescribeAttackForSledgeDebug(object? attackInstance) { if (attackInstance == null) { return ""; } try { string text = GetFieldValue(attackInstance, "m_attackAnimation") ?? string.Empty; string text2 = GetFieldValueRaw(attackInstance, "m_attackType")?.ToString() ?? ""; int privateInt = GetPrivateInt(attackInstance, "m_currentAttackCainLevel", -1); int privateInt2 = GetPrivateInt(attackInstance, "m_attackChainLevels", -1); float privateFloat = GetPrivateFloat(attackInstance, "m_attackRange", -1f); float privateFloat2 = GetPrivateFloat(attackInstance, "m_attackRayWidth", -1f); bool flag = GetFieldValueRaw(attackInstance, "m_triggerEffect") != null; bool flag2 = GetFieldValueRaw(attackInstance, "m_hitEffect") != null; bool flag3 = GetFieldValueRaw(attackInstance, "m_spawnOnTrigger") != null; return $"hash={RuntimeHelpers.GetHashCode(attackInstance)}, animation='{text}', type={text2}, chain={privateInt}/{privateInt2}, range={privateFloat.ToString(CultureInfo.InvariantCulture)}, rayWidth={privateFloat2.ToString(CultureInfo.InvariantCulture)}, triggerEffect={flag}, hitEffect={flag2}, spawnOnTrigger={flag3}"; } catch (Exception ex) { return $"hash={RuntimeHelpers.GetHashCode(attackInstance)}, describeFailed={ex.GetType().Name}"; } } public static void HumanoidSetupAnimationStatePostfix(object __instance) { if (!ModActive) { return; } Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); if (IsSledgeAlternateMovesetActiveForWeapon(currentWeapon)) { SetSledgeAnimationState(val, currentWeapon, useAlternateState: true); } } } private static void SetSledgeAnimationState(Player player, ItemData? weapon, bool useAlternateState) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } AnimationState val = (AnimationState)(useAlternateState ? 8 : ((weapon?.m_shared == null) ? 2 : ((int)weapon.m_shared.m_animationState))); try { GetCachedMethod(((object)player).GetType(), "SetAnimationState", new Type[1] { typeof(AnimationState) })?.Invoke(player, new object[1] { val }); } catch { } } public static bool AttackStartPrefix(object __instance, object[] __args, ref bool __result) { if (!ModActive || __instance == null) { return true; } if (!TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return true; } ResetAttackInstanceForFreshStart(__instance); Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return true; } WeaponCategory weaponCategory = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); bool flag = CharacterReportsInAttack((Character)(object)val) || IsInAttackState((Character)(object)val); bool flag2 = PlayerStartedAttackAirborne(val); bool flag3 = AlternateSledgeSecondaryRequests.Contains((Character)(object)val); bool flag4 = IsPendingSecondaryAttack((Character)(object)val); bool flag5 = weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon) && (flag3 || flag4); AttackInstanceStartedWhileAlreadyInAttack[__instance] = flag; AttackInstanceStartedAirborne[__instance] = flag2; if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon)) { float num = -1f; bool flag6 = false; try { num = val.GetStamina(); } catch { } try { flag6 = ((Character)val).IsOnGround(); } catch { } DebugSledgeAlternateLog($"attack-start-entry:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Attack.Start entry: requestMarker={flag3}, pendingSecondary={flag4}, resolvedAltSecondary={flag5}, grounded={flag6}, startedAirborne={flag2}, stamina={num.ToString(CultureInfo.InvariantCulture)}, alreadyInAttack={flag}, attack={DescribeAttackForSledgeDebug(__instance)}.", 0.02f); } if (flag5) { string text = DescribeAttackForSledgeDebug(__instance); if (!TryNormalizeAlternateSledgeSecondaryAttack(__instance, out string failureReason)) { __result = false; PendingAttackRoles.Remove((Character)(object)val); AlternateSledgeSecondaryRequests.Remove((Character)(object)val); SledgeAlternateMovesetAttacks.Remove(__instance); AttackInstanceRoles.Remove(__instance); AttackInstanceWeapons.Remove(__instance); DebugSledgeAlternateLog($"secondary-normalization-blocked:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", "Blocked alternate-sledge Secondary before native Attack.Start because the real two-handed-axe secondary could not be normalized safely. reason=" + failureReason + "; before=" + text + "; after=" + DescribeAttackForSledgeDebug(__instance) + ". No sledge primary or Area fallback was allowed.", 0.02f); return false; } SledgeAlternateMovesetAttacks.Add(__instance); AttackInstanceRoles[__instance] = AttackRole.Secondary; if (weapon != null) { AttackInstanceWeapons[__instance] = weapon; } DebugSledgeAlternateLog($"secondary-normalized:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Alternate-sledge Secondary is normalized to the real two-handed-axe secondary before native Attack.Start. marker={flag3}, pending={flag4}; before={text}; after={DescribeAttackForSledgeDebug(__instance)}.", 0.02f); } TryApplyBetterLightningEffectsToAttack(__instance, weapon); if (weaponCategory == WeaponCategory.Sledge && !flag2 && !flag5 && TryApplyAlternateMovesetToSledgeAttack(__instance, weapon)) { DebugSledgeAlternateLog($"primary-chain-selected:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Selected alternate-sledge primary-chain route because no authoritative Secondary request was active. requestMarker={flag3}, pendingSecondary={flag4}, attack={DescribeAttackForSledgeDebug(__instance)}.", 0.05f); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"sledge-alternate-moveset:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", 0.5f, "Sledge primary-chain attack for " + SafeName((Character)(object)val) + " is using the experimental alternate moveset; Primary1/Primary2/running use direct hit rays; Secondary uses the real two-handed-axe secondary attack with native hit rays and dedicated SledgeAlt tuning; Primary3 keeps normal Sledge AOE settings."); } } if (!IsMeleeWeaponCategory(weaponCategory)) { return true; } if (flag5 || IsPendingSecondaryAttack((Character)(object)val)) { return true; } if (TryStartJumpAttack(__instance, __args, val, weaponCategory, weapon)) { return true; } if (flag) { return true; } if (weaponCategory == WeaponCategory.Sledge && !SledgeAlternateMovesetAttacks.Contains(__instance)) { DebugSledgeAlternateLog($"running-reject-notalt:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Sledge running attack rejected because attack was not marked as alternate. altToggle={SledgeUseAlternateMoveset?.Value}, weaponEligible={IsSledgeEligibleForAlternateMoveset(weapon)}, startedAirborne={flag2}, requestMarker={flag3}, secondaryPending={flag4}.", 0.25f); return true; } if (weaponCategory == WeaponCategory.DualAxes) { return true; } WeaponCategory category = ResolveAttackSettingsCategory(__instance, weaponCategory, DetermineAttackRole((Character?)(object)val, __instance, weapon)); if (!TryGetRunningAttackConfig(category, out RunningAttackConfig config) || !config.Enabled.Value) { return true; } if (weaponCategory == WeaponCategory.Sledge && SledgeAlternateMovesetAttacks.Contains(__instance)) { if (!PlayerIsActuallyRunning(val)) { DebugSledgeAlternateLog($"running-reject-notrunning:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", "Sledge running attack rejected because PlayerIsActuallyRunning=false; velocity=" + GetPlayerVelocityMagnitude(val).ToString(CultureInfo.InvariantCulture) + ", min=" + config.MinimumVelocity.Value.ToString(CultureInfo.InvariantCulture) + ".", 0.25f); return true; } if (!PlayerMeetsRunningAttackVelocity(val, config.MinimumVelocity.Value)) { DebugSledgeAlternateLog($"running-reject-velocity:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", "Sledge running attack rejected by velocity; velocity=" + GetPlayerVelocityMagnitude(val).ToString(CultureInfo.InvariantCulture) + ", min=" + config.MinimumVelocity.Value.ToString(CultureInfo.InvariantCulture) + ".", 0.25f); return true; } if (!TryApplyRunningAttackSelection(__instance, __args, category, weapon, config, out string forcedText)) { AttackInstanceRoles[__instance] = AttackRole.Primary1; forcedText = "failed to force configured running moveset; falling back to Primary1 direct hit-ray"; } RunningAttackInstances.Add(__instance); DebugSledgeAlternateLog($"running-accepted:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Sledge running attack accepted; {forcedText}; role={DetermineAttackRole((Character?)(object)val, __instance, weapon)}, velocity={GetPlayerVelocityMagnitude(val).ToString(CultureInfo.InvariantCulture)}; running modifiers: damage={config.DamageMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, stagger={config.StaggerMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, stamina={config.StaminaRateMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, animation={config.AnimationSpeedMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, recovery={config.RecoveryAnimationSpeedMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, movement={config.MovementSpeedMultiplier.Value.ToString(CultureInfo.InvariantCulture)}, rotation={config.RotationFactor.Value.ToString(CultureInfo.InvariantCulture)}, lunge={config.LungeDistanceMultiplier.Value.ToString(CultureInfo.InvariantCulture)}.", 0.05f); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"running-attack:{((Object)val).GetInstanceID()}:{weaponCategory}", 0.5f, "Running attack detected for " + SafeName((Character)(object)val) + " using alternate sledge direct hit ray; " + forcedText + "."); } return true; } if (!PlayerIsActuallyRunning(val)) { return true; } if (!PlayerMeetsRunningAttackVelocity(val, config.MinimumVelocity.Value)) { return true; } if (!TryApplyRunningAttackSelection(__instance, __args, category, weapon, config, out string forcedText2)) { return true; } RunningAttackInstances.Add(__instance); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"running-attack:{((Object)val).GetInstanceID()}:{weaponCategory}", 0.5f, $"Running attack detected for {SafeName((Character)(object)val)} using {weaponCategory}; {forcedText2}."); } return true; } public static void AttackStartPostfix(object __instance, object[] __args, bool __result) { if (!ModActive) { return; } if (!__result) { CleanupAttackInstance(__instance, null); } else { if (!TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return; } CleanupPreviousAttackContextsForCharacter(character, __instance); WeaponCategory weaponCategory = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); bool value; bool flag = AttackInstanceStartedWhileAlreadyInAttack.TryGetValue(__instance, out value) && value; if (!BetterFreeAimPreAppliedAttacks.Remove(__instance) && !flag) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && IsMeleeWeaponCategory(weaponCategory) && BetterFreeAimCanRun(val)) { TryApplyBetterFreeAimSnap(val); } } ApplyForcedChainPostStart(__instance); AttackRole attackRole = DetermineAttackRole(character, __instance, weapon); WeaponCategory weaponCategory2 = ResolveAttackSettingsCategory(__instance, weaponCategory, attackRole); SetAttackState(character, weaponCategory2, attackRole, __instance); PendingAttackRoles.Remove(character); ClearBalancedHyperArmorCompleted(__instance); if (weaponCategory == WeaponCategory.Sledge && character is Player && IsSledgeAlternateMovesetActiveForWeapon(weapon)) { DebugSledgeAlternateLog($"attack-start-result:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Attack.Start returned true: resolvedRole={attackRole}, settingsCategory={weaponCategory2}, alternateMarked={SledgeAlternateMovesetAttacks.Contains(__instance)}, running={RunningAttackInstances.Contains(__instance)}, jump={JumpAttackInstances.Contains(__instance)}, attack={DescribeAttackForSledgeDebug(__instance)}.", 0.02f); } if (character is Player) { if (weapon != null) { ApplyRangedAttackTweaksIfConfigured(__instance, character, weapon, weaponCategory2, attackRole); } ApplyMeleeStealthAndHitboxIfConfigured(__instance, character, weaponCategory2, attackRole); ApplyPlayerSizeAttackRangeIfConfigured(__instance, character, weaponCategory2); ApplyAlternateSledgeTriggerShapeIfNeeded(__instance, character, weapon, weaponCategory, attackRole); ApplyAttackMovementSpeedIfConfigured(__instance, weapon, weaponCategory2, attackRole); ApplyAttackAnimationSpeedIfConfigured(__instance, character, weapon, weaponCategory2, attackRole); ApplyAttackRotationOverrideIfConfigured(__instance, weapon, weaponCategory2, attackRole); } else { ApplyEnemyAttackHitRayIfConfigured(__instance, character); ApplyEnemyAttackAnimationSpeedIfConfigured(__instance, character); } if (character is Player && weapon != null) { CategoryConfig config = GetConfig(weaponCategory2); if (config.GetHyperArmorMode(attackRole) == HyperArmorMode.FullAttackAnimation) { float num = Mathf.Max(0.01f, config.GetFullAttackDuration(attackRole)); SetHyperArmor(character, weaponCategory2, attackRole, HyperArmorMode.FullAttackAnimation, Time.time + num, __instance); } } } } private static void ReapplyAuthoritativeAttackTweaksAfterRoleCorrection(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole authoritativeRole, bool hadPreviousRole, AttackRole previousRole) { if (attackInstance == null || (Object)(object)character == (Object)null) { return; } bool flag = hadPreviousRole && previousRole != authoritativeRole; bool flag2 = AttackMovementSpeedContexts.ContainsKey(attackInstance); bool flag3 = AttackRotationContexts.ContainsKey(attackInstance); bool flag4 = AttackRangedFieldContexts.ContainsKey(attackInstance); bool flag5 = AttackAnimationSpeedContexts.ContainsKey(attackInstance); bool flag6 = AlternateSledgeHitRayContexts.ContainsKey(attackInstance); bool flag7 = flag2 || flag3 || flag4 || flag5 || flag6; if (!flag && !flag7) { return; } RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(attackInstance); RestoreAlternateSledgeHitRayTweaks(attackInstance); RecoveryAnimationSpeedAppliedAttacks.Remove(attackInstance); AttackInstanceRoles[attackInstance] = authoritativeRole; AttackInstanceOwners[attackInstance] = character; if (weapon != null) { AttackInstanceWeapons[attackInstance] = weapon; } WeaponCategory category2 = ResolveAttackSettingsCategory(attackInstance, category, authoritativeRole); if (character is Player) { EndHyperArmorForAttack(character, attackInstance, null); if (weapon != null) { ApplyRangedAttackTweaksIfConfigured(attackInstance, character, weapon, category2, authoritativeRole); } ApplyMeleeStealthAndHitboxIfConfigured(attackInstance, character, category2, authoritativeRole); ApplyPlayerSizeAttackRangeIfConfigured(attackInstance, character, category2); ApplyAlternateSledgeTriggerShapeIfNeeded(attackInstance, character, weapon, category, authoritativeRole); ApplyAttackMovementSpeedIfConfigured(attackInstance, weapon, category2, authoritativeRole); ApplyAttackAnimationSpeedIfConfigured(attackInstance, character, weapon, category2, authoritativeRole); ApplyAttackRotationOverrideIfConfigured(attackInstance, weapon, category2, authoritativeRole); if (GetConfig(category2).GetHyperArmorMode(authoritativeRole) == HyperArmorMode.FullAttackAnimation) { float effectiveFullAttackDurationSeconds = GetEffectiveFullAttackDurationSeconds(attackInstance, character, weapon, category2, authoritativeRole); SetHyperArmor(character, category2, authoritativeRole, HyperArmorMode.FullAttackAnimation, Time.time + effectiveFullAttackDurationSeconds, attackInstance); } } else { ApplyEnemyAttackHitRayIfConfigured(attackInstance, character); ApplyEnemyAttackAnimationSpeedIfConfigured(attackInstance, character); } if (Debug(DebugAttackLifecycle) && flag) { DebugLogThrottled($"authoritative-reapply:{RuntimeHelpers.GetHashCode(attackInstance)}:{previousRole}->{authoritativeRole}", 0.05f, $"Authoritative attack correction for {SafeName(character)} {category}: provisional {previousRole} -> {authoritativeRole}; restored movement={flag2}, rotation={flag3}, ranged={flag4}, animation={flag5}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } public static void AttackUpdatePostfix(object __instance, object[] __args) { if (!ModActive || !TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return; } if (!CharacterReportsInAttack(character)) { bool privateBool = GetPrivateBool(__instance, "m_wasInAttack", fallback: false); bool privateBool2 = GetPrivateBool(__instance, "m_attackDone", fallback: false); bool privateBool3 = GetPrivateBool(__instance, "m_abortAttack", fallback: false); if (privateBool || privateBool2 || privateBool3) { EndCounterVulnerableForAttack(character, __instance); if (character is Player) { EndHyperArmorForAttack(character, __instance, HyperArmorMode.Balanced); } CleanupAttackInstance(__instance, character); } return; } WeaponCategory category = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); AttackRole role = DetermineAttackRole(character, __instance, weapon); WeaponCategory category2 = ResolveAttackSettingsCategory(__instance, category, role); SetAttackStateIfChanged(character, category2, role, __instance); Player val = (Player)(object)((character is Player) ? character : null); if (val != null && weapon != null) { TryHandleBlockCancelDuringAttack(val, __instance, weapon, category2, role); } MarkCounterVulnerableDuringAttackAnimation(character, category2, role, __instance); if ((character is Player && weapon != null) || !(character is Player)) { ReapplyAttackAnimationSpeedTweaksIfConfigured(__instance); } if (character is Player && weapon != null && GetEffectiveHyperArmorMode(__instance, category2, role) == HyperArmorMode.Balanced && !IsBalancedHyperArmorCompleted(__instance)) { float effectiveFullAttackDurationSeconds = GetEffectiveFullAttackDurationSeconds(__instance, character, weapon, category2, role); SetHyperArmorIfChanged(character, category2, role, HyperArmorMode.Balanced, Time.time + effectiveFullAttackDurationSeconds, __instance); } } public static bool AttackDoMeleeAttackPrefix(object __instance, object[] __args) { CurrentMeleeRayAttackInstance = null; CurrentMeleeRayHitObject = null; if (!ModActive) { return true; } if (!TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return true; } WeaponCategory weaponCategory = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); AttackRole attackRole = DetermineAttackRole(character, __instance, weapon); WeaponCategory category = ResolveAttackSettingsCategory(__instance, weaponCategory, attackRole); if (character is Player) { if (ShouldSuppressHitboxForBlockCancel(__instance)) { MarkLungeHitboxTriggered(__instance); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, attackRole); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, category, attackRole); return false; } MarkLungeHitboxTriggered(__instance); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, attackRole); if (weapon != null) { ApplyRecoveryAnimationSpeedIfConfigured(__instance, character, weapon, category, attackRole); } } SetAttackState(character, category, attackRole, __instance); PushAttackHitContext(__instance, __args); if (character is Player && weapon != null) { BeginMeleeHitStopContext(character, category, attackRole, __instance); if (weaponCategory == WeaponCategory.Sledge && TryGetAlternateSledgeDirectHitRaySettings(__instance, character, weapon, weaponCategory, attackRole, out var _, out var _, out var _, out var _)) { bool flag = TryRunAlternateSledgeDirectHitRay(__instance, __args, character, weapon, weaponCategory, attackRole); DebugSledgeAlternateLog($"domelee-direct:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"DoMeleeAttack intercepted for alternate sledge direct role={attackRole}; running={RunningAttackInstances.Contains(__instance)}, handled={flag}, chain={ResolvePrimaryChainRole(__instance)}.", 0.05f); if (flag) { return false; } } } CurrentMeleeRayAttackInstance = __instance; CurrentMeleeRayHitObject = null; return true; } public static IEnumerable AttackDoMeleeAttackTranspiler(IEnumerable instructions) { List list = instructions.ToList(); MethodInfo methodInfo = AccessTools.Method(typeof(GooCombatOverhaulPlugin), "ShouldContinueCurrentMeleeRay", (Type[])null, (Type[])null); int num = 0; if (methodInfo != null) { foreach (CodeInstruction item in list) { if (item.opcode == OpCodes.Ldfld && item.operand is FieldInfo { Name: "m_hitThroughWalls" } fieldInfo && fieldInfo.DeclaringType?.Name == "Attack") { item.opcode = OpCodes.Call; item.operand = methodInfo; num++; } } } if (num != 1) { Log.LogWarning((object)$"Expected to replace exactly one Attack.m_hitThroughWalls read in DoMeleeAttack, but replaced {num}. Split Hit Through Mobs / Hit Through Walls behavior may be unavailable on this Valheim build."); } return list; } public static void ProjectileFindHitObjectPostfix(GameObject __result) { if (CurrentMeleeRayAttackInstance != null) { CurrentMeleeRayHitObject = __result; } } private static bool ShouldContinueCurrentMeleeRay(object attackInstance) { bool privateBool = GetPrivateBool(attackInstance, "m_hitThroughWalls", fallback: false); if (!ModActive || attackInstance == null || CurrentMeleeRayAttackInstance != attackInstance) { return privateBool; } if (!TryGetAttackContext(attackInstance, Array.Empty(), out Character character, out ItemData weapon) || !(character is Player) || weapon?.m_shared == null) { return privateBool; } WeaponCategory category = ClassifyWeapon(weapon); if (!IsMeleeWeaponCategory(category) || !IsFeatureApplied(category, ModFeature.MultiTargetPenalty)) { return privateBool; } AttackRole role = DetermineAttackRole(character, attackInstance, weapon); GameObject currentMeleeRayHitObject = CurrentMeleeRayHitObject; bool flag = false; if ((Object)(object)currentMeleeRayHitObject != (Object)null) { try { flag = (Object)(object)currentMeleeRayHitObject.GetComponentInParent() != (Object)null; } catch { } } if (!flag) { return GetEffectiveAttackHitThroughWallsEnabled(attackInstance, category, role); } return GetEffectiveAttackHitThroughTargetsEnabled(attackInstance, category, role); } private static bool IsSpearLoyaltyEligible(ItemData? item) { if (SpearLoyaltyMode != null && SpearLoyaltyMode.Value && item?.m_shared != null && !IsAbyssalHarpoon(item)) { return ClassifyWeapon(item) == WeaponCategory.Spear; } return false; } private static void ApplySpearLoyaltyConsumeSuppressionIfNeeded(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance != null && character is Player && role == AttackRole.Secondary && category == WeaponCategory.Spear && IsSpearLoyaltyEligible(weapon) && GetFieldValueRaw(attackInstance, "m_consumeItem") is bool flag && flag) { SpearLoyaltyConsumeItemOriginals[attackInstance] = flag; SetFieldValueRaw(attackInstance, "m_consumeItem", false); } } private static void RestoreSpearLoyaltyConsumeSuppression(object attackInstance) { if (attackInstance != null && SpearLoyaltyConsumeItemOriginals.TryGetValue(attackInstance, out var value)) { SpearLoyaltyConsumeItemOriginals.Remove(attackInstance); SetFieldValueRaw(attackInstance, "m_consumeItem", value); } } private static void ApplySpearLoyaltyProjectileDropSuppression(object projectileInstance, Character owner, ItemData item) { if (projectileInstance != null && owner is Player && IsSpearLoyaltyEligible(item)) { SetFieldValueRaw(projectileInstance, "m_respawnItemOnHit", false); SetFieldValueRaw(projectileInstance, "m_spawnItem", null); } } public static bool AttackHitboxEventPrefix(object __instance, object[] __args) { if (!ModActive) { return true; } if (!TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return true; } if (!(character is Player) || weapon == null) { return true; } WeaponCategory weaponCategory = ClassifyWeapon(weapon); AttackRole attackRole = DetermineAttackRole(character, __instance, weapon); WeaponCategory weaponCategory2 = ResolveAttackSettingsCategory(__instance, weaponCategory, attackRole); ApplySpearLoyaltyConsumeSuppressionIfNeeded(__instance, character, weapon, weaponCategory, attackRole); if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon) && SledgeAlternateMovesetAttacks.Contains(__instance) && (attackRole == AttackRole.Secondary || (AttackInstanceRoles.TryGetValue(__instance, out var value) && value == AttackRole.Secondary))) { if (!TryValidateAlternateSledgeSecondaryAttack(__instance, out string failureReason)) { DebugSledgeAlternateLog($"secondary-hitbox-blocked:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", "Blocked alternate-sledge Secondary at OnAttackTrigger because it was no longer the native two-handed-axe melee-ray attack. reason=" + failureReason + "; attack=" + DescribeAttackForSledgeDebug(__instance) + ". No Area fallback was allowed.", 0.01f); return false; } attackRole = AttackRole.Secondary; weaponCategory2 = WeaponCategory.SledgeAlt; AttackInstanceRoles[__instance] = AttackRole.Secondary; DebugSledgeAlternateLog($"secondary-hitbox-validated:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", "Validated alternate-sledge Secondary immediately before native OnAttackTrigger dispatch: native two-handed-axe melee-ray payload preserved; attack=" + DescribeAttackForSledgeDebug(__instance) + ".", 0.01f); } if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon)) { DebugSledgeAlternateLog($"hitbox-dispatch-before:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"OnAttackTrigger before route selection: role={attackRole}, settingsCategory={weaponCategory2}, alternateMarked={SledgeAlternateMovesetAttacks.Contains(__instance)}, running={RunningAttackInstances.Contains(__instance)}, requestMarker={AlternateSledgeSecondaryRequests.Contains(character)}, pendingSecondary={IsPendingSecondaryAttack(character)}, attack={DescribeAttackForSledgeDebug(__instance)}.", 0.02f); } ApplyAlternateSledgeTriggerShapeIfNeeded(__instance, character, weapon, weaponCategory, attackRole); if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon)) { float height; float range; float angle; float thickness; bool flag = TryGetAlternateSledgeDirectHitRaySettings(__instance, character, weapon, weaponCategory, attackRole, out height, out range, out angle, out thickness); DebugSledgeAlternateLog($"hitbox-dispatch-after:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"OnAttackTrigger route resolved: role={attackRole}, directRoute={flag}, directShape=(height={height.ToString(CultureInfo.InvariantCulture)}, range={range.ToString(CultureInfo.InvariantCulture)}, angle={angle.ToString(CultureInfo.InvariantCulture)}, thickness={thickness.ToString(CultureInfo.InvariantCulture)}), attack={DescribeAttackForSledgeDebug(__instance)}.", 0.02f); } if (ShouldSuppressHitboxForBlockCancel(__instance)) { MarkLungeHitboxTriggered(__instance); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, weaponCategory2, attackRole); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, weaponCategory2, attackRole); return false; } MarkLungeHitboxTriggered(__instance); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, weaponCategory2, attackRole); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, weaponCategory2, attackRole); ApplyRecoveryAnimationSpeedIfConfigured(__instance, character, weapon, weaponCategory2, attackRole); return true; } public static void AttackHitboxEventPostfix(object __instance, object[] __args) { if (TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { LockEnemyRotationAfterHitRayIfConfigured(__instance, character, "OnAttackTrigger"); } RestoreSpearLoyaltyConsumeSuppression(__instance); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "OnAttackTrigger"); } public static bool AttackUpdateAttachPrefix(object __instance, object[] __args) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || __instance == null || HarpoonRopeStrengthMultiplier == null || Mathf.Approximately(HarpoonRopeStrengthMultiplier.Value, 1f)) { return true; } if (!IsHarpoonAttackInstance(__instance)) { return true; } float num = ((__args != null && __args.Length != 0 && __args[0] is float num2) ? num2 : Time.deltaTime); object? fieldValueRaw = GetFieldValueRaw(__instance, "m_attachTarget"); Transform val = (Transform)((fieldValueRaw is Transform) ? fieldValueRaw : null); if (val == null) { return true; } object? fieldValueRaw2 = GetFieldValueRaw(__instance, "m_character"); Character val2 = (Character)((fieldValueRaw2 is Character) ? fieldValueRaw2 : null); if (val2 == null) { return true; } Character component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { if (component.IsDead()) { InvokeAttackStop(__instance); return false; } float num3 = GetPrivateFloat(__instance, "m_detachTimer", 0f) + num; if (num3 > 0.3f) { num3 = 0f; if (component.IsDodgeInvincible()) { SetPrivateFloat(__instance, "m_detachTimer", num3); InvokeAttackStop(__instance); return false; } } SetPrivateFloat(__instance, "m_detachTimer", num3); } Vector3 val3 = ((GetFieldValueRaw(__instance, "m_attachOffset") is Vector3 val4) ? val4 : Vector3.zero); Vector3 val5 = ((GetFieldValueRaw(__instance, "m_attachHitPoint") is Vector3 val6) ? val6 : Vector3.zero); Vector3 val7 = val.TransformPoint(val3); Vector3 val8 = val.TransformPoint(val5); float num4 = Mathf.Clamp01(0.1f * Mathf.Clamp(HarpoonRopeStrengthMultiplier.Value, 0f, 20f)); Vector3 val9 = Vector3.Lerp(((Component)val2).transform.position, val7, num4); Vector3 val10 = val8 - val9; ((Vector3)(ref val10)).Normalize(); if (((Vector3)(ref val10)).sqrMagnitude > 0.0001f) { ((Component)val2).transform.rotation = Quaternion.LookRotation(val10); } ((Component)val2).transform.position = val8 - val10 * val2.GetRadius(); Rigidbody component2 = ((Component)val2).GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.linearVelocity = Vector3.zero; } return false; } private static bool IsHarpoonAttackInstance(object attackInstance) { if (attackInstance == null) { return false; } if (AttackInstanceWeapons.TryGetValue(attackInstance, out ItemData value) && IsAbyssalHarpoon(value)) { return true; } object? fieldValueRaw = GetFieldValueRaw(attackInstance, "m_weapon"); ItemData val = (ItemData)((fieldValueRaw is ItemData) ? fieldValueRaw : null); if (val != null) { return IsAbyssalHarpoon(val); } return false; } private static void InvokeAttackStop(object attackInstance) { try { GetCachedMethod(attackInstance.GetType(), "Stop")?.Invoke(attackInstance, Array.Empty()); } catch { } } private static Vector3 GetCharacterAimDirection(Character character, Vector3 originPosition) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((character is Player) ? character : null); if (val != null) { try { Vector3 lookDir = ((Character)val).GetLookDir(); if (((Vector3)(ref lookDir)).sqrMagnitude > 0.0001f) { return ((Vector3)(ref lookDir)).normalized; } } catch { } } try { Vector3 forward = ((Component)character).transform.forward; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f) { return ((Vector3)(ref forward)).normalized; } } catch { } return Vector3.forward; } private static string SafeObjectName(GameObject? go) { if ((Object)(object)go == (Object)null) { return ""; } try { return string.IsNullOrWhiteSpace(((Object)go).name) ? "" : ((Object)go).name; } catch { return ""; } } private static bool DebugSledgeAlternate() { if (!Debug(DebugSledgeAlternateMoveset)) { if (VerboseDebugLogging != null && VerboseDebugLogging.Value && DebugLogging != null) { return DebugLogging.Value; } return false; } return true; } private static void DebugSledgeAlternateLog(string key, string message, float throttle = 0.1f) { if (DebugSledgeAlternate()) { DebugLogThrottled("sledge-alt:" + key, throttle, "[GCO SledgeAlt] " + message); } } private static bool IsGroundTerrainMeleeHit(GameObject go, Collider? collider, Vector3 hitNormal) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)collider != (Object)null) ? ((Component)collider).gameObject : null); bool flag = ((Object)(object)go != (Object)null && (Object)(object)go.GetComponentInParent() != (Object)null) || ((Object)(object)val != (Object)null && (Object)(object)val.GetComponentInParent() != (Object)null); if (!flag) { int num = (((Object)(object)val != (Object)null) ? val.layer : (((Object)(object)go != (Object)null) ? go.layer : (-1))); flag = ((num >= 0) ? LayerMask.LayerToName(num) : string.Empty).Equals("terrain", StringComparison.OrdinalIgnoreCase); } if (flag) { return hitNormal.y >= 0.5f; } return false; } private static bool IsWallLikeMeleeHit(GameObject go, Collider? collider = null) { if ((Object)(object)go == (Object)null && (Object)(object)collider == (Object)null) { return true; } GameObject val = (((Object)(object)collider != (Object)null) ? ((Component)collider).gameObject : null); if (((Object)(object)go != (Object)null && (Object)(object)go.GetComponentInParent() != (Object)null) || ((Object)(object)val != (Object)null && (Object)(object)val.GetComponentInParent() != (Object)null)) { return false; } if (((Object)(object)go != (Object)null && ((Object)(object)go.GetComponentInParent() != (Object)null || (Object)(object)go.GetComponentInParent() != (Object)null || (Object)(object)go.GetComponentInParent() != (Object)null)) || ((Object)(object)val != (Object)null && ((Object)(object)val.GetComponentInParent() != (Object)null || (Object)(object)val.GetComponentInParent() != (Object)null || (Object)(object)val.GetComponentInParent() != (Object)null))) { return true; } int num = (((Object)(object)val != (Object)null) ? val.layer : (((Object)(object)go != (Object)null) ? go.layer : (-1))); string text = ((num >= 0) ? LayerMask.LayerToName(num) : string.Empty); if (text.Equals("terrain", StringComparison.OrdinalIgnoreCase) || text.Equals("piece", StringComparison.OrdinalIgnoreCase) || text.Equals("piece_nonsolid", StringComparison.OrdinalIgnoreCase) || text.Equals("static_solid", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private static bool TryRunAlternateSledgeDirectHitRay(object attackInstance, object[] args, Character character, ItemData weapon, WeaponCategory category, AttackRole role) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_07fd: Unknown result type (might be due to invalid IL or missing references) //IL_0802: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0670: Unknown result type (might be due to invalid IL or missing references) //IL_0677: Expected O, but got Unknown //IL_068d: Unknown result type (might be due to invalid IL or missing references) //IL_070e: Unknown result type (might be due to invalid IL or missing references) //IL_0710: Unknown result type (might be due to invalid IL or missing references) //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_0726: Unknown result type (might be due to invalid IL or missing references) //IL_072f: Unknown result type (might be due to invalid IL or missing references) //IL_0734: Unknown result type (might be due to invalid IL or missing references) //IL_073d: Unknown result type (might be due to invalid IL or missing references) //IL_0742: Unknown result type (might be due to invalid IL or missing references) //IL_0744: Unknown result type (might be due to invalid IL or missing references) //IL_0749: Unknown result type (might be due to invalid IL or missing references) //IL_0833: Unknown result type (might be due to invalid IL or missing references) //IL_0837: Unknown result type (might be due to invalid IL or missing references) //IL_083c: Unknown result type (might be due to invalid IL or missing references) //IL_0841: Unknown result type (might be due to invalid IL or missing references) //IL_092e: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_0768: Unknown result type (might be due to invalid IL or missing references) //IL_076d: Unknown result type (might be due to invalid IL or missing references) //IL_076f: Unknown result type (might be due to invalid IL or missing references) //IL_0774: Unknown result type (might be due to invalid IL or missing references) //IL_0778: Unknown result type (might be due to invalid IL or missing references) //IL_075f: Unknown result type (might be due to invalid IL or missing references) //IL_0574: Unknown result type (might be due to invalid IL or missing references) //IL_0579: Unknown result type (might be due to invalid IL or missing references) //IL_0584: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_077d: Unknown result type (might be due to invalid IL or missing references) //IL_08cf: Unknown result type (might be due to invalid IL or missing references) //IL_08d4: Unknown result type (might be due to invalid IL or missing references) //IL_08d9: Unknown result type (might be due to invalid IL or missing references) //IL_063d: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_07a6: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_07d9: Unknown result type (might be due to invalid IL or missing references) //IL_07de: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) if (!TryGetAlternateSledgeDirectHitRaySettings(attackInstance, character, weapon, category, role, out var height, out var range, out var angle, out var thickness)) { return false; } Transform alternateSledgeAttackOrigin = GetAlternateSledgeAttackOrigin(attackInstance, character); Vector3 forward = ((Component)character).transform.forward; Vector3 val = forward; try { val = GetCharacterAimDirection(character, alternateSledgeAttackOrigin.position); val.x = forward.x; val.z = forward.z; ((Vector3)(ref val)).Normalize(); } catch { val = forward; } if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = forward; } Vector3 val2 = Vector3.RotateTowards(forward, val, (float)Math.PI / 3f, 10f); Vector3 val3 = ((Component)character).transform.InverseTransformDirection(val2); float num = Mathf.Clamp(GetEffectiveAttackOffsetMultiplier(attackInstance, category, role), 0f, 20f); float privateFloat = GetPrivateFloat(attackInstance, "m_attackOffset", 0f); Vector3 val4 = alternateSledgeAttackOrigin.position + Vector3.up * height + ((Component)character).transform.right * (privateFloat * num); float num2 = Mathf.Max(0f, angle) * 0.5f; float num3 = 4f; int alternateSledgeAttackMask = GetAlternateSledgeAttackMask(attackInstance); bool flag = IsFeatureApplied(WeaponCategory.Sledge, ModFeature.MultiTargetPenalty); AttackMultiTargetDamagePenaltyMode attackMultiTargetDamagePenaltyMode = ((!flag) ? AttackMultiTargetDamagePenaltyMode.Disabled : GetEffectiveMultiTargetDamagePenaltyMode(attackInstance, category, role)); bool privateBool = GetPrivateBool(attackInstance, "m_hitThroughWalls", fallback: false); bool flag2 = (flag ? GetEffectiveAttackHitThroughTargetsEnabled(attackInstance, category, role) : privateBool); bool flag3 = (flag ? GetEffectiveAttackHitThroughWallsEnabled(attackInstance, category, role) : privateBool); bool flag4 = (flag ? GetEffectiveAttackMultiHitEnabled(attackInstance, category, role) : GetPrivateBool(attackInstance, "m_multiHit", fallback: true)); List list = new List(); HashSet hashSet = new HashSet(); for (float num4 = 0f - num2; num4 <= num2; num4 += num3) { Quaternion val5 = Quaternion.Euler(0f, 0f - num4, 0f); Vector3 val6 = ((Component)character).transform.TransformDirection(val5 * val3); if (((Vector3)(ref val6)).sqrMagnitude < 0.0001f) { continue; } ((Vector3)(ref val6)).Normalize(); float num5 = Mathf.Max(0f, range - Mathf.Max(0f, thickness)); RaycastHit[] array; try { array = ((thickness > 0f) ? Physics.SphereCastAll(val4, thickness, val6, num5, alternateSledgeAttackMask, (QueryTriggerInteraction)1) : Physics.RaycastAll(val4, val6, range, alternateSledgeAttackMask, (QueryTriggerInteraction)1)); } catch { continue; } Array.Sort(array, (RaycastHit x, RaycastHit y) => ((RaycastHit)(ref x)).distance.CompareTo(((RaycastHit)(ref y)).distance)); RaycastHit[] array2 = array; for (int num6 = 0; num6 < array2.Length; num6++) { RaycastHit val7 = array2[num6]; if ((Object)(object)((RaycastHit)(ref val7)).collider == (Object)null || (Object)(object)((Component)((RaycastHit)(ref val7)).collider).gameObject == (Object)(object)((Component)character).gameObject) { continue; } GameObject val8; try { val8 = Projectile.FindHitObject(((RaycastHit)(ref val7)).collider); } catch { val8 = ((Component)((RaycastHit)(ref val7)).collider).gameObject; } if ((Object)(object)val8 == (Object)null || (Object)(object)val8 == (Object)(object)((Component)character).gameObject) { continue; } bool flag5 = !IsGroundTerrainMeleeHit(val8, ((RaycastHit)(ref val7)).collider, ((RaycastHit)(ref val7)).normal) && IsWallLikeMeleeHit(val8, ((RaycastHit)(ref val7)).collider); bool flag6 = IsValidAlternateSledgeDirectHitTarget(character, weapon, val8); if (flag6 && !hashSet.Contains(val8)) { Vector3 val9 = ((RaycastHit)(ref val7)).point; if (val9 == Vector3.zero) { try { val9 = ((RaycastHit)(ref val7)).collider.ClosestPoint(val4); } catch { val9 = val4 + val6 * Mathf.Min(range, Mathf.Max(0f, ((RaycastHit)(ref val7)).distance)); } } list.Add(new DirectSledgeHitPoint { Go = val8, Collider = ((RaycastHit)(ref val7)).collider, Point = val9, Distance = ((RaycastHit)(ref val7)).distance }); hashSet.Add(val8); } if (flag5) { DebugSledgeAlternateLog($"ray-blocker:{RuntimeHelpers.GetHashCode(attackInstance)}:{RuntimeHelpers.GetHashCode(((RaycastHit)(ref val7)).collider)}", $"Ray hit terrain/building blocker '{SafeObjectName(val8)}' at distance={((RaycastHit)(ref val7)).distance.ToString(CultureInfo.InvariantCulture)}; hitThroughWalls={flag3}. validTarget={flag6}.", 0.05f); if (!flag3) { break; } } else if (flag6 && !flag2) { break; } } } DebugSledgeAlternateLog($"direct-cast-result:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Direct sledge hit-ray cast complete: role={role}, running={RunningAttackInstances.Contains(attackInstance)}, height={height.ToString(CultureInfo.InvariantCulture)}, range={range.ToString(CultureInfo.InvariantCulture)}, angle={angle.ToString(CultureInfo.InvariantCulture)}, thickness={thickness.ToString(CultureInfo.InvariantCulture)}, hitRayFeature={IsFeatureApplied(WeaponCategory.Sledge, ModFeature.HitRay)}, multiTargetFeature={flag}, multiTargetPenalty={attackMultiTargetDamagePenaltyMode}, hitThroughTargets={flag2}, hitThroughWalls={flag3}, multiHit={flag4}, candidateHitPoints={list.Count}.", 0.05f); if (list.Count == 0) { try { Player val10 = (Player)(object)((character is Player) ? character : null); if (val10 != null) { character.AddAdrenaline(val10.m_attackMissAdrenaline); } } catch { } return true; } int num7 = 0; Vector3 val11 = Vector3.zero; Character val12 = null; SkillType skillType = weapon.m_shared.m_skillType; float privateFloat2 = GetPrivateFloat(attackInstance, "m_raiseSkillAmount", 1f); float privateFloat3 = GetPrivateFloat(attackInstance, "m_attackHitNoise", 0f); float privateFloat4 = GetPrivateFloat(attackInstance, "m_attackHealthReturnHit", 0f); float privateFloat5 = GetPrivateFloat(attackInstance, "m_forceMultiplier", 1f); float privateFloat6 = GetPrivateFloat(attackInstance, "m_staggerMultiplier", 1f); foreach (DirectSledgeHitPoint item in list.OrderBy((DirectSledgeHitPoint h) => h.Distance)) { IDestructible component = item.Go.GetComponent(); if (component == null) { continue; } float num8 = 1f; try { num8 = character.GetRandomSkillFactor(skillType); } catch { } if (flag4 && attackMultiTargetDamagePenaltyMode == AttackMultiTargetDamagePenaltyMode.Enabled && list.Count > 1) { num8 /= (float)list.Count * 0.75f; } HitData val13 = new HitData(); val13.m_toolTier = (short)weapon.m_shared.m_toolTier; val13.m_skillLevel = character.GetSkillLevel(skillType); val13.m_itemLevel = (short)weapon.m_quality; val13.m_itemWorldLevel = (byte)weapon.m_worldLevel; val13.m_pushForce = weapon.m_shared.m_attackForce * num8 * privateFloat5; val13.m_backstabBonus = weapon.m_shared.m_backstabBonus; val13.m_staggerMultiplier = privateFloat6; val13.m_dodgeable = weapon.m_shared.m_dodgeable; val13.m_blockable = weapon.m_shared.m_blockable; val13.m_skill = skillType; val13.m_skillRaiseAmount = privateFloat2; val13.m_damage = weapon.GetDamage(); val13.m_point = item.Point; Vector3 val14 = item.Point - val4; Vector3 dir; if (!(((Vector3)(ref val14)).sqrMagnitude > 0.0001f)) { dir = ((Component)character).transform.forward; } else { val14 = item.Point - val4; dir = ((Vector3)(ref val14)).normalized; } val13.m_dir = dir; val13.m_hitCollider = item.Collider; val13.SetAttacker(character); val13.m_hitType = (HitType)((!(character is Player)) ? 1 : 2); val13.m_healthReturn = privateFloat4; TryInvokeAttackModifyDamage(attackInstance, val13, num8); TryInvokeAttackSpawnOnHit(attackInstance, item.Go); try { weapon.m_shared.m_hitEffect.Create(item.Point, Quaternion.identity, (Transform)null, 1f, -1); } catch { } TryCreateAttackEffectList(attackInstance, "m_hitEffect", item.Point, Quaternion.identity, null); Character val15 = (Character)(object)((component is Character) ? component : null); if ((Object)(object)val15 != (Object)null) { val12 = val15; } component.Damage(val13); num7++; val11 += item.Point; if (privateFloat4 > 0f && (Object)(object)val15 != (Object)null) { try { character.Heal(privateFloat4, true); } catch { } } if (!flag4) { break; } } DebugSledgeAlternateLog($"direct-damage-result:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Direct sledge hit-ray damage complete: candidateHitPoints={list.Count}, damaged={num7}, multiHit={flag4}.", 0.05f); if (num7 > 0) { val11 /= (float)num7; try { if (weapon.m_shared.m_useDurability && character.IsPlayer()) { weapon.m_durability -= weapon.m_shared.m_useDurabilityDrain; } } catch { } try { character.AddNoise(privateFloat3); } catch { } try { character.FreezeFrame(0.15f); } catch { } try { character.RaiseSkill(skillType, privateFloat2 * (((Object)(object)val12 != (Object)null) ? 1.5f : 1f)); } catch { } try { if ((Object)(object)val12 != (Object)null) { character.AddAdrenaline(GetPrivateFloat(attackInstance, "m_attackAdrenaline", 0f) * val12.m_enemyAdrenalineMultiplier); } } catch { } } return true; } private static Transform GetAlternateSledgeAttackOrigin(object attackInstance, Character character) { try { object? obj = GetCachedMethod(attackInstance.GetType(), "GetAttackOrigin")?.Invoke(attackInstance, Array.Empty()); Transform val = (Transform)((obj is Transform) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { return val; } } catch { } return ((Component)character).transform; } private static int GetAlternateSledgeAttackMask(object attackInstance) { try { bool privateBool = GetPrivateBool(attackInstance, "m_hitTerrain", fallback: false); FieldInfo cachedField = GetCachedField(attackInstance.GetType(), privateBool ? "m_attackMaskTerrain" : "m_attackMask"); if (cachedField != null && cachedField.IsStatic) { object value = cachedField.GetValue(null); if (value is int) { return (int)value; } } } catch { } return -1; } private static bool IsValidAlternateSledgeDirectHitTarget(Character attacker, ItemData weapon, GameObject go) { if ((Object)(object)attacker == (Object)null || weapon?.m_shared == null || (Object)(object)go == (Object)null) { return false; } Character component = go.GetComponent(); if ((Object)(object)component != (Object)null) { if (component == attacker) { return false; } bool flag = false; try { flag = BaseAI.IsEnemy(attacker, component); } catch { } try { flag |= (Object)(object)component.GetBaseAI() != (Object)null && component.GetBaseAI().IsAggravatable() && attacker.IsPlayer(); } catch { } bool flag2 = attacker is Player && component is Player && attacker.IsPVPEnabled() && component.IsPVPEnabled(); if (!flag && !flag2) { return false; } if (weapon.m_shared.m_dodgeable && component.IsDodgeInvincible()) { Player val = (Player)(object)((component is Player) ? component : null); if (val != null) { val.HitWhileDodging(); } return false; } if (weapon.m_shared.m_tamedOnly && !component.IsTamed()) { return false; } return true; } if (weapon.m_shared.m_tamedOnly) { return false; } return go.GetComponent() != null; } private static bool TryInvokeAttackModifyDamage(object attackInstance, HitData hitData, float skillFactor) { try { MethodInfo cachedMethod = GetCachedMethod(attackInstance.GetType(), "ModifyDamage", new Type[2] { typeof(HitData), typeof(float) }); if (cachedMethod != null) { cachedMethod.Invoke(attackInstance, new object[2] { hitData, skillFactor }); return true; } } catch { } try { ((DamageTypes)(ref hitData.m_damage)).Modify(skillFactor); } catch { } return false; } private static void TryInvokeAttackSpawnOnHit(object attackInstance, GameObject target) { try { GetCachedMethod(attackInstance.GetType(), "SpawnOnHit", new Type[1] { typeof(GameObject) })?.Invoke(attackInstance, new object[1] { target }); } catch { } } private static void TryCreateAttackEffectList(object attackInstance, string fieldName, Vector3 point, Quaternion rotation, Transform? parent) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) object fieldValueRaw = GetFieldValueRaw(attackInstance, fieldName); if (fieldValueRaw == null) { return; } try { GetCachedMethod(fieldValueRaw.GetType(), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) })?.Invoke(fieldValueRaw, new object[5] { point, rotation, parent, 1f, -1 }); } catch { } } private static bool TryInvokeAttackDoMeleeAttack(object attackInstance) { if (attackInstance == null) { return false; } try { MethodInfo cachedMethod = GetCachedMethod(attackInstance.GetType(), "DoMeleeAttack"); if (cachedMethod == null) { return false; } cachedMethod.Invoke(attackInstance, Array.Empty()); return true; } catch { return false; } } public static void AttackDoMeleeAttackPostfix(object __instance, object[] __args) { if (CurrentMeleeRayAttackInstance == __instance) { CurrentMeleeRayAttackInstance = null; CurrentMeleeRayHitObject = null; } if (TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { LockEnemyRotationAfterHitRayIfConfigured(__instance, character, "DoMeleeAttack"); } PopAttackHitContext(); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "DoMeleeAttack"); EndMeleeHitStopContext(__instance, __args); } public static bool AttackDoAreaAttackPrefix(object __instance, object[] __args, ref SledgeAreaAttackScaleState? __state) { __state = null; if (ModActive && TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon) && weapon != null) { WeaponCategory weaponCategory = ClassifyWeapon(weapon); AttackRole attackRole = DetermineAttackRole(character, __instance, weapon); if (weaponCategory == WeaponCategory.Sledge && IsSledgeAlternateMovesetActiveForWeapon(weapon) && SledgeAlternateMovesetAttacks.Contains(__instance) && (attackRole == AttackRole.Secondary || (AttackInstanceRoles.TryGetValue(__instance, out var value) && value == AttackRole.Secondary))) { string failureReason; bool flag = TryValidateAlternateSledgeSecondaryAttack(__instance, out failureReason); bool flag2 = flag && TryInvokeAttackDoMeleeAttack(__instance); DebugSledgeAlternateLog($"secondary-doarea-redirect:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"Suppressed an unexpected native DoAreaAttack dispatch for alternate-sledge Secondary and redirected to native DoMeleeAttack. valid={flag}, invoked={flag2}, failure='{failureReason}', attack={DescribeAttackForSledgeDebug(__instance)}.", 0.01f); return false; } if (TryGetAlternateSledgeDirectHitRaySettings(__instance, character, weapon, weaponCategory, attackRole, out var _, out var _, out var _, out var _)) { DebugSledgeAlternateLog($"doarea-direct:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"DoAreaAttack fallback reached for alternate sledge direct role={attackRole}; suppressing AOE trigger/effects and invoking the dedicated normal hit-ray route. running={RunningAttackInstances.Contains(__instance)}, chain={ResolvePrimaryChainRole(__instance)}, attack={DescribeAttackForSledgeDebug(__instance)}.", 0.05f); ApplyAlternateSledgeTriggerShapeIfNeeded(__instance, character, weapon, weaponCategory, attackRole); PushAttackHitContext(__instance, __args); if (character is Player) { BeginMeleeHitStopContext(character, ResolveAttackSettingsCategory(__instance, weaponCategory, attackRole), attackRole, __instance); } try { bool flag3 = TryRunAlternateSledgeDirectHitRay(__instance, __args, character, weapon, weaponCategory, attackRole); DebugSledgeAlternateLog($"doarea-direct-result:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", $"DoAreaAttack fallback used dedicated direct hit ray; role={attackRole}, running={RunningAttackInstances.Contains(__instance)}, handled={flag3}, chain={ResolvePrimaryChainRole(__instance)}.", 0.05f); } finally { PopAttackHitContext(); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "AlternateSledgeFallbackDirectHitRay"); EndMeleeHitStopContext(__instance, __args); } return false; } if (weaponCategory == WeaponCategory.Sledge) { float num = ((SledgeSlamAoeRadiusMultiplier != null) ? Mathf.Clamp(SledgeSlamAoeRadiusMultiplier.Value, 0f, 20f) : 1f); float privateFloat = GetPrivateFloat(__instance, "m_attackRayWidth", 0f); float privateFloat2 = GetPrivateFloat(__instance, "m_attackRayWidthCharExtra", 0f); if (!Mathf.Approximately(num, 1f)) { SetFieldValueRaw(__instance, "m_attackRayWidth", Mathf.Max(0f, privateFloat * num)); SetFieldValueRaw(__instance, "m_attackRayWidthCharExtra", Mathf.Max(0f, privateFloat2 * num)); __state = new SledgeAreaAttackScaleState(__instance, privateFloat, privateFloat2); } DebugSledgeAlternateLog($"doarea-native:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(__instance)}", string.Format("Native sledge Area route selected: role={0}, radiusMultiplier={1}, baseRadius={2}->{3}, characterExtraRadius={4}->{5}, alternateMarked={6}, chain={7}, attack={8}.", attackRole, num.ToString(CultureInfo.InvariantCulture), privateFloat.ToString(CultureInfo.InvariantCulture), GetPrivateFloat(__instance, "m_attackRayWidth", privateFloat).ToString(CultureInfo.InvariantCulture), privateFloat2.ToString(CultureInfo.InvariantCulture), GetPrivateFloat(__instance, "m_attackRayWidthCharExtra", privateFloat2).ToString(CultureInfo.InvariantCulture), SledgeAlternateMovesetAttacks.Contains(__instance), ResolvePrimaryChainRole(__instance), DescribeAttackForSledgeDebug(__instance)), 0.05f); } } PushAttackHitContext(__instance, __args); if (ModActive) { PushIncomingParryStaggerSource(IncomingParryStaggerSource.Aoe); } return true; } public static void AttackDoAreaAttackPostfix(object __instance, object[] __args, SledgeAreaAttackScaleState? __state) { try { if (TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { LockEnemyRotationAfterHitRayIfConfigured(__instance, character, "DoAreaAttack"); } PopAttackHitContext(); PopIncomingParryStaggerSource(); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "DoAreaAttack"); } finally { __state?.Restore(); } } public static Exception? AttackDoAreaAttackFinalizer(Exception? __exception, SledgeAreaAttackScaleState? __state) { __state?.Restore(); return __exception; } public static void AttackFireProjectileBurstPrefix(object __instance, object[] __args) { PushAttackHitContext(__instance, __args); } public static void AttackFireProjectileBurstPostfix(object __instance, object[] __args) { PopAttackHitContext(); } private static void PushAttackHitContext(object __instance, object[] __args) { if (ModActive && TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { WeaponCategory category = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); AttackRole role = DetermineAttackRole(character, __instance, weapon); category = ResolveAttackSettingsCategory(__instance, category, role); bool attackStartedAirborne = GetAttackStartedAirborne(character, __instance); AttackRuntimeState item = new AttackRuntimeState(category, role, Time.time + 5f, __instance, attackStartedAirborne); if (CurrentAttackHitContexts == null) { CurrentAttackHitContexts = new Stack(); } CurrentAttackHitContexts.Push(item); } } private static void PopAttackHitContext() { if (CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count != 0) { CurrentAttackHitContexts.Pop(); } } private static void PushIncomingParryStaggerSource(IncomingParryStaggerSource source) { if (CurrentIncomingParryStaggerSources == null) { CurrentIncomingParryStaggerSources = new Stack(); } CurrentIncomingParryStaggerSources.Push(source); } private static void PopIncomingParryStaggerSource() { if (CurrentIncomingParryStaggerSources != null && CurrentIncomingParryStaggerSources.Count != 0) { CurrentIncomingParryStaggerSources.Pop(); } } private static IncomingParryStaggerSource GetCurrentIncomingParryStaggerSource() { if (CurrentIncomingParryStaggerSources == null || CurrentIncomingParryStaggerSources.Count == 0) { return IncomingParryStaggerSource.None; } return CurrentIncomingParryStaggerSources.Peek(); } private static bool TryGetCurrentHitAttackContext(Character? attacker, WeaponCategory category, out AttackRuntimeState context) { context = default(AttackRuntimeState); if (CurrentAttackHitContexts == null || CurrentAttackHitContexts.Count == 0) { return false; } context = CurrentAttackHitContexts.Peek(); if (!SamePublicWeaponCategory(context.Category, category)) { return false; } if (context.EndTime < Time.time) { return false; } if ((Object)(object)attacker != (Object)null && context.AttackInstance != null && AttackInstanceOwners.TryGetValue(context.AttackInstance, out Character value) && value != attacker) { return false; } return true; } private static void EndBalancedHyperArmorAfterAttackEvent(object __instance, object[] __args, string source) { if (ModActive && TryGetAttackContext(__instance, __args, out Character character, out ItemData _) && character is Player) { MarkBalancedHyperArmorCompleted(__instance); EndHyperArmorForAttack(character, __instance, HyperArmorMode.Balanced); } } public static void AttackStopPostfix(object __instance, object[] __args) { if (ModActive && TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { CleanupAttackInstance(__instance, character); } } private static void CleanupAttackInstance(object? attackInstance, Character? character) { if (attackInstance == null) { return; } if ((Object)(object)character == (Object)null && AttackInstanceOwners.TryGetValue(attackInstance, out Character value)) { character = value; } if ((Object)(object)character != (Object)null) { EndCounterVulnerableForAttack(character, attackInstance); EndAttackStateForAttack(character, attackInstance); EndMeleeHitStopContextForCharacter(character, attackInstance); PendingAttackRoles.Remove(character); if (character is Player) { EndHyperArmorForAttack(character, attackInstance, null); } } Player val = (Player)(object)((character is Player) ? character : null); if (val != null && DebugSledgeAlternate() && SledgeAlternateMovesetAttacks.Contains(attackInstance)) { DebugSledgeAlternateLog($"cleanup:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", string.Format("Cleaning alternate-sledge attack: inAttack={0}, running={1}, role={2}, hadMovementFieldMutation={3}. Retained Humanoid.m_currentAttack will be ignored once native InAttack() becomes false.", CharacterReportsInAttack((Character)(object)val), RunningAttackInstances.Contains(attackInstance), AttackInstanceRoles.TryGetValue(attackInstance, out var value2) ? value2.ToString() : "", AttackMovementSpeedContexts.ContainsKey(attackInstance))); } RunningAttackInstances.Remove(attackInstance); RunningAttackForcedChainLevels.Remove(attackInstance); JumpAttackInstances.Remove(attackInstance); JumpAttackForcedChainLevels.Remove(attackInstance); BetterFreeAimPreAppliedAttacks.Remove(attackInstance); LungeHitboxTriggeredAttacks.Remove(attackInstance); RecoveryAnimationSpeedAppliedAttacks.Remove(attackInstance); BlockCanceledAttacks.Remove(attackInstance); BlockCancelRequestedAttacks.Remove(attackInstance); BlockCancelAnimationSpeedByAttack.Remove(attackInstance); EnemyRotationLockedAfterHitRayAttacks.Remove(attackInstance); AttackInstanceOwners.Remove(attackInstance); AttackInstanceWeapons.Remove(attackInstance); AttackInstanceRoles.Remove(attackInstance); SledgeAlternateMovesetAttacks.Remove(attackInstance); RestoreAlternateSledgeHitRayTweaks(attackInstance); AttackInstanceStartedAirborne.Remove(attackInstance); AttackInstanceStartedWhileAlreadyInAttack.Remove(attackInstance); ClearBalancedHyperArmorCompleted(attackInstance); RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(attackInstance); RestoreBetterLightningAttackSpawnTweaks(attackInstance); } private static void ResetAttackInstanceForFreshStart(object? attackInstance) { if (attackInstance != null) { RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(attackInstance); RestoreBetterLightningAttackSpawnTweaks(attackInstance); RestoreAlternateSledgeHitRayTweaks(attackInstance); RunningAttackInstances.Remove(attackInstance); RunningAttackForcedChainLevels.Remove(attackInstance); JumpAttackInstances.Remove(attackInstance); JumpAttackForcedChainLevels.Remove(attackInstance); BetterFreeAimPreAppliedAttacks.Remove(attackInstance); LungeHitboxTriggeredAttacks.Remove(attackInstance); RecoveryAnimationSpeedAppliedAttacks.Remove(attackInstance); BlockCanceledAttacks.Remove(attackInstance); BlockCancelRequestedAttacks.Remove(attackInstance); BlockCancelAnimationSpeedByAttack.Remove(attackInstance); EnemyRotationLockedAfterHitRayAttacks.Remove(attackInstance); AttackInstanceOwners.Remove(attackInstance); AttackInstanceWeapons.Remove(attackInstance); AttackInstanceRoles.Remove(attackInstance); SledgeAlternateMovesetAttacks.Remove(attackInstance); AttackInstanceStartedAirborne.Remove(attackInstance); AttackInstanceStartedWhileAlreadyInAttack.Remove(attackInstance); ClearBalancedHyperArmorCompleted(attackInstance); ProjectileAttackContexts.Remove(attackInstance); AoeAttackContexts.Remove(attackInstance); } } private static void CleanupPreviousAttackContextsForCharacter(Character? character, object? keepAttackInstance) { if ((Object)(object)character == (Object)null) { return; } PendingAttackRoleState value; bool flag = PendingAttackRoles.TryGetValue(character, out value); foreach (object item in (from kvp in AttackInstanceOwners where kvp.Value == character && (keepAttackInstance == null || kvp.Key != keepAttackInstance) select kvp.Key).ToList()) { CleanupAttackInstance(item, character); } if (AttackStates.TryGetValue(character, out var value2) && value2.AttackInstance != null && keepAttackInstance != null && value2.AttackInstance != keepAttackInstance) { AttackStates.Remove(character); } if (MeleeHitStopContexts.TryGetValue(character, out var value3) && value3.AttackInstance != null && keepAttackInstance != null && value3.AttackInstance != keepAttackInstance) { MeleeHitStopContexts.Remove(character); } if (flag && Time.time <= value.EndTime) { PendingAttackRoles[character] = value; } } private static void ProcessGooDebugKeybinds() { if (IsGuestDebugAuthorityLimited() || !IsGooCombatDebugModeActive()) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !GcoPlayerCanTakeGameplayInput(localPlayer)) { return; } try { if (IsConfiguredKeyDown(DebugModeHealKeybind, "heal")) { HealDebugPlayerResources(localPlayer); BroadcastGcoChatMessage("Healed "); } } catch { } try { if (IsConfiguredKeyDown(DebugModeAddRestedKeybind, "addrested")) { AddRestedDebugEffect(localPlayer); BroadcastGcoChatMessage("Added Rested "); } } catch { } try { if (IsDebugNoCostToggleAllowed() && IsConfiguredKeyDown(ToggleNoPlacementCostKeybind, "nocost")) { _gcoNoPlacementCostDesired = !_gcoNoPlacementCostDesired; ApplyGooDebugDesiredPlayerToggles(localPlayer); BroadcastGcoChatMessage((_gcoNoPlacementCostDesired ? "Enabled" : "Disabled") + " NoPlacementCost "); } } catch { } try { if (IsDebugFlyToggleAllowed() && IsConfiguredKeyDown(ToggleDebugFlyKeybind, "fly")) { _gcoDebugFlyDesired = !_gcoDebugFlyDesired; ApplyGooDebugDesiredPlayerToggles(localPlayer); BroadcastGcoChatMessage((_gcoDebugFlyDesired ? "Enabled" : "Disabled") + " DebugFly "); } } catch { } try { if (IsDebugGhostToggleAllowed() && IsConfiguredKeyDown(ToggleGhostModeKeybind, "ghost")) { _gcoGhostModeDesired = !_gcoGhostModeDesired; ApplyGooDebugDesiredPlayerToggles(localPlayer); BroadcastGcoChatMessage((_gcoGhostModeDesired ? "Enabled" : "Disabled") + " GhostMode "); } } catch { } } private static bool IsConfiguredKeyDown(ConfigEntry? entry, string debounceKey) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) try { if (entry == null) { return false; } KeyCode value = entry.Value; if ((int)value == 0) { return false; } if (DebugShortcutDebounceUntil.TryGetValue(debounceKey, out var value2) && Time.unscaledTime < value2) { return false; } if (!TryZInputGetKeyDown(value)) { return false; } DebugShortcutDebounceUntil[debounceKey] = Time.unscaledTime + 0.08f; return true; } catch { return false; } } private static bool GcoPlayerCanTakeGameplayInput(Player player) { if ((Object)(object)player == (Object)null) { return false; } try { MethodInfo methodInfo = GetCachedMethod(((object)player).GetType(), "TakeInput") ?? GetCachedMethod(typeof(Player), "TakeInput"); if (methodInfo != null) { object obj = methodInfo.Invoke(player, Array.Empty()); if (obj is bool) { return (bool)obj; } } } catch { } return !IsGcoTypingInputActive(); } private static bool IsGcoTypingInputActive() { try { if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return true; } } catch { } try { if (Console.IsVisible()) { return true; } } catch { } try { if (TextInput.IsVisible()) { return true; } } catch { } return false; } private static void ProcessGooCombatDebugMode() { Player localPlayer = Player.m_localPlayer; if (IsGuestDebugAuthorityLimited()) { if (_lastGooCombatDebugModeActive || _gcoNoPlacementCostDesired || _gcoDebugFlyDesired || _gcoGhostModeDesired) { ForceDisableGooDebugPlayerToggles(localPlayer); } _lastGooCombatDebugModeActive = false; ResetGuestRestrictedOneShotButtons(); return; } bool flag = IsGooCombatDebugModeActive(); if (_lastGooCombatDebugModeActive && !flag) { ForceDisableGooDebugPlayerToggles(localPlayer); } _lastGooCombatDebugModeActive = flag; if (!flag) { ResetPendingGooDebugToggleButtons(save: false); } else if (!((Object)(object)localPlayer == (Object)null)) { EnforceGooDebugToggleAvailability(localPlayer); EnsureGcoNativeGodMode(localPlayer, value: true); ProcessGooDebugToggleButtons(localPlayer); } } private static void ProcessGooDebugToggleButtons(Player player) { bool flag = false; if (ToggleNoPlacementCost != null && ToggleNoPlacementCost.Value) { ToggleNoPlacementCost.Value = false; flag = true; if (IsDebugNoCostToggleAllowed()) { _gcoNoPlacementCostDesired = !_gcoNoPlacementCostDesired; try { ApplyGooDebugDesiredPlayerToggles(player); } catch { } BroadcastGcoChatMessage((_gcoNoPlacementCostDesired ? "Enabled" : "Disabled") + " NoPlacementCost "); } else { DebugLogThrottled("debug-nocost-toggle-disabled", 1f, "[GCO Debug] Ignored DebugNoCost button because EnableNoPlacementCostToggle is false."); } } if (ToggleDebugFly != null && ToggleDebugFly.Value) { ToggleDebugFly.Value = false; flag = true; if (IsDebugFlyToggleAllowed()) { _gcoDebugFlyDesired = !_gcoDebugFlyDesired; try { ApplyGooDebugDesiredPlayerToggles(player); } catch { } BroadcastGcoChatMessage((_gcoDebugFlyDesired ? "Enabled" : "Disabled") + " DebugFly "); } else { DebugLogThrottled("debug-fly-toggle-disabled", 1f, "[GCO Debug] Ignored DebugFly button because EnableDebugFlyToggle is false."); } } if (ToggleGhostMode != null && ToggleGhostMode.Value) { ToggleGhostMode.Value = false; flag = true; if (IsDebugGhostToggleAllowed()) { _gcoGhostModeDesired = !_gcoGhostModeDesired; try { ApplyGooDebugDesiredPlayerToggles(player); } catch { } BroadcastGcoChatMessage((_gcoGhostModeDesired ? "Enabled" : "Disabled") + " GhostMode "); } else { DebugLogThrottled("debug-ghost-toggle-disabled", 1f, "[GCO Debug] Ignored DebugGhost button because EnableGhostModeToggle is false."); } } if (DebugModeHeal != null && DebugModeHeal.Value) { DebugModeHeal.Value = false; flag = true; try { HealDebugPlayerResources(player); } catch { } BroadcastGcoChatMessage("Healed "); } if (DebugModeAddRested != null && DebugModeAddRested.Value) { DebugModeAddRested.Value = false; flag = true; try { AddRestedDebugEffect(player); } catch { } BroadcastGcoChatMessage("Added Rested "); } if (flag) { try { ((BaseUnityPlugin)Instance).Config.Save(); } catch { } } } private static void ResetPendingGooDebugToggleButtons(bool save) { bool flag = false; if (ToggleNoPlacementCost != null && ToggleNoPlacementCost.Value) { ToggleNoPlacementCost.Value = false; flag = true; } if (ToggleDebugFly != null && ToggleDebugFly.Value) { ToggleDebugFly.Value = false; flag = true; } if (ToggleGhostMode != null && ToggleGhostMode.Value) { ToggleGhostMode.Value = false; flag = true; } if (DebugModeHeal != null && DebugModeHeal.Value) { DebugModeHeal.Value = false; flag = true; } if (DebugModeAddRested != null && DebugModeAddRested.Value) { DebugModeAddRested.Value = false; flag = true; } if (flag && save) { try { ((BaseUnityPlugin)Instance).Config.Save(); } catch { } } } private static bool IsDebugFlyToggleAllowed() { if (EnableDebugFlyToggle != null) { return EnableDebugFlyToggle.Value; } return true; } private static bool IsDebugNoCostToggleAllowed() { if (EnableNoPlacementCostToggle != null) { return EnableNoPlacementCostToggle.Value; } return true; } private static bool IsDebugGhostToggleAllowed() { if (EnableGhostModeToggle != null) { return EnableGhostModeToggle.Value; } return true; } private static void EnforceGooDebugToggleAvailability(Player player) { bool flag = false; if (!IsDebugNoCostToggleAllowed() && _gcoNoPlacementCostDesired) { _gcoNoPlacementCostDesired = false; flag = true; BroadcastGcoChatMessage("Disabled NoPlacementCost "); } if (!IsDebugFlyToggleAllowed() && _gcoDebugFlyDesired) { _gcoDebugFlyDesired = false; flag = true; BroadcastGcoChatMessage("Disabled DebugFly "); } if (!IsDebugGhostToggleAllowed() && _gcoGhostModeDesired) { _gcoGhostModeDesired = false; flag = true; BroadcastGcoChatMessage("Disabled GhostMode "); } if (flag) { try { ApplyGooDebugDesiredPlayerToggles(player); } catch { } } } private static void ApplyGooDebugDesiredPlayerToggles(Player player) { if ((Object)(object)player == (Object)null || !IsGooCombatDebugModeActive()) { return; } try { SetGcoNoPlacementCostState(player, _gcoNoPlacementCostDesired); } catch { } try { SetGcoDebugFlyState(player, _gcoDebugFlyDesired); } catch { } try { SetGcoGhostModeState(player, _gcoGhostModeDesired); } catch { } } private static void ForceDisableGooDebugPlayerToggles(Player? player) { bool gcoNoPlacementCostDesired = _gcoNoPlacementCostDesired; bool gcoDebugFlyDesired = _gcoDebugFlyDesired; bool gcoGhostModeDesired = _gcoGhostModeDesired; _gcoNoPlacementCostDesired = false; _gcoDebugFlyDesired = false; _gcoGhostModeDesired = false; if (gcoNoPlacementCostDesired) { BroadcastGcoChatMessage("Disabled NoPlacementCost "); } if (gcoDebugFlyDesired) { BroadcastGcoChatMessage("Disabled DebugFly "); } if (gcoGhostModeDesired) { BroadcastGcoChatMessage("Disabled GhostMode "); } if ((Object)(object)player == (Object)null) { return; } try { SetGcoNoPlacementCostState(player, value: false); } catch { } try { SetGcoDebugFlyState(player, value: false); } catch { } try { SetGcoGhostModeState(player, value: false); } catch { } try { EnsureGcoNativeGodMode(player, value: false); } catch { } } private static void EnsureGcoNativeGodMode(Player player, bool value) { if ((Object)(object)player == (Object)null) { return; } try { if (((Character)player).InGodMode() != value) { player.SetGodMode(value); } } catch { } } private static void SetGcoNoPlacementCostState(Player player, bool value) { if (GetPrivateBool(player, "m_noPlacementCost", fallback: false) != value) { try { player.SetNoPlacementCost(value); } catch { } SetFieldValueRaw(player, "m_noPlacementCost", value); TryInvokePrivateVoid(player, "UpdateAvailablePiecesList"); } } private static void SetGcoDebugFlyState(Player player, bool value) { if (GetPrivateBool(player, "m_debugFly", fallback: false) == value) { return; } try { if (((Character)player).IsDebugFlying() != value) { player.ToggleDebugFly(); } } catch { } SetFieldValueRaw(player, "m_debugFly", value); try { ZNetView fieldValue = GetFieldValue(player, "m_nview"); if ((Object)(object)fieldValue != (Object)null && fieldValue.IsValid()) { fieldValue.GetZDO().Set(ZDOVars.s_debugFly, value); } } catch { } } private static void SetGcoGhostModeState(Player player, bool value) { if (GetPrivateBool(player, "m_ghostMode", fallback: false) != value) { try { player.SetGhostMode(value); } catch { } SetFieldValueRaw(player, "m_ghostMode", value); } } private static bool IsGooCombatDebugModeActive() { if (ModActive && !IsGuestDebugAuthorityLimited() && GooCombatDebugMode != null) { return GooCombatDebugMode.Value; } return false; } private static void HealDebugPlayerResources(Player player) { if ((Object)(object)player == (Object)null) { return; } try { float health = Mathf.Max(1f, ((Character)player).GetMaxHealth()); ((Character)player).SetHealth(health); } catch { } try { SetPlayerStamina(player, Mathf.Max(0f, ((Character)player).GetMaxStamina())); } catch { } try { SetPlayerEitr(player, Mathf.Max(0f, ((Character)player).GetMaxEitr())); } catch { } } private static void AddRestedDebugEffect(Player player) { if ((Object)(object)player == (Object)null) { return; } try { ((Character)player).GetSEMan().AddStatusEffect(SEMan.s_statusEffectRested, true, 0, 0f); } catch { } } private static void SetPlayerEitr(Player player, float value) { SetPrivateFloat(player, "m_eitr", value); try { ZNetView fieldValue = GetFieldValue(player, "m_nview"); if ((Object)(object)fieldValue != (Object)null && fieldValue.IsValid() && fieldValue.IsOwner()) { fieldValue.GetZDO().Set(ZDOVars.s_eitr, value); } } catch { } } private static float TryEstimateHitTotalDamage(HitData hit) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (hit == null) { return 0f; } try { MethodInfo method = ((object)hit).GetType().GetMethod("GetTotalDamage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null && method.Invoke(hit, null) is float result) { return result; } } catch { } try { MethodInfo method2 = ((object)hit.m_damage).GetType().GetMethod("GetTotalDamage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method2 != null && method2.Invoke(hit.m_damage, null) is float result2) { return result2; } } catch { } return 999999f; } private static bool ItemUsesDurability(ItemData item) { if (item?.m_shared == null) { return false; } try { if (item.GetMaxDurability() > 0f) { return true; } } catch { } if (!item.m_shared.m_useDurability && !(item.m_shared.m_useDurabilityDrain > 0f)) { return item.m_shared.m_durabilityDrain > 0f; } return true; } private static DurabilityPatchState? CaptureDurabilityStateFromInstance(object instance) { if (!ModActive || instance == null || (GlobalUnbreakableGear == null && GlobalDurabilityConsumptionMultiplier == null)) { return null; } Player val = (Player)((instance is Player) ? instance : null); if ((Object)(object)val == (Object)null) { Character val2 = (Character)((instance is Character) ? instance : null); if (val2 != null) { val = (Player)(object)((val2 is Player) ? val2 : null); } } if ((Object)(object)val == (Object)null) { Humanoid val3 = (Humanoid)((instance is Humanoid) ? instance : null); if (val3 != null) { val = (Player)(object)((val3 is Player) ? val3 : null); } } if ((Object)(object)val == (Object)null) { Character? fieldValue = GetFieldValue(instance, "m_character"); val = (Player)(object)((fieldValue is Player) ? fieldValue : null); } if ((Object)(object)val != (Object)null) { return CapturePlayerDurabilityState(val); } ItemData fieldValue2 = GetFieldValue(instance, "m_weapon"); if (fieldValue2 != null) { return new DurabilityPatchState((IEnumerable)(object)new ItemData[1] { fieldValue2 }); } return null; } private static DurabilityPatchState CapturePlayerDurabilityState(Player player) { List list = new List(); try { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { list.AddRange(inventory.GetAllItems()); } } catch { } string[] array = new string[8] { "m_rightItem", "m_leftItem", "m_chestItem", "m_legItem", "m_helmetItem", "m_shoulderItem", "m_utilityItem", "m_trinketItem" }; foreach (string fieldName in array) { ItemData equippedItem = GetEquippedItem(player, fieldName); if (equippedItem != null && !list.Contains(equippedItem)) { list.Add(equippedItem); } } return new DurabilityPatchState(list); } private static void ApplyDurabilityState(DurabilityPatchState? state) { if (!ModActive || state == null) { return; } foreach (KeyValuePair item in state.Before) { ItemData key = item.Key; if (key == null || key.m_shared == null) { continue; } float value = item.Value; float durability = key.m_durability; if (!(durability >= value)) { float num = value - durability; bool unbreakable; float durabilityConsumptionMultiplier = GetDurabilityConsumptionMultiplier(key, out unbreakable); float num2 = (unbreakable ? value : (value - num * durabilityConsumptionMultiplier)); try { float num3 = Mathf.Max(value, key.GetMaxDurability()); key.m_durability = Mathf.Clamp(num2, 0f, num3); } catch { key.m_durability = Mathf.Max(0f, num2); } } } } private static float GetDurabilityConsumptionMultiplier(ItemData item, out bool unbreakable) { unbreakable = false; float num = ((GlobalDurabilityConsumptionMultiplier != null) ? Mathf.Clamp(GlobalDurabilityConsumptionMultiplier.Value, 0f, 20f) : 1f); if (GlobalUnbreakableGear != null && GlobalUnbreakableGear.Value) { unbreakable = true; } GearDurabilityConfig gearDurabilityConfig = GetGearDurabilityConfig(item); if (gearDurabilityConfig != null) { if (gearDurabilityConfig.Unbreakable.Value) { unbreakable = true; } num *= Mathf.Clamp(gearDurabilityConfig.DurabilityConsumptionMultiplier.Value, 0f, 20f); } return Mathf.Clamp(num, 0f, 100f); } private static float GetBaseDurabilityMultiplier(ItemData item) { float num = ((GlobalBaseDurabilityMultiplier != null) ? Mathf.Clamp(GlobalBaseDurabilityMultiplier.Value, 0f, 20f) : 1f); GearDurabilityConfig gearDurabilityConfig = GetGearDurabilityConfig(item); if (gearDurabilityConfig != null) { num *= Mathf.Clamp(gearDurabilityConfig.BaseDurabilityMultiplier.Value, 0f, 20f); } return Mathf.Clamp(num, 0f, 100f); } private static float GetDurabilityMultiplier(ItemData item) { float num = ((GlobalDurabilityMultiplier != null) ? Mathf.Clamp(GlobalDurabilityMultiplier.Value, 0f, 20f) : 1f); GearDurabilityConfig gearDurabilityConfig = GetGearDurabilityConfig(item); if (gearDurabilityConfig != null) { num *= Mathf.Clamp(gearDurabilityConfig.DurabilityMultiplier.Value, 0f, 20f); } return Mathf.Clamp(num, 0f, 100f); } private static GearDurabilityConfig? GetGearDurabilityConfig(ItemData item) { if (item?.m_shared == null) { return null; } string key = GearDurabilityKey(item); if (!GearDurabilityConfigs.TryGetValue(key, out GearDurabilityConfig value)) { return null; } return value; } private static string GearDurabilityKey(ItemData item) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_000e: Invalid comparison between Unknown and I4 //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 ItemType itemType = item.m_shared.m_itemType; if ((int)itemType == 5) { return NormalizeCommandKey(ShieldDurabilityName(item)); } if (IsArmorLikeItem(item)) { return NormalizeCommandKey(ArmorDurabilityName(item)); } if ((int)itemType == 19 || (int)itemType == 15) { return NormalizeCommandKey("Tools"); } if ((int)itemType == 3 || (int)itemType == 14 || (int)itemType == 22 || (int)itemType == 4) { return NormalizeCommandKey(WeaponDurabilityName(ClassifyWeapon(item))); } return NormalizeCommandKey("Other Gear"); } private static string WeaponDurabilityName(WeaponCategory category) { return category switch { WeaponCategory.TwoHandedSword => "Two-Handed Swords", WeaponCategory.TwoHandedAxe => "Two-Handed Axes", WeaponCategory.Sledge => "Sledges", WeaponCategory.Pickaxe => "Pickaxes", WeaponCategory.DualAxes => "Dual Axes", WeaponCategory.Atgeir => "Atgeirs", WeaponCategory.OneHandedSword => "One-Handed Swords", WeaponCategory.OneHandedAxe => "One-Handed Axes", WeaponCategory.Club => "Clubs and Maces", WeaponCategory.Knife => "Knives", WeaponCategory.DualKnives => "Dual Knives", WeaponCategory.Spear => "Spears", WeaponCategory.Bow => "Bows", WeaponCategory.Crossbow => "Crossbows", WeaponCategory.Magic => "Other", WeaponCategory.Unarmed => "Unarmed", _ => "Other", }; } private static string ShieldDurabilityName(ItemData item) { if (item?.m_shared == null) { return "Medium Shields"; } SharedData shared = item.m_shared; if (shared.m_timedBlockBonus > 1f) { return "Bucklers"; } if (shared.m_movementModifier <= -0.15f || shared.m_name.ToLowerInvariant().Contains("tower")) { return "Tower Shields"; } return "Medium Shields"; } private static string ArmorDurabilityName(ItemData item) { float movementModifier = item.m_shared.m_movementModifier; if (movementModifier >= -0.005f) { return "Light Armor"; } if (movementModifier > -0.035f) { return "Medium Armor"; } return "Heavy Armor"; } public static bool HumanoidDrainEquippedItemDurabilityPrefix(ItemData item, ref float dt) { if (!ModActive || item == null || item.m_shared == null) { return true; } bool unbreakable; float durabilityConsumptionMultiplier = GetDurabilityConsumptionMultiplier(item, out unbreakable); if (unbreakable || durabilityConsumptionMultiplier <= 0.0001f) { return false; } if (!Mathf.Approximately(durabilityConsumptionMultiplier, 1f)) { dt *= durabilityConsumptionMultiplier; } return true; } public static void DurabilitySnapshotPrefix(object __instance, ref DurabilityPatchState? __state) { __state = CaptureDurabilityStateFromInstance(__instance); } public static void DurabilitySnapshotPostfix(DurabilityPatchState? __state) { ApplyDurabilityState(__state); } public static void PlayerUpdateFoodPrefix(Player __instance, bool forceUpdate, ref FoodFreezePatchState? __state) { __state = null; if (!ModActive || (Object)(object)__instance == (Object)null) { return; } List playerFoodList = GetPlayerFoodList(__instance); if (playerFoodList == null) { return; } FoodFreezePatchState foodFreezePatchState = new FoodFreezePatchState(); if (PermanentFoodBuffs != null && PermanentFoodBuffs.Value) { foreach (Food item in playerFoodList) { if (item != null) { foodFreezePatchState.FoodSnapshots.Add(new FoodSnapshot(item)); } } } float num = ((FoodHealingRateMultiplier != null) ? Mathf.Clamp(FoodHealingRateMultiplier.Value, 0f, 50f) : 1f); if (!forceUpdate && !Mathf.Approximately(num, 1f)) { foreach (Food item2 in playerFoodList) { SharedData val = item2?.m_item?.m_shared; if (val != null && !foodFreezePatchState.FoodRegenBefore.ContainsKey(val)) { foodFreezePatchState.FoodRegenBefore[val] = val.m_foodRegen; val.m_foodRegen = Mathf.Max(0f, val.m_foodRegen * num); } } } if (foodFreezePatchState.HasWork) { __state = foodFreezePatchState; } } public static void PlayerUpdateFoodPostfix(Player __instance, FoodFreezePatchState? __state) { if (__state == null) { return; } foreach (KeyValuePair item in __state.FoodRegenBefore) { try { item.Key.m_foodRegen = item.Value; } catch { } } if (!((Object)(object)__instance != (Object)null) || __state.FoodSnapshots.Count <= 0) { return; } List playerFoodList = GetPlayerFoodList(__instance); if (playerFoodList == null) { return; } foreach (FoodSnapshot foodSnapshot in __state.FoodSnapshots) { if (foodSnapshot.Food != null) { if (!playerFoodList.Contains(foodSnapshot.Food)) { playerFoodList.Add(foodSnapshot.Food); } foodSnapshot.RestoreTimeAndRecalculateValues(); } } RecalculatePlayerFoodMaxValues(__instance, flashBars: false); } public static void SEStatsModifyStaminaRegenPrefix(SE_Stats __instance, ref float staminaRegen, ref float __state) { __state = staminaRegen; } public static void SEStatsModifyStaminaRegenPostfix(SE_Stats __instance, float __state, ref float staminaRegen) { ApplyMeadRecoveryBonusMultiplier((StatusEffect)(object)__instance, __state, ref staminaRegen, MeadStaminaRecoveryBonusMultiplier); } public static void SEStatsModifyEitrRegenPrefix(SE_Stats __instance, ref float staminaRegen, ref float __state) { __state = staminaRegen; } public static void SEStatsModifyEitrRegenPostfix(SE_Stats __instance, float __state, ref float staminaRegen) { ApplyMeadRecoveryBonusMultiplier((StatusEffect)(object)__instance, __state, ref staminaRegen, MeadEitrRecoveryBonusMultiplier); } public static void SEStatsModifyHealthRegenPrefix(SE_Stats __instance, ref float regenMultiplier, ref float __state) { __state = regenMultiplier; } public static void SEStatsModifyHealthRegenPostfix(SE_Stats __instance, float __state, ref float regenMultiplier) { ApplyMeadRecoveryBonusMultiplier((StatusEffect)(object)__instance, __state, ref regenMultiplier, MeadHealthRegenBonusMultiplier); } private static void ApplyMeadRecoveryBonusMultiplier(StatusEffect effect, float before, ref float after, ConfigEntry? config) { if (!ModActive || (Object)(object)effect == (Object)null || config == null || !IsLikelyMeadStatusEffect(effect)) { return; } float num = Mathf.Clamp(config.Value, 0f, 50f); if (!Mathf.Approximately(num, 1f)) { float num2 = after - before; if (!Mathf.Approximately(num2, 0f)) { after = before + num2 * num; } } } public static void StatusEffectUpdateStatusEffectPostfix(StatusEffect __instance) { if (ModActive && !((Object)(object)__instance == (Object)null)) { if (PermanentRestedBuff != null && PermanentRestedBuff.Value && __instance.NameHash() == SEMan.s_statusEffectRested) { SetPrivateFloat(__instance, "m_time", 0f); } else if (PermanentMeadBuffs != null && PermanentMeadBuffs.Value && IsLikelyMeadStatusEffect(__instance)) { SetPrivateFloat(__instance, "m_time", 0f); } } } private static bool IsLikelyMeadStatusEffect(StatusEffect se) { string text = SafeLower(((Object)se).name) + " " + SafeLower(se.m_name) + " " + SafeLower(se.m_category) + " " + SafeLower(se.m_tooltip); if (!text.Contains("mead") && !text.Contains("potion") && !text.Contains("barleywine") && !text.Contains("wine") && !text.Contains("fire resistance") && !text.Contains("frost resistance") && !text.Contains("poison resistance")) { return text.Contains("lingering"); } return true; } private static void ApplyFlankingDamageIfEligible(Character victim, Character? attacker, HitData hit) { if (!TryGetFlankingDamageContext(victim, attacker, hit, out Player player, out bool pvp, out WeaponCategory category, out AttackRole role, out float global, out float local, out bool dingEnabled) || !ShouldApplyStackableDamageBonus(hit, DamageBonusKind.Flanking, FlankingDamageStackableWithOtherBonuses != null && FlankingDamageStackableWithOtherBonuses.Value)) { return; } float num = Mathf.Clamp(global * local, 0f, 100f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); if (dingEnabled && SuccessfulFlankDing != null && SuccessfulFlankDing.Value) { PlayConditionalDamageDing(victim, attacker, hit, "flank"); } if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"flank-damage:{((Object)victim).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, $"Flank damage for {SafeName((Character)(object)player)} -> {SafeName(victim)} using {category}/{role}: pvp={pvp}, global={global.ToString(CultureInfo.InvariantCulture)}, local={local.ToString(CultureInfo.InvariantCulture)}, final={num.ToString(CultureInfo.InvariantCulture)}, ding={dingEnabled}."); } } } private static bool TryGetFlankingDamageContext(Character victim, Character? attacker, HitData hit, out Player player, out bool pvp, out WeaponCategory category, out AttackRole role, out float global, out float local, out bool dingEnabled) { player = null; pvp = false; category = WeaponCategory.Unarmed; role = AttackRole.Primary1; global = 1f; local = 1f; dingEnabled = false; if (ModActive && EnableFlankingDamage != null && EnableFlankingDamage.Value && !((Object)(object)victim == (Object)null) && hit != null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && victim != attacker) { pvp = IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim; if (!pvp) { try { if (victim.IsTamed()) { return false; } } catch { } } player = val; ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); if (TryGetOriginatingAttackInstance(player, out object attackInstance) && IsThrowableAttack(attackInstance, currentWeapon)) { if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"flank-throwable-rejected:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, "Flank bonus rejected for bomb-style throwable damage from " + SafeName((Character)(object)player) + "; projectile and lingering AOE damage do not use facing-based flank bonuses."); } return false; } category = ResolveActiveAttackCategory((Character)(object)player, currentWeapon); if (category == WeaponCategory.Sledge) { return false; } role = GetCurrentAttackRole((Character)(object)player, category); if (!GetConfiguredFlankingDamageEnabled(category, role)) { return false; } if (!IsFlankingHit(victim, attacker, hit, pvp)) { return false; } global = ((!pvp) ? ((GlobalBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalBackstabDamageMultiplier.Value, 0f, 20f) : 1f) : ((GlobalPvpBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalPvpBackstabDamageMultiplier.Value, 0f, 20f) : 1f)); local = GetConfiguredFlankingDamageMultiplier(category, role, pvp); dingEnabled = GetConfiguredFlankingDingEnabled(category, role); return !Mathf.Approximately(Mathf.Clamp(global * local, 0f, 100f), 1f); } } return false; } private static bool IsFlankingHit(Character victim, Character? attacker, HitData hit, bool pvp) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: 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_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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) float num = ((!pvp) ? ((FlankingPveRearAngleDegrees != null) ? Mathf.Clamp(FlankingPveRearAngleDegrees.Value, 0f, 360f) : 90f) : ((FlankingPvpRearAngleDegrees != null) ? Mathf.Clamp(FlankingPvpRearAngleDegrees.Value, 0f, 360f) : 90f)); if (num <= 0f) { return false; } if (num >= 359.9f) { return true; } Vector3 val; try { val = victim.GetCenterPoint(); } catch { val = ((Component)victim).transform.position; } Vector3 val2 = Vector3.ProjectOnPlane(hit.m_point - val, Vector3.up); if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f && (Object)(object)attacker != (Object)null) { val2 = Vector3.ProjectOnPlane(((Component)attacker).transform.position - val, Vector3.up); } Vector3 val3 = Vector3.ProjectOnPlane(((Component)victim).transform.forward, Vector3.up); if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f || ((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { return false; } ((Vector3)(ref val2)).Normalize(); ((Vector3)(ref val3)).Normalize(); Vector3 val4 = -val3; float num2 = Mathf.Cos(num * 0.5f * ((float)Math.PI / 180f)); return Vector3.Dot(val2, val4) >= num2; } private static DamageBonusStackState GetOrBuildDamageBonusStackState(Character victim, Character? attacker, HitData hit) { if (hit == null) { return DamageBonusStackState.Empty; } if (DamageBonusStackStates.TryGetValue(hit, out DamageBonusStackState value)) { return value; } bool backstab = IsBackstabBonusLikelyEligible(victim, attacker, hit); bool staggeredEnemy = IsStaggeredPveEnemyBonusEligible(victim, attacker); Player player; bool pvp; WeaponCategory category; AttackRole role; float global; float local; bool dingEnabled; bool flanking = TryGetFlankingDamageContext(victim, attacker, hit, out player, out pvp, out category, out role, out global, out local, out dingEnabled); bool counterDamage = IsCounterDamageBonusLikelyEligible(victim, attacker, hit); DamageBonusStackState damageBonusStackState = new DamageBonusStackState(backstab, staggeredEnemy, flanking, counterDamage); try { DamageBonusStackStates.Add(hit, damageBonusStackState); } catch { } return damageBonusStackState; } private static bool ShouldApplyStackableDamageBonus(HitData hit, DamageBonusKind kind, bool stackable) { if (hit == null) { return true; } if (!DamageBonusStackStates.TryGetValue(hit, out DamageBonusStackState value)) { return true; } if (!value.IsActive(kind) || value.ActiveCount <= 1) { return true; } return stackable; } private static bool IsStaggeredPveEnemyBonusEligible(Character victim, Character? attacker) { if ((Object)(object)victim == (Object)null || !(attacker is Player) || victim == attacker) { return false; } if (IsPvpLikeTarget(victim) || !victim.IsStaggering()) { return false; } try { if (victim.IsTamed()) { return false; } } catch { } return true; } private static bool IsBackstabBonusLikelyEligible(Character victim, Character? attacker, HitData hit) { if (!((Object)(object)victim == (Object)null) && hit != null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && victim != attacker) { BaseAI baseAI; try { baseAI = victim.GetBaseAI(); } catch { return false; } if ((Object)(object)baseAI == (Object)null || baseAI.IsAlerted()) { return false; } float privateFloat = GetPrivateFloat(victim, "m_backstabTime", -99999f); if (Time.time - privateFloat <= 300f) { return false; } if (hit.m_ranged) { return hit.m_backstabBonus > 1f; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); if (currentWeapon != null && IsAbyssalHarpoon(currentWeapon)) { return false; } WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); if (!IsBackstabConfigCategory(category) && !TryGetStaffConfig(currentWeapon, out StaffConfig _)) { return false; } AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, category); return GetConfiguredBackstabDamageMultiplier(val, currentWeapon, category, currentAttackRole) > 1f; } } return false; } private static bool GetConfiguredFlankingDamageEnabled(WeaponCategory category, AttackRole role) { if (AttackFlankingDamageEnabled.TryGetValue(category, out RoleSettings value)) { return value.Get(role).Value; } return DefaultGooFlankingDamageEnabled(category, role); } private static bool GetConfiguredFlankingDingEnabled(WeaponCategory category, AttackRole role) { if (AttackFlankingDingEnabled.TryGetValue(category, out RoleSettings value)) { return value.Get(role).Value; } return GetConfiguredFlankingDamageEnabled(category, role); } private static float GetConfiguredFlankingDamageMultiplier(WeaponCategory category, AttackRole role, bool pvp) { if ((pvp ? AttackPvpFlankingDamageMultipliers : AttackFlankingDamageMultipliers).TryGetValue(category, out RoleSettings value)) { return Mathf.Clamp(value.Get(role).Value, 0f, 20f); } return 1f; } private static bool DefaultGooFlankingDamageEnabled(WeaponCategory category, AttackRole role) { switch (category) { case WeaponCategory.Bow: case WeaponCategory.Crossbow: case WeaponCategory.Magic: return false; case WeaponCategory.Spear: if (role == AttackRole.Secondary) { return false; } break; } return true; } private static float DefaultGooFlankingDamageMultiplier(WeaponCategory category, bool pvp) { switch (category) { case WeaponCategory.Sledge: return 1f; default: return 1.5f; case WeaponCategory.Knife: case WeaponCategory.DualKnives: return 2f; } } private static void ApplyEffectiveBlockAngleMultiplier(Player blocker, ItemData activeBlocker, HitData hit) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_012c: 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_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)blocker == (Object)null || activeBlocker?.m_shared == null || hit == null) { return; } float configuredEffectiveBlockAngleMultiplier = GetConfiguredEffectiveBlockAngleMultiplier(activeBlocker); float num = 0f - Mathf.Cos(Mathf.Clamp(90f * configuredEffectiveBlockAngleMultiplier, 0f, 179.9f) * ((float)Math.PI / 180f)); Vector3 val = Vector3.ProjectOnPlane(((Component)blocker).transform.forward, Vector3.up); Vector3 val2 = Vector3.ProjectOnPlane(hit.m_dir, Vector3.up); if (((Vector3)(ref val)).sqrMagnitude < 0.0001f || ((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { return; } ((Vector3)(ref val)).Normalize(); ((Vector3)(ref val2)).Normalize(); float num2 = Vector3.Dot(val2, val); bool flag = num2 <= 0f; bool flag2 = num2 <= num; if (flag == flag2) { return; } if (!EffectiveBlockAngleOriginalHitDirs.ContainsKey(hit)) { EffectiveBlockAngleOriginalHitDirs[hit] = hit.m_dir; } Vector3 val5; if (flag2) { Vector3 val3 = Vector3.Cross(Vector3.up, val); if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { val3 = Vector3.right; } ((Vector3)(ref val3)).Normalize(); float num3 = Mathf.Sign(Vector3.Dot(val2, val3)); if (Mathf.Approximately(num3, 0f)) { num3 = 1f; } Vector3 val4 = val3 * num3; val5 = val4 + Vector3.up * hit.m_dir.y; hit.m_dir = ((Vector3)(ref val5)).normalized; } else { val5 = val + Vector3.up * hit.m_dir.y; hit.m_dir = ((Vector3)(ref val5)).normalized; } } private static void RestoreEffectiveBlockAngleHitDir(HitData hit) { //IL_0014: 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) if (hit != null && EffectiveBlockAngleOriginalHitDirs.TryGetValue(hit, out var value)) { hit.m_dir = value; EffectiveBlockAngleOriginalHitDirs.Remove(hit); } } private static float GetConfiguredEffectiveBlockAngleMultiplier(ItemData? blocker) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 float num = ((GlobalEffectiveBlockAngleMultiplier != null) ? Mathf.Clamp(GlobalEffectiveBlockAngleMultiplier.Value, 0f, 4f) : 1f); if (blocker?.m_shared == null) { return num; } StaffConfig cfg; ConfigEntry value; if ((int)blocker.m_shared.m_itemType == 5) { ConfigEntry shieldEffectiveBlockAngleEntry = GetShieldEffectiveBlockAngleEntry(blocker); num *= ((shieldEffectiveBlockAngleEntry != null) ? Mathf.Clamp(shieldEffectiveBlockAngleEntry.Value, 0f, 4f) : 1f); } else if (TryGetStaffConfig(blocker, out cfg) && StaffEffectiveBlockAngleMultipliers.TryGetValue(cfg.PrefabName, out value)) { num *= Mathf.Clamp(value.Value, 0f, 4f); } else { WeaponCategory key = ClassifyWeapon(blocker); if (WeaponEffectiveBlockAngleMultipliers.TryGetValue(key, out ConfigEntry value2)) { num *= Mathf.Clamp(value2.Value, 0f, 4f); } } return Mathf.Clamp(num, 0f, 4f); } private static ConfigEntry? GetShieldEffectiveBlockAngleEntry(ItemData blocker) { string text = SafeLower((((Object)(object)blocker.m_dropPrefab != (Object)null) ? ((Object)blocker.m_dropPrefab).name : string.Empty) + " " + ((blocker.m_shared != null) ? blocker.m_shared.m_name : string.Empty)); if (text.Contains("tower")) { return ShieldTowerEffectiveBlockAngleMultiplier; } if (text.Contains("buckler")) { return ShieldBucklerEffectiveBlockAngleMultiplier; } return ShieldMediumEffectiveBlockAngleMultiplier; } public static void CharacterDamagePrefix(Character __instance, HitData hit, ref DamagePatchState __state) { __state = null; if (!ModActive || hit == null || (Object)(object)__instance == (Object)null) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); DurabilityPatchState durabilityPatchState = ((val != null) ? CapturePlayerDurabilityState(val) : null); Character attacker = GetAttacker(hit); GetOrBuildDamageBonusStackState(__instance, attacker, hit); CapturePvpTestDummyDebugBeforeDamage(__instance, attacker, hit); MarkMeleeHitStopPvpIfNeeded(__instance, attacker); SuppressBlockedPvpStaffSpawnDamageIfNeeded(__instance, attacker, hit); ApplyStaffSpawnDamageIfNeeded(__instance, attacker, hit); ApplyAlwaysMaxDamageInPvpIfNeeded(__instance, attacker, hit); ApplyBackstabDamageMultiplierIfConfigured(attacker, hit); AwardSneakXpForSuccessfulSneakAttackIfEligible(__instance, attacker, hit); ApplyKnockbackForceModifiers(__instance, attacker, hit); ApplyStaffShieldActiveMitigationIfNeeded(__instance, attacker, hit); ApplyOutgoingDamageAndPvpCalibration(__instance, attacker, hit); ApplyFlankingDamageIfEligible(__instance, attacker, hit); ApplyCounterDamageIfEligible(__instance, attacker, hit); ApplyPvpDamageCalibrationIfNeeded(__instance, attacker, hit); ApplyAlternateDamageCurveAfterConditionalBonusesIfNeeded(__instance, attacker, hit); ApplyPvpTestDummyArmorIfNeeded(__instance, attacker, hit); ApplyOutgoingStaggerCalibration(attacker, hit); ApplyPvpStaggerCalibration(__instance, attacker, hit); ApplyAdrenalineMultiplierIfEligible(attacker, hit); ApplyGlobalPlayerDamageTakenIfNeeded(__instance, hit); DamagePatchState damagePatchState = ApplyHyperArmorIfActive(__instance, attacker, hit); if (damagePatchState != null) { __state = damagePatchState; } if (durabilityPatchState != null) { if (__state == null) { __state = new DamagePatchState(__instance, -1f, 1f); } __state.DurabilityState = durabilityPatchState; } } public static void CharacterDamagePostfix(Character __instance, HitData hit, DamagePatchState __state) { if (__state != null) { if ((Object)(object)__instance != (Object)null) { ApplyHyperArmorFinalDamageReduction(__state); } ApplyDurabilityState(__state.DurabilityState); } TryPlayMoreLightningVisualEffectsOnHit(__instance, GetAttacker(hit), hit); LogPvpTestDummyDamageIfNeeded(__instance, hit); } private static void TryPlayMoreLightningVisualEffectsOnHit(Character victim, Character? attacker, HitData hit) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || BetterLightningEffects == null || !BetterLightningEffects.Value || (Object)(object)victim == (Object)null || hit == null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || victim == attacker || LightningVisualEffectHits.TryGetValue(hit, out ProcessedHitMarker _) || IsDundrWeapon(GetCurrentWeapon((Character)(object)val)) || !HitLooksLikePlayerLightningDamage(hit, val) || !TryGetHimminAflHitEffectTemplate(out object effectList) || effectList == null) { return; } Vector3 val2 = hit.m_point; if (float.IsNaN(val2.x) || float.IsNaN(val2.y) || float.IsNaN(val2.z) || float.IsInfinity(val2.x) || float.IsInfinity(val2.y) || float.IsInfinity(val2.z) || ((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { try { val2 = victim.GetCenterPoint(); } catch { val2 = ((Component)victim).transform.position; } } Quaternion val3 = ((((Vector3)(ref hit.m_dir)).sqrMagnitude > 0.0001f) ? Quaternion.LookRotation(-((Vector3)(ref hit.m_dir)).normalized) : Quaternion.identity); MethodInfo cachedMethod = GetCachedMethod(effectList.GetType(), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) }); if (cachedMethod == null) { return; } try { LightningVisualEffectHits.Add(hit, ProcessedHitMarker.Instance); cachedMethod.Invoke(effectList, new object[5] { val2, val3, null, 1f, -1 }); } catch (Exception ex) { if (Debug(DebugAttackLifecycle)) { Log.LogDebug((object)("Failed to play MoreLightningVisualEffectsOnHit: " + ex.Message)); } } } private static bool HitLooksLikePlayerLightningDamage(HitData hit, Player player) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (hit != null && hit.m_damage.m_lightning > 0f) { return true; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); if (currentWeapon?.m_shared == null) { return false; } try { if (currentWeapon.m_shared.m_damages.m_lightning > 0f || currentWeapon.m_shared.m_damagesPerLevel.m_lightning > 0f) { return true; } return currentWeapon.GetDamage(currentWeapon.m_quality, (float)currentWeapon.m_worldLevel).m_lightning > 0f; } catch { return WeaponLooksLightningCapable(currentWeapon); } } private static bool TryGetHimminAflHitEffectTemplate(out object? effectList) { effectList = null; if (CachedHimminAflHitEffectTemplate != null) { effectList = CachedHimminAflHitEffectTemplate; return true; } if (HimminAflHitEffectLookupAttempted || (Object)(object)ObjectDB.instance == (Object)null) { return false; } HimminAflHitEffectLookupAttempted = true; string[] array = new string[3] { "AtgeirHimminAfl", "AtgeirHimminafl", "AtgeirHimminAfl(Clone)" }; for (int i = 0; i < array.Length; i++) { if (TryGetHitEffectFromItemPrefab(array[i], out effectList) && effectList != null) { CachedHimminAflHitEffectTemplate = effectList; return true; } } try { foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } string text = SafeLower(((Object)item).name); if (text.Contains("himmin") || text.Contains("himin")) { effectList = GetBestHitEffectTemplate(item.GetComponent()?.m_itemData); if (effectList != null) { CachedHimminAflHitEffectTemplate = effectList; return true; } } } } catch { } return false; } private static bool TryGetHitEffectFromItemPrefab(string prefabName, out object? effectList) { effectList = null; try { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefabName) : null); effectList = GetBestHitEffectTemplate((((Object)(object)val != (Object)null) ? val.GetComponent() : null)?.m_itemData); return effectList != null; } catch { return false; } } private static object? GetBestHitEffectTemplate(ItemData? item) { if (item?.m_shared == null) { return null; } object fieldValueRaw = GetFieldValueRaw(item.m_shared.m_attack, "m_hitEffect"); if (fieldValueRaw != null) { return fieldValueRaw; } object fieldValueRaw2 = GetFieldValueRaw(item.m_shared, "m_hitEffect"); if (fieldValueRaw2 != null) { return fieldValueRaw2; } return GetFieldValueRaw(item.m_shared.m_secondaryAttack, "m_hitEffect"); } private static void AwardSneakXpForSuccessfulSneakAttackIfEligible(Character victim, Character? attacker, HitData hit) { if (!ModActive || SneakXpOnSuccessfulSneakAttack == null || !SneakXpOnSuccessfulSneakAttack.Value || (Object)(object)victim == (Object)null || hit == null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || victim == attacker || SneakXpAwardedHits.TryGetValue(hit, out ProcessedHitMarker _) || !IsBackstabBonusLikelyEligible(victim, attacker, hit)) { return; } float num = Mathf.Clamp((SneakXpOnSuccessfulSneakAttackBaseAmount != null) ? SneakXpOnSuccessfulSneakAttackBaseAmount.Value : 1f, 0f, 100f); if (num <= 0f) { return; } if (SneakXpScalesWithSneakDamageMultiplier != null && SneakXpScalesWithSneakDamageMultiplier.Value) { num *= Mathf.Clamp(GetEffectiveSneakXpDamageScale(val, hit), 1f, 100f); } if (hit.m_ranged) { num *= Mathf.Clamp((SneakXpRangedAttackMultiplier != null) ? SneakXpRangedAttackMultiplier.Value : 0.5f, 0f, 10f); } num = Mathf.Clamp(num, 0f, 1000f); if (num <= 0f) { return; } try { SneakXpAwardedHits.Add(hit, ProcessedHitMarker.Instance); ((Character)val).RaiseSkill((SkillType)101, num); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"sneak-xp:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, "Awarded Sneak XP " + num.ToString("0.###", CultureInfo.InvariantCulture) + " for successful sneak attack by " + SafeName((Character)(object)val) + " on " + SafeName(victim) + "."); } } catch { } } private static float GetEffectiveSneakXpDamageScale(Player player, HitData hit) { if (hit == null) { return 1f; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); if (hit.m_ranged) { return Mathf.Max(1f, hit.m_backstabBonus); } WeaponCategory category = ResolveActiveAttackCategory((Character)(object)player, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)player, category); return Mathf.Max(1f, GetConfiguredBackstabDamageMultiplier(player, currentWeapon, category, currentAttackRole)); } private static void ApplyGlobalPlayerDamageTakenIfNeeded(Character victim, HitData hit) { if (victim is Player && hit != null && GlobalPlayerDamageTakenMultiplier != null) { float num = Mathf.Clamp(GlobalPlayerDamageTakenMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } } } private static void CapturePvpTestDummyDebugBeforeDamage(Character victim, Character? attacker, HitData hit) { if ((Object)(object)victim == (Object)null || !(attacker is Player) || hit == null || !IsPvpTestDummy(victim) || PvpTestDummyDebugHits.TryGetValue(hit, out PvpDummyDebugState _)) { return; } try { PvpTestDummyDebugHits.Add(hit, new PvpDummyDebugState(victim.GetHealth(), GetPrivateFloat(victim, "m_staggerDamage", 0f))); } catch { } } private static void LogPvpTestDummyDamageIfNeeded(Character victim, HitData hit) { if (!((Object)(object)victim == (Object)null) && hit != null && DebugPvpTestDummyOutput != null && DebugPvpTestDummyOutput.Value && IsPvpTestDummy(victim) && GetAttacker(hit) is Player) { float num = victim.GetMaxHealth(); float num2 = 0f; if (PvpTestDummyDebugHits.TryGetValue(hit, out PvpDummyDebugState value)) { num = value.HealthBefore; num2 = value.StaggerBefore; PvpTestDummyDebugHits.Remove(hit); } float num3 = Mathf.Max(1f, victim.GetMaxHealth()); float num4 = Mathf.Clamp((value != null && value.HealthAfterDamage >= 0f) ? value.HealthAfterDamage : victim.GetHealth(), 0f, num3); float num5 = Mathf.Max(0f, num - num4); float num6 = ((value != null && value.StaggerAfterDamage >= 0f) ? value.StaggerAfterDamage : GetPrivateFloat(victim, "m_staggerDamage", 0f)); float num7 = num3 * Mathf.Max(0.001f, GetPrivateFloat(victim, "m_staggerDamageFactor", 0.4f)); Log.LogInfo((object)("GCO PvP dummy hit: damage dealt " + num5.ToString("0.###", CultureInfo.InvariantCulture) + ", HP " + num.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num4.ToString("0.###", CultureInfo.InvariantCulture) + ", stagger meter " + num2.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num6.ToString("0.###", CultureInfo.InvariantCulture) + " / " + num7.ToString("0.###", CultureInfo.InvariantCulture) + ".")); } } private static void ApplyDamageToStaggeredPveEnemyIfNeeded(Character victim, Character? attacker, HitData hit) { if ((Object)(object)victim == (Object)null || hit == null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && victim != attacker && IsStaggeredPveEnemyBonusEligible(victim, attacker)) { GetOrBuildDamageBonusStackState(victim, attacker, hit); bool flag = ShouldApplyStackableDamageBonus(hit, DamageBonusKind.StaggeredEnemy, StaggeredEnemyDamageStackableWithOtherBonuses != null && StaggeredEnemyDamageStackableWithOtherBonuses.Value); ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); WeaponCategory weaponCategory = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, weaponCategory); float num = ((GlobalDamageToStaggeredEnemiesMultiplier != null) ? Mathf.Clamp(GlobalDamageToStaggeredEnemiesMultiplier.Value, 0f, 20f) : 2f); float localDamageToStaggeredEnemyMultiplier = GetLocalDamageToStaggeredEnemyMultiplier(val, weaponCategory, currentAttackRole); float num2 = (flag ? Mathf.Clamp(num * localDamageToStaggeredEnemyMultiplier, 0f, 100f) : 1f); float num3 = num2 / 2f; if (!Mathf.Approximately(num3, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num3); } if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"staggered-enemy-damage:{((Object)victim).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, $"Damage-to-staggered-enemy result set for {SafeName((Character)(object)val)} -> {SafeName(victim)} using {weaponCategory} {currentAttackRole}: stackable={flag}, global={num.ToString(CultureInfo.InvariantCulture)}, local={localDamageToStaggeredEnemyMultiplier.ToString(CultureInfo.InvariantCulture)}, final={num2.ToString(CultureInfo.InvariantCulture)}, preVanillaScale={num3.ToString(CultureInfo.InvariantCulture)}."); } } } private static float GetLocalDamageToStaggeredEnemyMultiplier(Player player, WeaponCategory category, AttackRole role) { if ((Object)(object)player != (Object)null && TryGetJumpAttackStaggeredEnemyDamageMultiplier(player, category, out var multiplier)) { return multiplier; } if ((Object)(object)player != (Object)null && TryGetRunningAttackStaggeredEnemyDamageMultiplier(player, category, out var multiplier2)) { return multiplier2; } if (AttackStaggeredEnemyDamageMultipliers.TryGetValue(category, out RoleSettings value)) { return Mathf.Clamp(value.Get(role).Value, 0f, 20f); } return 1f; } private static bool TryGetJumpAttackStaggeredEnemyDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (fieldValueRaw == null || !JumpAttackInstances.Contains(fieldValueRaw)) { return false; } if (!JumpAttackStaggeredEnemyDamageMultipliers.TryGetValue(category, out ConfigEntry value)) { return false; } multiplier = Mathf.Clamp(value.Value, 0f, 20f); return true; } private static bool TryGetRunningAttackStaggeredEnemyDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (fieldValueRaw == null || !RunningAttackInstances.Contains(fieldValueRaw)) { return false; } if (!RunningAttackStaggeredEnemyDamageMultipliers.TryGetValue(category, out ConfigEntry value)) { return false; } multiplier = Mathf.Clamp(value.Value, 0f, 20f); return true; } public static void CharacterApplyDamagePrefix(Character __instance, HitData hit) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || (Object)(object)__instance == (Object)null || hit == null) { return; } Character attacker = GetAttacker(hit); ApplyDamageToStaggeredPveEnemyIfNeeded(__instance, attacker, hit); if (!IsPvpTestDummy(__instance) || !(attacker is Player)) { return; } if (__instance.IsStaggering()) { hit.ApplyModifier(0.5f); } float num = 1f; try { if ((Object)(object)Game.instance != (Object)null) { num *= Game.instance.GetDifficultyDamageScaleEnemy(((Component)__instance).transform.position); } } catch { } try { num *= Game.m_playerDamageRate; } catch { } float num2 = 1f; try { num2 = Game.m_localDamgeTakenRate; } catch { } if (num > 0.001f) { hit.ApplyModifier(num2 / num); } } public static void CharacterApplyDamagePostfix(Character __instance, HitData hit) { if (!ModActive || (Object)(object)__instance == (Object)null || !IsPvpTestDummy(__instance)) { return; } ZNetView component = ((Component)__instance).GetComponent(); if (!((Object)(object)component != (Object)null) || !component.IsValid() || component.IsOwner()) { float health = __instance.GetHealth(); if (hit != null && PvpTestDummyDebugHits.TryGetValue(hit, out PvpDummyDebugState value)) { value.HealthAfterDamage = health; value.StaggerAfterDamage = GetPrivateFloat(__instance, "m_staggerDamage", 0f); } if (!(health > 0f)) { float pvpTestDummyMaxHealth = GetPvpTestDummyMaxHealth(__instance); __instance.SetMaxHealth(pvpTestDummyMaxHealth); __instance.SetHealth(Mathf.Max(1f, pvpTestDummyMaxHealth)); SetPrivateFloat(__instance, "m_staggerDamage", 0f); } } } public static void CharacterRpcDamagePrefix(Character __instance, long sender, HitData hit, ref RpcDamageFlankSoundState? __state) { __state = null; if (!ModActive || (Object)(object)__instance == (Object)null || hit == null) { return; } CapturePendingPvpStaggerDurationFromIncomingHit(__instance, hit); Character attacker = GetAttacker(hit); if (!ShouldSuppressNativeStaggerCritEffectForFlank(__instance, attacker, hit)) { return; } object? fieldValueRaw = GetFieldValueRaw(__instance, "m_critHitEffects"); EffectList val = (EffectList)((fieldValueRaw is EffectList) ? fieldValueRaw : null); if (val != null) { __state = new RpcDamageFlankSoundState(CurrentRpcDamageCritEffectListToSuppress, CurrentRpcDamageCritEffectSuppressionDepth); CurrentRpcDamageCritEffectListToSuppress = val; CurrentRpcDamageCritEffectSuppressionDepth++; if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"flank-native-crit-armed:{((Object)__instance).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.1f, "Armed exact EffectList interception for same-hit native stagger critical sound on " + SafeName(__instance) + "; attacker=" + SafeName(attacker) + "."); } } } private static bool ShouldSuppressNativeStaggerCritEffectForFlank(Character victim, Character? attacker, HitData hit) { if (!ModActive || SuccessfulFlankDing == null || !SuccessfulFlankDing.Value || EnableFlankingDamage == null || !EnableFlankingDamage.Value) { return false; } if (!((Object)(object)victim == (Object)null) && hit != null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && victim != attacker) { bool flag = IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim; if (!flag) { try { if (victim.IsTamed()) { return false; } } catch { } } if (!IsFlankingHit(victim, attacker, hit, flag)) { return false; } ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); if (TryGetOriginatingAttackInstance(val, out object attackInstance) && IsThrowableAttack(attackInstance, currentWeapon)) { return false; } if (currentWeapon != null) { WeaponCategory weaponCategory = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); if (weaponCategory == WeaponCategory.Sledge) { return false; } AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, weaponCategory); if (!GetConfiguredFlankingDamageEnabled(weaponCategory, currentAttackRole) || !GetConfiguredFlankingDingEnabled(weaponCategory, currentAttackRole)) { return false; } float num = ((!flag) ? ((GlobalBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalBackstabDamageMultiplier.Value, 0f, 20f) : 1f) : ((GlobalPvpBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalPvpBackstabDamageMultiplier.Value, 0f, 20f) : 1f)); float configuredFlankingDamageMultiplier = GetConfiguredFlankingDamageMultiplier(weaponCategory, currentAttackRole, flag); if (Mathf.Approximately(Mathf.Clamp(num * configuredFlankingDamageMultiplier, 0f, 100f), 1f)) { return false; } } return true; } } return false; } private static bool TryGetCurrentAlternateSledgeEffectContext(out object attackInstance, out ItemData weapon, out AttackRole role) { attackInstance = null; weapon = null; role = AttackRole.Primary1; if (!ModActive) { return false; } AttackRuntimeState? attackRuntimeState = ((CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count > 0) ? new AttackRuntimeState?(CurrentAttackHitContexts.Peek()) : ((AttackRuntimeState?)null)); object obj = attackRuntimeState?.AttackInstance; if (obj == null) { Player localPlayer = Player.m_localPlayer; object obj2 = (((Object)(object)localPlayer != (Object)null) ? GetFieldValueRaw(localPlayer, "m_currentAttack") : null); if (obj2 != null && SledgeAlternateMovesetAttacks.Contains(obj2)) { obj = obj2; } } if (obj == null || !SledgeAlternateMovesetAttacks.Contains(obj)) { return false; } attackInstance = obj; ItemData value = null; if (!AttackInstanceWeapons.TryGetValue(attackInstance, out value)) { object? fieldValueRaw = GetFieldValueRaw(attackInstance, "m_weapon"); value = (ItemData)((fieldValueRaw is ItemData) ? fieldValueRaw : null); } if (value?.m_shared == null || ClassifyWeapon(value) != WeaponCategory.Sledge || !IsSledgeAlternateMovesetActiveForWeapon(value)) { return false; } weapon = value; role = (AttackInstanceRoles.TryGetValue(attackInstance, out var value2) ? value2 : (attackRuntimeState.HasValue ? attackRuntimeState.Value.Role : ResolvePrimaryChainRole(attackInstance))); return true; } private static bool TryGetAlternateSledgeTwoHandedAxeHitEffect(out EffectList? effectList) { effectList = CachedAlternateSledgeTwoHandedAxeHitEffect; if (effectList != null) { return true; } if (AlternateSledgeTwoHandedAxeHitEffectLookupAttempted || (Object)(object)ObjectDB.instance == (Object)null) { return false; } AlternateSledgeTwoHandedAxeHitEffectLookupAttempted = true; string[] array = new string[2] { "Battleaxe", "BattleaxeCrystal" }; foreach (string text in array) { try { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); EffectList val = ((itemPrefab == null) ? null : itemPrefab.GetComponent()?.m_itemData)?.m_shared?.m_hitEffect; if (val == null) { continue; } CachedAlternateSledgeTwoHandedAxeHitEffect = val; effectList = val; DebugSledgeAlternateLog("two-handed-axe-hit-effect-resolved", "Resolved alternate-sledge hit effect from vanilla prefab '" + text + "'."); return true; } catch { } } try { foreach (GameObject item in ObjectDB.instance.m_items) { ItemData val2 = ((item == null) ? null : item.GetComponent()?.m_itemData); if (val2?.m_shared != null && ClassifyWeapon(val2) == WeaponCategory.TwoHandedAxe) { EffectList hitEffect = val2.m_shared.m_hitEffect; if (hitEffect != null) { CachedAlternateSledgeTwoHandedAxeHitEffect = hitEffect; effectList = hitEffect; DebugSledgeAlternateLog("two-handed-axe-hit-effect-resolved-fallback", "Resolved alternate-sledge hit effect from fallback vanilla two-handed-axe prefab '" + ((Object)item).name + "'."); return true; } } } } catch { } DebugSledgeAlternateLog("two-handed-axe-hit-effect-unavailable", "Could not resolve a vanilla two-handed-axe shared hit EffectList for alternate-sledge impact sound.", 1f); return false; } private static void TryPlayAlternateSledgeTwoHandedAxeHitEffect(Vector3 point) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_005d: Unknown result type (might be due to invalid IL or missing references) if (!TryGetAlternateSledgeTwoHandedAxeHitEffect(out EffectList effectList) || effectList == null) { return; } try { effectList.Create(point, Quaternion.identity, (Transform)null, 1f, -1); DebugSledgeAlternateLog($"two-handed-axe-hit-effect-play:{Mathf.RoundToInt(point.x * 10f)}:{Mathf.RoundToInt(point.z * 10f)}", $"Played vanilla two-handed-axe hit effect for alternate-sledge impact at {point}.", 0.01f); } catch (Exception ex) { DebugSledgeAlternateLog("two-handed-axe-hit-effect-failed", "Failed to play alternate-sledge two-handed-axe hit effect: " + ex.GetType().Name + ": " + ex.Message); } } public static bool EffectListCreateFlankPriorityPrefix(EffectList __instance, object[] __args) { //IL_007e: 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) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if (TryGetCurrentAlternateSledgeEffectContext(out object attackInstance, out ItemData weapon, out AttackRole role)) { if (role != AttackRole.Primary3 && __instance == weapon.m_shared.m_triggerEffect) { DebugSledgeAlternateLog($"direct-role-shared-trigger-suppressed:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Suppressed the equipped sledge shared trigger EffectList for direct-ray alternate role {role}. Native sledge start/swing effects remain unchanged; Primary3 retains the native slam trigger. attack={DescribeAttackForSledgeDebug(attackInstance)}.", 0.01f); return false; } if (__instance == weapon.m_shared.m_hitEffect && __args != null && __args.Length != 0 && __args[0] is Vector3 point) { TryPlayAlternateSledgeTwoHandedAxeHitEffect(point); DebugSledgeAlternateLog($"shared-hit-replaced:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Replaced the equipped sledge shared hit EffectList with the vanilla two-handed-axe hit effect. role={role}, attack={DescribeAttackForSledgeDebug(attackInstance)}.", 0.01f); return false; } object fieldValueRaw = GetFieldValueRaw(attackInstance, "m_hitEffect"); if (role != AttackRole.Secondary && fieldValueRaw != null && __instance == fieldValueRaw) { DebugSledgeAlternateLog($"attack-hit-suppressed:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Suppressed the original sledge attack-specific hit EffectList after playing the replacement two-handed-axe shared hit effect. role={role}, attack={DescribeAttackForSledgeDebug(attackInstance)}.", 0.01f); return false; } } if (CurrentRpcDamageCritEffectSuppressionDepth <= 0 || CurrentRpcDamageCritEffectListToSuppress == null) { return true; } if (__instance != CurrentRpcDamageCritEffectListToSuppress) { return true; } if (Debug(DebugAttackLifecycle)) { DebugLogThrottled("flank-native-crit-suppressed", 0.1f, "Suppressed the exact same-hit native staggered-enemy critical EffectList because flank ding has priority."); } return false; } public static Exception? CharacterRpcDamageFinalizer(Character __instance, HitData __1, Exception? __exception, RpcDamageFlankSoundState? __state) { if (__state != null && !__state.Restored) { __state.Restored = true; CurrentRpcDamageCritEffectListToSuppress = __state.PreviousEffectList; CurrentRpcDamageCritEffectSuppressionDepth = __state.PreviousDepth; } if (__exception != null && TryGetCurrentAlternateSledgeEffectContext(out object attackInstance, out ItemData _, out AttackRole role)) { DebugSledgeAlternateLog($"rpc-damage-exception:{RuntimeHelpers.GetHashCode(attackInstance)}:{((__instance != null) ? ((Object)__instance).GetInstanceID() : 0)}", string.Format("Character.RPC_Damage threw during alternate-sledge hit processing: victim={0}, role={1}, exception={2}: {3}, weakSpot={4}, attack={5}.", SafeName(__instance), role, __exception.GetType().Name, __exception.Message, (__1 != null) ? __1.m_weakSpot.ToString(CultureInfo.InvariantCulture) : "", DescribeAttackForSledgeDebug(attackInstance)), 0.01f); } return __exception; } public static void CharacterRpcStaggerPrefix(Character __instance, ref Vector3 __1) { //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) if (!ModActive || (Object)(object)__instance == (Object)null) { return; } bool value; if (!(__instance is Player)) { if (MobMaintainDirectionWhenStaggered == null) { return; } value = MobMaintainDirectionWhenStaggered.Value; } else { if (PlayerMaintainDirectionWhenStaggered == null) { return; } value = PlayerMaintainDirectionWhenStaggered.Value; } if (value) { __1 = Vector3.zero; } } public static void CharacterRpcStaggerPostfix(Character __instance) { if (ModActive && !((Object)(object)__instance == (Object)null)) { ApplyPendingPvpStaggerDuration(__instance); } } public static void DestructibleDamagePrefix(object __instance, HitData __0) { if (ModActive && __instance != null && __0 != null && !(__instance is Character)) { Character attacker = GetAttacker(__0); ApplyOutgoingAttackDamageCalibration(attacker, __0); ApplyDamageToBuildingMultiplierIfNeeded(__instance, attacker, __0); } } private static void ApplyDamageToBuildingMultiplierIfNeeded(object target, Character? attacker, HitData hit) { if (target == null || hit == null || !IsWearNTearBuildingTarget(target)) { return; } float num = 1f; Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { if (PlayerDamageToBuildingsMultiplier != null) { num *= Mathf.Clamp(PlayerDamageToBuildingsMultiplier.Value, 0f, 10f); } num *= GetPlayerAttackBuildingDamageMultiplier(val, hit); } else if ((Object)(object)attacker != (Object)null && EnemyDamageToBuildingsMultiplier != null) { num *= Mathf.Clamp(EnemyDamageToBuildingsMultiplier.Value, 0f, 10f); } if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } } private static bool IsWearNTearBuildingTarget(object target) { try { Type type = target.GetType(); while (type != null) { if (string.Equals(type.Name, "WearNTear", StringComparison.Ordinal)) { return true; } type = type.BaseType; } } catch { } return false; } private static float GetPlayerAttackBuildingDamageMultiplier(Player player, HitData hit) { try { ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); WeaponCategory weaponCategory = ResolveActiveAttackCategory((Character)(object)player, currentWeapon); if (TryGetJumpAttackBuildingDamageMultiplier(player, weaponCategory, out var multiplier)) { return multiplier; } if (TryGetRunningAttackBuildingDamageMultiplier(player, weaponCategory, out var multiplier2)) { return multiplier2; } AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)player, weaponCategory); if (AttackBuildingDamageMultipliers.TryGetValue(weaponCategory, out RoleSettings value)) { return Mathf.Clamp(value.Get(currentAttackRole).Value, 0f, 10f); } } catch { } return 1f; } private static bool TryGetJumpAttackBuildingDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (fieldValueRaw == null || !JumpAttackInstances.Contains(fieldValueRaw)) { return false; } if (!JumpAttackBuildingDamageMultipliers.TryGetValue(category, out ConfigEntry value)) { return false; } multiplier = Mathf.Clamp(value.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackBuildingDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (fieldValueRaw == null || !RunningAttackInstances.Contains(fieldValueRaw)) { return false; } if (!RunningAttackBuildingDamageMultipliers.TryGetValue(category, out ConfigEntry value)) { return false; } multiplier = Mathf.Clamp(value.Value, 0f, 10f); return true; } public static void SEShieldSetLevelPostfix(object __instance) { if (ModActive && __instance != null && IsStaffShieldStatusEffect(__instance) && StaffConfigs.TryGetValue("StaffShield", out StaffConfig value) && value.ShieldHealthMultiplier != null) { float num = Mathf.Clamp(value.ShieldHealthMultiplier.Value, 0f, 20f); float privateFloat = GetPrivateFloat(__instance, "m_totalAbsorbDamage", 0f); if (privateFloat > 0f && !Mathf.Approximately(num, 1f)) { SetPrivateFloat(__instance, "m_totalAbsorbDamage", privateFloat * num); } } } public static void SEShieldOnDamagedPrefix(object __instance, HitData __0, Character __1) { if (ModActive && __instance != null && __0 != null && __1 is Player && IsStaffShieldStatusEffect(__instance) && StaffConfigs.TryGetValue("StaffShield", out StaffConfig value) && value.DamageTakenFromPlayersMultiplier != null) { float num = Mathf.Clamp(value.DamageTakenFromPlayersMultiplier.Value, 0f, 20f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref __0.m_damage)).Modify(num); } } } private static bool IsStaffShieldStatusEffect(object se) { return (SafeLower(GetFieldValueRaw(se, "m_name")?.ToString()) + " " + SafeLower(se.ToString())).Contains("shield"); } public unsafe static void CharacterAddRootMotionPrefix(Character __instance, ref Vector3 vel) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_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) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) if (!ModActive) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null) { return; } Vector3 val2 = vel; bool flag = false; WeaponCategory category = WeaponCategory.Other; AttackRole role = AttackRole.Primary1; Vector3 val3; if (((Character)val).InDodge() && RollDistanceMultiplier != null) { float num = Mathf.Clamp(RollDistanceMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { vel = new Vector3(vel.x * num, vel.y, vel.z * num); val3 = vel - val2; flag = ((Vector3)(ref val3)).sqrMagnitude > 1E-06f; } } if (TryGetActiveMeleeLungeMultiplier((Character)(object)val, out category, out role, out var multiplier)) { object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); multiplier *= ((GlobalLungeDistanceMultiplier != null) ? Mathf.Clamp(GlobalLungeDistanceMultiplier.Value, 0f, 10f) : 1f); if (fieldValueRaw != null && RunningAttackInstances.Contains(fieldValueRaw)) { multiplier *= ((GlobalRunningAttackLungeDistanceMultiplier != null) ? Mathf.Clamp(GlobalRunningAttackLungeDistanceMultiplier.Value, 0f, 10f) : 1f); } if (fieldValueRaw != null && JumpAttackInstances.Contains(fieldValueRaw)) { multiplier *= ((GlobalJumpAttackLungeDistanceMultiplier != null) ? Mathf.Clamp(GlobalJumpAttackLungeDistanceMultiplier.Value, 0f, 10f) : 1f); } vel = ScaleForwardRootMotionOnly(__instance, vel, multiplier); val3 = vel - val2; flag = ((Vector3)(ref val3)).sqrMagnitude > 1E-06f; } if (flag && Debug(DebugMovement)) { DebugLogThrottled($"lunge:{((Object)val).GetInstanceID()}:{category}:{role}", 0.5f, $"Lunge/root-motion adjusted for {SafeName((Character)(object)val)} using {category} {role}: {((object)(*(Vector3*)(&val2))/*cast due to .constrained prefix*/).ToString()} -> {((object)Unsafe.As(ref vel)/*cast due to .constrained prefix*/).ToString()}."); } } private static Vector3 ScaleForwardRootMotionOnly(Character character, Vector3 vel, float multiplier) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0081: 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) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || Mathf.Approximately(multiplier, 1f)) { return vel; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(vel.x, 0f, vel.z); if (((Vector3)(ref val)).sqrMagnitude <= 1E-06f) { return vel; } Vector3 forward = ((Component)character).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude <= 1E-06f) { return vel; } ((Vector3)(ref forward)).Normalize(); float num = Vector3.Dot(val, forward); if (num <= 0f) { return vel; } Vector3 val2 = forward * num; Vector3 val3 = val - val2 + val2 * multiplier; return new Vector3(val3.x, vel.y, val3.z); } public static bool CharacterStaggerPrefix(Character __instance, ref Vector3 __0) { if (!ModActive || (Object)(object)__instance == (Object)null) { return true; } if (__instance is Player && TryGetActiveHyperArmor(__instance, out WeaponCategory category, out AttackRole role, out object attackInstance) && Mathf.Max(0f, GetEffectiveHyperArmorStaggerTakenMultiplier(attackInstance, category, role)) <= 0.0001f) { if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)("Blocked stagger during hyperarmor for " + SafeName(__instance) + ".")); } return false; } return true; } public static void CharacterStaggerPostfix(Character __instance) { if (ModActive && !((Object)(object)__instance == (Object)null)) { ApplyPendingPvpStaggerDuration(__instance); } } private static void MarkMeleeHitStopPvpIfNeeded(Character victim, Character? attacker) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)victim == (Object)null) && IsPvpLikeTarget(victim) && victim != attacker && MeleeHitStopContexts.TryGetValue((Character)(object)val, out var value)) { MeleeHitStopContexts[(Character)(object)val] = new AttackRuntimeState(value.Category, value.Role, value.EndTime, value.AttackInstance, value.StartedAirborne, isPvpHitStop: true); } } private static void BeginMeleeHitStopContext(Character character, WeaponCategory category, AttackRole role, object? attackInstance) { if (!((Object)(object)character == (Object)null)) { MeleeHitStopContexts[character] = new AttackRuntimeState(category, role, Time.time + 0.5f, attackInstance); } } private static void EndMeleeHitStopContext(object? attackInstance, object[]? args) { Character character = null; Character value; if (attackInstance != null && args != null && TryGetAttackContext(attackInstance, args, out character, out ItemData _)) { if ((Object)(object)character != (Object)null) { MeleeHitStopContexts.Remove(character); } } else if (attackInstance != null && AttackInstanceOwners.TryGetValue(attackInstance, out value)) { MeleeHitStopContexts.Remove(value); } } private static void EndMeleeHitStopContextForCharacter(Character character, object? attackInstance) { if (!((Object)(object)character == (Object)null) && MeleeHitStopContexts.TryGetValue(character, out var value) && (value.AttackInstance == null || attackInstance == null || value.AttackInstance == attackInstance)) { MeleeHitStopContexts.Remove(character); } } private static bool TryGetMeleeHitStopContext(Character character, out AttackRuntimeState state) { state = default(AttackRuntimeState); if ((Object)(object)character == (Object)null) { return false; } if (!MeleeHitStopContexts.TryGetValue(character, out state)) { return false; } if (Time.time > state.EndTime || IsCharacterDead(character)) { MeleeHitStopContexts.Remove(character); return false; } return true; } public static bool CharacterFreezeFramePrefix(Character __instance, ref float __0) { if (ModActive) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { if (__0 <= 0f || __0 > 0.5f) { return true; } if (!TryGetMeleeHitStopContext((Character)(object)val, out var state)) { return true; } if (GlobalHitStopEnabled != null && !GlobalHitStopEnabled.Value) { if (Debug(DebugHitStop)) { Log.LogInfo((object)("Suppressed melee FreezeFrame(" + __0.ToString(CultureInfo.InvariantCulture) + "s) for " + SafeName((Character)(object)val) + " because global hit-stop is disabled.")); } return false; } if (!GetEffectiveHitStopOnHit(state.AttackInstance, state.Category, state.Role)) { if (Debug(DebugHitStop)) { Log.LogInfo((object)$"Suppressed melee FreezeFrame({__0.ToString(CultureInfo.InvariantCulture)}s) for {SafeName((Character)(object)val)} using {state.Category} {state.Role}."); } return false; } if (state.IsPvpHitStop) { float num = Mathf.Clamp(GetEffectivePvpHitStopDurationMultiplier(state.AttackInstance, state.Category, state.Role), 0f, 10f) * ((GlobalPvpHitStopDurationMultiplier != null) ? Mathf.Clamp(GlobalPvpHitStopDurationMultiplier.Value, 0f, 10f) : 1f); __0 *= num; } else { float value = HitStopDurationOverrideSeconds.Value; if (value >= 0f) { __0 = Mathf.Max(0f, value); } else { float num2 = Mathf.Clamp(HitStopDurationMultiplier.Value, 0f, 5f); float num3 = Mathf.Clamp(GetEffectiveHitStopDurationMultiplier(state.AttackInstance, state.Category, state.Role), 0f, 5f); __0 *= num2 * num3; } } if (__0 <= 0.0001f) { if (Debug(DebugHitStop)) { Log.LogInfo((object)$"Suppressed melee FreezeFrame through global hit-stop duration settings for {SafeName((Character)(object)val)} using {state.Category} {state.Role}."); } return false; } if (Debug(DebugHitStop)) { Log.LogInfo((object)$"Allowed melee FreezeFrame duration {__0.ToString(CultureInfo.InvariantCulture)}s for {SafeName((Character)(object)val)} using {state.Category} {state.Role}."); } return true; } } return true; } private static HyperArmorMode GetEffectiveHyperArmorMode(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return HyperArmorMode.Off; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.HyperArmorMode.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.HyperArmorMode.Value; } return GetConfig(category).GetHyperArmorMode(role); } private static float GetEffectiveHyperArmorDamageTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.DamageTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.DamageTakenMultiplier.Value; } return GetConfig(category).GetDamageTakenMultiplier(role); } private static float GetEffectiveHyperArmorStaggerTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.StaggerTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.StaggerTakenMultiplier.Value; } return GetConfig(category).GetStaggerTakenMultiplier(role); } private static float GetEffectiveHyperArmorKnockbackTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.KnockbackTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.KnockbackTakenMultiplier.Value; } return GetConfig(category).GetKnockbackTakenMultiplier(role); } private static float GetEffectivePvpHyperArmorDamageTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.PvpDamageTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.PvpDamageTakenMultiplier.Value; } return GetConfig(category).GetPvpDamageTakenMultiplierDuringHyperArmor(role); } private static float GetEffectivePvpHyperArmorStaggerTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.PvpStaggerTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.PvpStaggerTakenMultiplier.Value; } return GetConfig(category).GetPvpStaggerTakenMultiplierDuringHyperArmor(role); } private static float GetEffectivePvpHyperArmorKnockbackTakenMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.HyperArmor)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.PvpKnockbackTakenMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.PvpKnockbackTakenMultiplier.Value; } return GetConfig(category).GetPvpKnockbackTakenMultiplierDuringHyperArmor(role); } private static bool GetEffectiveCounterDamageEnabled(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.CounterDamage)) { return false; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.CounterDamageEnabled.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.CounterDamageEnabled.Value; } return GetConfig(category).GetCounterDamageEnabled(role); } private static float GetEffectiveCounterDamageMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.CounterDamage)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.CounterDamageMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.CounterDamageMultiplier.Value; } return GetConfig(category).GetCounterDamageMultiplier(role); } private static float GetEffectivePvpCounterDamageMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsFeatureApplied(category, ModFeature.CounterDamage)) { return 1f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.PvpCounterDamageMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.PvpCounterDamageMultiplier.Value; } return GetConfig(category).GetPvpCounterDamageMultiplier(role); } private static bool GetEffectiveHitStopOnHit(object? attackInstance, WeaponCategory category, AttackRole role) { if (IsFeatureApplied(category, ModFeature.HitStop)) { return GetEffectiveHitStopDurationMultiplier(attackInstance, category, role) > 0f; } return false; } private static float GetEffectiveHitStopDurationMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.HitStopDurationMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.HitStopDurationMultiplier.Value; } return GetConfig(category).GetHitStopDurationMultiplier(role); } private static float GetEffectivePvpHitStopDurationMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.PvpHitStopDurationMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.PvpHitStopDurationMultiplier.Value; } return GetConfig(category).GetPvpHitStopDurationMultiplier(role); } private static bool IsBalancedHyperArmorCompleted(object? attackInstance) { if (attackInstance != null) { return BalancedHyperArmorCompletedAttacks.Contains(attackInstance); } return false; } private static void MarkBalancedHyperArmorCompleted(object? attackInstance) { if (attackInstance != null) { BalancedHyperArmorCompletedAttacks.Add(attackInstance); if (Debug(DebugHyperArmorLifecycle)) { DebugLogThrottled("balanced-complete:" + RuntimeHelpers.GetHashCode(attackInstance), 1f, "Balanced hyperarmor marked complete for current attack; later recovery-frame Attack.Update calls will not restart it."); } } } private static void ClearBalancedHyperArmorCompleted(object? attackInstance) { if (attackInstance != null) { BalancedHyperArmorCompletedAttacks.Remove(attackInstance); } } private static void SetAttackStateIfChanged(Character character, WeaponCategory category, AttackRole role, object? attackInstance) { if (!((Object)(object)character == (Object)null) && (!AttackStates.TryGetValue(character, out var value) || value.Category != category || value.Role != role || value.AttackInstance != attackInstance || !(Time.time <= value.EndTime))) { SetAttackState(character, category, role, attackInstance); } } private static bool GetAttackStartedAirborne(Character character, object? attackInstance) { if ((Object)(object)character == (Object)null) { return false; } if (attackInstance != null && AttackInstanceStartedAirborne.TryGetValue(attackInstance, out var value)) { return value; } if (AttackStates.TryGetValue(character, out var value2) && (value2.AttackInstance == null || attackInstance == null || value2.AttackInstance == attackInstance)) { return value2.StartedAirborne; } Player val = (Player)(object)((character is Player) ? character : null); if (val != null) { return PlayerStartedAttackAirborne(val); } return false; } private static float GetEffectiveFullAttackDurationSeconds(object? attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); float num = Mathf.Max(0.01f, GetConfig(category).GetFullAttackDuration(role)); float num2 = 1f; if (character is Player) { num2 = Mathf.Clamp(GetResolvedAttackAnimationSpeedMultiplierForContext(attackInstance, character, weapon, category, role), 0.05f, 5f); } if (num2 > 0.0001f) { num /= num2; } return Mathf.Max(0.01f, num); } private static void SetAttackState(Character character, WeaponCategory category, AttackRole role, object? attackInstance) { if ((Object)(object)character == (Object)null) { return; } category = ResolveAttackSettingsCategory(attackInstance, category, role); float effectiveFullAttackDurationSeconds = GetEffectiveFullAttackDurationSeconds(attackInstance, character, GetCurrentWeapon(character), category, role); bool attackStartedAirborne = GetAttackStartedAirborne(character, attackInstance); AttackStates[character] = new AttackRuntimeState(category, role, Time.time + effectiveFullAttackDurationSeconds, attackInstance, attackStartedAirborne); if (attackInstance != null) { AttackInstanceOwners[attackInstance] = character; AttackInstanceRoles[attackInstance] = role; AttackInstanceStartedAirborne[attackInstance] = attackStartedAirborne; ItemData currentWeapon = GetCurrentWeapon(character); if (currentWeapon != null) { AttackInstanceWeapons[attackInstance] = currentWeapon; } } if (Debug(DebugAttackLifecycle)) { int num = ((attackInstance != null) ? RuntimeHelpers.GetHashCode(attackInstance) : 0); DebugLogThrottled($"attack-state:{((Object)character).GetInstanceID()}:{num}:{category}:{role}", 0.05f, $"Attack state: {SafeName(character)} {category} {role}; attackHash={num}; startedAirborne={attackStartedAirborne}; fallback end {(Time.time + effectiveFullAttackDurationSeconds).ToString(CultureInfo.InvariantCulture)}."); } } private static void EndAttackStateForAttack(Character character, object? attackInstance) { if (!((Object)(object)character == (Object)null) && AttackStates.TryGetValue(character, out var value) && (value.AttackInstance == null || attackInstance == null || value.AttackInstance == attackInstance)) { AttackStates.Remove(character); } } private static AttackRole GetCurrentAttackRole(Character attacker, WeaponCategory category) { if ((Object)(object)attacker == (Object)null) { return AttackRole.Primary1; } if (TryGetCurrentHitAttackContext(attacker, category, out var context)) { return context.Role; } if (!AttackStates.TryGetValue(attacker, out var value)) { return AttackRole.Primary1; } object fieldValueRaw = GetFieldValueRaw(attacker, "m_currentAttack"); if (Time.time > value.EndTime || IsCharacterDead(attacker) || !CharacterReportsInAttack(attacker) || (value.AttackInstance != null && fieldValueRaw != null && value.AttackInstance != fieldValueRaw)) { CleanupAttackInstance(value.AttackInstance, attacker); return AttackRole.Primary1; } if (!SamePublicWeaponCategory(value.Category, category)) { return AttackRole.Primary1; } if (value.AttackInstance != null && fieldValueRaw != null && value.AttackInstance == fieldValueRaw && !RunningAttackInstances.Contains(value.AttackInstance) && !JumpAttackInstances.Contains(value.AttackInstance)) { object fieldValueRaw2 = GetFieldValueRaw(attacker, "m_currentAttackIsSecondary"); if (fieldValueRaw2 is bool) { if (!(bool)fieldValueRaw2) { return ResolvePrimaryChainRole(value.AttackInstance); } return AttackRole.Secondary; } } return value.Role; } private static bool IsInAttackState(Character character) { if ((Object)(object)character == (Object)null || !AttackStates.TryGetValue(character, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead(character)) { if (value.AttackInstance != null) { LungeHitboxTriggeredAttacks.Remove(value.AttackInstance); } AttackStates.Remove(character); return false; } return true; } private static AttackRole DetermineAttackRole(Character? character, object? attackInstance, ItemData? weapon) { if (attackInstance != null && SledgeAlternateMovesetAttacks.Contains(attackInstance) && AttackInstanceRoles.TryGetValue(attackInstance, out var value) && value == AttackRole.Secondary) { return AttackRole.Secondary; } if (attackInstance != null && (RunningAttackInstances.Contains(attackInstance) || JumpAttackInstances.Contains(attackInstance)) && AttackInstanceRoles.TryGetValue(attackInstance, out var value2)) { return value2; } if ((Object)(object)character != (Object)null && attackInstance != null) { object fieldValueRaw = GetFieldValueRaw(character, "m_currentAttack"); object fieldValueRaw2 = GetFieldValueRaw(character, "m_currentAttackIsSecondary"); if (fieldValueRaw != null && fieldValueRaw == attackInstance && fieldValueRaw2 is bool) { AttackRole attackRole = (((bool)fieldValueRaw2) ? AttackRole.Secondary : ResolvePrimaryChainRole(attackInstance)); AttackInstanceRoles[attackInstance] = attackRole; return attackRole; } } if ((Object)(object)character != (Object)null && attackInstance != null && PendingAttackRoles.TryGetValue(character, out var value3)) { if (!(Time.time > value3.EndTime)) { if (value3.Role != AttackRole.Secondary) { return ResolvePrimaryChainRole(attackInstance); } return AttackRole.Secondary; } PendingAttackRoles.Remove(character); } if (attackInstance != null && AttackInstanceRoles.TryGetValue(attackInstance, out var value4)) { return value4; } if ((Object)(object)character != (Object)null && AttackStates.TryGetValue(character, out var value5) && value5.AttackInstance != null && attackInstance != null && value5.AttackInstance == attackInstance) { return value5.Role; } return ResolvePrimaryChainRole(attackInstance); } private static AttackRole ResolvePrimaryChainRole(object? attackInstance) { int num = 0; object fieldValueRaw = GetFieldValueRaw(attackInstance, "m_currentAttackCainLevel"); int result; if (fieldValueRaw is int num2) { num = num2; } else if (fieldValueRaw != null && int.TryParse(fieldValueRaw.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) { num = result; } return num switch { 1 => AttackRole.Primary2, 2 => AttackRole.Primary3, 3 => AttackRole.Primary4, _ => AttackRole.Primary1, }; } private static void MarkCounterVulnerableDuringAttackAnimation(Character character, WeaponCategory category, AttackRole role, object? attackInstance) { if (!((Object)(object)character == (Object)null) && (!CounterVulnerableStates.TryGetValue(character, out var value) || value.AttackInstance != attackInstance)) { CounterVulnerableStates[character] = new CounterVulnerableState(attackInstance); if (Debug(DebugCounterDamage)) { DebugLogThrottled($"counter-begin:{((Object)character).GetInstanceID()}", 1f, $"Counter vulnerability began for {SafeName(character)} using {category} {role}; ends when Valheim's real InAttack() state ends."); } } } private static void EndCounterVulnerableForAttack(Character character, object? attackInstance) { if (!((Object)(object)character == (Object)null) && CounterVulnerableStates.TryGetValue(character, out var value) && (value.AttackInstance == null || attackInstance == null || value.AttackInstance == attackInstance)) { CounterVulnerableStates.Remove(character); if (Debug(DebugCounterDamage)) { DebugLogThrottled($"counter-end:{((Object)character).GetInstanceID()}", 1f, "Counter vulnerability ended for " + SafeName(character) + "."); } } } private static bool TryGetCounterVulnerable(Character target, out string rejectReason) { rejectReason = string.Empty; if ((Object)(object)target == (Object)null) { rejectReason = "victim is null"; return false; } if (!CounterVulnerableStates.TryGetValue(target, out var value)) { rejectReason = "victim is not in a tracked attack animation"; return false; } if (!AttackStates.TryGetValue(target, out var value2)) { CounterVulnerableStates.Remove(target); rejectReason = "victim has counter state but no tracked Attack runtime state"; return false; } if (value.AttackInstance != null && value2.AttackInstance != null && value.AttackInstance != value2.AttackInstance) { CounterVulnerableStates.Remove(target); rejectReason = "victim counter state does not match current tracked attack instance"; return false; } if (IsCharacterDead(target)) { CounterVulnerableStates.Remove(target); rejectReason = "victim is dead"; return false; } if (!CharacterReportsInAttack(target)) { CounterVulnerableStates.Remove(target); rejectReason = "victim's real Valheim InAttack() state has ended"; return false; } return true; } private static bool CharacterIsInAttackFast(Character target) { if ((Object)(object)target == (Object)null) { return false; } try { return target.InAttack(); } catch { return CharacterReportsInAttack(target); } } private static bool CharacterReportsInAttack(Character target) { if ((Object)(object)target == (Object)null) { return false; } try { MethodInfo methodInfo = GetCachedMethod(((object)target).GetType(), "InAttack"); if ((object)CharacterInAttackMethod == null) { CharacterInAttackMethod = AccessTools.Method(typeof(Character), "InAttack", (Type[])null, (Type[])null); } if ((object)methodInfo == null) { methodInfo = CharacterInAttackMethod; } if (methodInfo == null) { return true; } return !(methodInfo.Invoke(target, null) is bool flag) || flag; } catch { return true; } } private static bool IsCounterDamageBonusLikelyEligible(Character victim, Character? attacker, HitData hit) { if (EnableCounterDamage == null || !EnableCounterDamage.Value || (Object)(object)victim == (Object)null || (Object)(object)attacker == (Object)null || (Object)(object)attacker == (Object)(object)victim || hit == null) { return false; } bool flag = attacker is Player; if (OnlyPlayersCanDealCounterDamage != null && OnlyPlayersCanDealCounterDamage.Value && !flag) { return false; } if (!TryGetCounterVulnerable(victim, out string _)) { return false; } ItemData currentWeapon = GetCurrentWeapon(attacker); if (currentWeapon == null) { return false; } WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); if (!IsMeleeWeaponCategory(category)) { return false; } AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); object obj = GetFieldValueRaw(attacker, "m_currentAttack"); if (obj == null && AttackStates.TryGetValue(attacker, out var value)) { obj = value.AttackInstance; } if (!GetEffectiveCounterDamageEnabled(obj, category, currentAttackRole)) { return false; } float num = Mathf.Max(0f, GetEffectiveCounterDamageMultiplier(obj, category, currentAttackRole)); if (attacker is Player && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { num *= Mathf.Clamp(GetEffectivePvpCounterDamageMultiplier(obj, category, currentAttackRole), 0f, 10f); } num *= Mathf.Clamp((GlobalCounterDamageMultiplier != null) ? GlobalCounterDamageMultiplier.Value : 1f, 0f, 10f); return !Mathf.Approximately(num, 1f); } private static void PlayConditionalDamageDing(Character victim, Character? attacker, HitData hit, string dingKey) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)victim == (Object)null || hit == null || string.IsNullOrEmpty(dingKey)) { return; } bool flag = string.Equals(dingKey, "counter", StringComparison.OrdinalIgnoreCase); if (flag && DamageBonusStackStates.TryGetValue(hit, out DamageBonusStackState value) && (value.Backstab || value.StaggeredEnemy)) { return; } CombatDingState value2 = CombatDingStates.GetValue(hit, (HitData _) => new CombatDingState()); if (value2.FlankingDingPlayed || value2.CounterDamageDingPlayed) { return; } object obj = (((Object)(object)attacker != (Object)null) ? GetFieldValueRaw(attacker, "m_critHitEffects") : null); if (obj == null) { obj = GetFieldValueRaw(victim, "m_critHitEffects"); } if (obj == null) { return; } Vector3 val = hit.m_point; if (float.IsNaN(val.x) || float.IsNaN(val.y) || float.IsNaN(val.z) || float.IsInfinity(val.x) || float.IsInfinity(val.y) || float.IsInfinity(val.z) || ((Vector3)(ref val)).sqrMagnitude < 0.0001f) { try { val = victim.GetCenterPoint(); } catch { val = ((Component)victim).transform.position; } } MethodInfo cachedMethod = GetCachedMethod(obj.GetType(), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) }); if (cachedMethod == null) { return; } try { cachedMethod.Invoke(obj, new object[5] { val, Quaternion.identity, null, 1f, -1 }); if (flag) { value2.CounterDamageDingPlayed = true; } else { value2.FlankingDingPlayed = true; } } catch (Exception ex) { if (Debug(DebugAttackLifecycle)) { DebugLogThrottled("conditional-ding-failed:" + dingKey, 2f, "GCO conditional ding effect failed: " + ex.GetType().Name + ": " + ex.Message); } } } private static void ApplyCounterDamageIfEligible(Character victim, Character? attacker, HitData hit) { if (!EnableCounterDamage.Value || (Object)(object)victim == (Object)null || (Object)(object)attacker == (Object)null || (Object)(object)attacker == (Object)(object)victim || hit == null) { return; } bool flag = attacker is Player; if (OnlyPlayersCanDealCounterDamage.Value && !flag) { return; } string rejectReason; bool flag2 = TryGetCounterVulnerable(victim, out rejectReason); bool flag3 = IsInAttackState(victim); if (!flag2) { DebugCounterDamageReject(attacker, victim, $"{rejectReason}; victimInAttackState={flag3}"); return; } ItemData currentWeapon = GetCurrentWeapon(attacker); if (currentWeapon == null) { DebugCounterDamageReject(attacker, victim, "attacker has no current weapon"); return; } WeaponCategory weaponCategory = ResolveActiveAttackCategory(attacker, currentWeapon); if (!IsMeleeWeaponCategory(weaponCategory)) { DebugCounterDamageReject(attacker, victim, $"attacker weapon category {weaponCategory} is not melee"); return; } AttackRole currentAttackRole = GetCurrentAttackRole(attacker, weaponCategory); object obj = GetFieldValueRaw(attacker, "m_currentAttack"); if (obj == null && AttackStates.TryGetValue(attacker, out var value)) { obj = value.AttackInstance; } if (!GetEffectiveCounterDamageEnabled(obj, weaponCategory, currentAttackRole)) { DebugCounterDamageReject(attacker, victim, $"counter damage disabled for {weaponCategory} {currentAttackRole}; victimCounterVulnerable={flag2}, victimInAttackState={flag3}"); return; } float num = Mathf.Max(0f, GetEffectiveCounterDamageMultiplier(obj, weaponCategory, currentAttackRole)); if (attacker is Player && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { num *= Mathf.Clamp(GetEffectivePvpCounterDamageMultiplier(obj, weaponCategory, currentAttackRole), 0f, 10f); } num *= Mathf.Clamp(GlobalCounterDamageMultiplier.Value, 0f, 10f); if (Mathf.Approximately(num, 1f)) { DebugCounterDamageReject(attacker, victim, $"counter multiplier for {weaponCategory} {currentAttackRole} is 1"); return; } if (!ShouldApplyStackableDamageBonus(hit, DamageBonusKind.CounterDamage, CounterDamageStackableWithOtherBonuses != null && CounterDamageStackableWithOtherBonuses.Value)) { DebugCounterDamageReject(attacker, victim, "counter-damage bonus is configured not to stack with another active conditional damage bonus"); return; } ((DamageTypes)(ref hit.m_damage)).Modify(num); if (CounterDamageDing != null && CounterDamageDing.Value) { PlayConditionalDamageDing(victim, attacker, hit, "counter"); } if (Debug(DebugCounterDamage)) { Log.LogInfo((object)$"Applied counter-damage x{num.ToString(CultureInfo.InvariantCulture)}: {SafeName(attacker)} -> {SafeName(victim)} with {weaponCategory} {currentAttackRole}."); } } private static void DebugCounterDamageReject(Character attacker, Character victim, string reason) { if (Debug(DebugCounterDamage) && attacker is Player) { DebugLogThrottled("counter-reject:" + SafeName(attacker) + ":" + SafeName(victim) + ":" + reason, 1.5f, "Counter-damage rejected: " + reason + ". attacker=" + SafeName(attacker) + ", victim=" + SafeName(victim) + "."); } } private static IEnumerable GetAllInstanceFields(Type type) { Type t = type; while (t != null) { FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { yield return fields[i]; } t = t.BaseType; } } private static bool TryStartJumpAttack(object attackInstance, object[] args, Player player, WeaponCategory category, ItemData? weapon) { if (attackInstance == null || (Object)(object)player == (Object)null) { return false; } if (!TryGetJumpAttackConfig(category, out JumpAttackConfig config) || !config.Enabled.Value) { return false; } if (PendingAttackRoles.TryGetValue((Character)(object)player, out var value) && value.Role == AttackRole.Secondary && Time.time <= value.EndTime) { return false; } if (!PlayerStartedAttackAirborne(player)) { return false; } if (!(AttackInstanceStartedWhileAlreadyInAttack.TryGetValue(attackInstance, out var value2) && value2) && BetterFreeAimCanRun(player)) { TryApplyBetterFreeAimSnap(player); BetterFreeAimPreAppliedAttacks.Add(attackInstance); } JumpAttackUsed jumpAttackUsed = NormalizeJumpAttackUsed(category, config.UsedAttack.Value); if (jumpAttackUsed == JumpAttackUsed.Secondary) { if (!TryCopySecondaryAttackDataOntoAttackInstance(attackInstance, weapon)) { return false; } AttackInstanceRoles[attackInstance] = AttackRole.Secondary; JumpAttackInstances.Add(attackInstance); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"jump-attack:{((Object)player).GetInstanceID()}:{category}", 0.5f, $"Jump attack detected for {SafeName((Character)(object)player)} using {category}; using secondary attack moveset."); } return true; } int privateInt = GetPrivateInt(attackInstance, "m_attackChainLevels", 0); int num = JumpAttackUsedToChainLevel(jumpAttackUsed); if (num < 0 || privateInt <= num) { return false; } SetForcedPrimaryChainLevel(attackInstance, args, num); AttackRole value3 = RoleForForcedPrimaryChainLevel(num); AttackInstanceRoles[attackInstance] = value3; JumpAttackForcedChainLevels[attackInstance] = num; JumpAttackInstances.Add(attackInstance); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"jump-attack:{((Object)player).GetInstanceID()}:{category}", 0.5f, $"Jump attack detected for {SafeName((Character)(object)player)} using {category}; using {jumpAttackUsed}, forcing chain level {num} (primary chain {num + 1})."); } return true; } private static bool TryCopySecondaryAttackDataOntoAttackInstance(object attackInstance, ItemData? weapon) { if (attackInstance == null || weapon == null) { return false; } object secondaryAttack = weapon.m_shared.m_secondaryAttack; if (secondaryAttack == null) { return false; } if (string.IsNullOrEmpty(GetFieldValue(secondaryAttack, "m_attackAnimation") ?? string.Empty)) { return false; } return TryCopyAttackFields(attackInstance, secondaryAttack); } private static int JumpAttackUsedToChainLevel(JumpAttackUsed used) { return used switch { JumpAttackUsed.Primary1 => 0, JumpAttackUsed.Primary2 => 1, JumpAttackUsed.Primary3 => 2, JumpAttackUsed.Primary4 => 3, _ => -1, }; } private static void SetForcedPrimaryChainLevel(object attackInstance, object[]? args, int forcedChainLevel) { if (attackInstance == null || forcedChainLevel < 0) { return; } SetFieldValueRaw(attackInstance, "m_currentAttackCainLevel", forcedChainLevel); if (args != null) { if (args.Length > 6) { args[6] = null; } if (args.Length > 7) { args[7] = 0f; } } } private static void ApplyForcedChainPostStart(object attackInstance) { if (attackInstance == null) { return; } string arg; if (JumpAttackForcedChainLevels.TryGetValue(attackInstance, out var value)) { arg = "jump attack"; } else { if (!RunningAttackForcedChainLevels.TryGetValue(attackInstance, out value)) { return; } arg = "running attack"; } if (GetPrivateInt(attackInstance, "m_currentAttackCainLevel", -1) == value) { return; } SetFieldValueRaw(attackInstance, "m_currentAttackCainLevel", value); SetFieldValueRaw(attackInstance, "m_nextAttackChainLevel", value + 1); string text = GetFieldValue(attackInstance, "m_attackAnimation") ?? string.Empty; ZSyncAnimation fieldValue = GetFieldValue(attackInstance, "m_zanim"); if (!((Object)(object)fieldValue != (Object)null) || string.IsNullOrEmpty(text)) { return; } try { fieldValue.SetTrigger(text + value); if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"forced-chain-retrigger:{attackInstance.GetHashCode()}", 0.5f, $"{arg} post-start corrected chain animation to {text}{value}."); } } catch { } } private static bool TryGetRunningAttackConfig(WeaponCategory category, out RunningAttackConfig config) { return RunningAttackConfigs.TryGetValue(category, out config); } private static bool TryApplyRunningAttackSelection(object attackInstance, object[] args, WeaponCategory category, ItemData? weapon, RunningAttackConfig cfg, out string forcedText) { forcedText = ""; if (attackInstance == null || cfg == null) { return false; } RunningAttackUsed value = cfg.UsedAttack.Value; if (value == RunningAttackUsed.Secondary) { if (!TryCopySecondaryAttackDataOntoAttackInstance(attackInstance, weapon)) { return false; } AttackInstanceRoles[attackInstance] = AttackRole.Secondary; forcedText = "using secondary attack moveset"; return true; } int num = RunningAttackUsedToChainLevel(value); if (num < 0) { return false; } if (GetPrivateInt(attackInstance, "m_attackChainLevels", 0) <= num) { return false; } SetForcedPrimaryChainLevel(attackInstance, args, num); AttackInstanceRoles[attackInstance] = RoleForForcedPrimaryChainLevel(num); RunningAttackForcedChainLevels[attackInstance] = num; forcedText = $"using {value}, forcing chain level {num} (primary chain {num + 1})"; return true; } private static int RunningAttackUsedToChainLevel(RunningAttackUsed used) { return used switch { RunningAttackUsed.Primary1 => 0, RunningAttackUsed.Primary2 => 1, RunningAttackUsed.Primary3 => 2, _ => -1, }; } private static RunningAttackUsed DefaultRunningAttackUsed(WeaponCategory category) { return category switch { WeaponCategory.OneHandedAxe => RunningAttackUsed.Primary3, WeaponCategory.OneHandedSword => RunningAttackUsed.Primary3, WeaponCategory.TwoHandedSword => RunningAttackUsed.Primary3, WeaponCategory.Club => RunningAttackUsed.Primary3, WeaponCategory.Knife => RunningAttackUsed.Primary3, WeaponCategory.DualKnives => RunningAttackUsed.Primary3, _ => RunningAttackUsed.Primary2, }; } private static AttackRole RoleFromRunningAttackUsed(RunningAttackUsed used) { return used switch { RunningAttackUsed.Primary2 => AttackRole.Primary2, RunningAttackUsed.Primary3 => AttackRole.Primary3, RunningAttackUsed.Secondary => AttackRole.Secondary, _ => AttackRole.Primary1, }; } private JumpAttackUsedSetting BindJumpAttackUsedConfig(WeaponCategory category, string section) { string description = "Which underlying attack moveset this category uses for its special jump attack. Primary options force that primary-chain attack; Secondary copies the weapon's secondary attack data when available. Primary4 is a normal option for Dual Axes and a modded-slot option for other melee categories."; ConfigEntry fullEntry = BindConfig(section, "JumpAttackUsed", DefaultJumpAttackUsed(category), description); return new JumpAttackUsedSetting(category, fullEntry); } private static JumpAttackUsed ToFullJumpAttackUsed(JumpAttackUsedBasic used) { return used switch { JumpAttackUsedBasic.Primary2 => JumpAttackUsed.Primary2, JumpAttackUsedBasic.Primary3 => JumpAttackUsed.Primary3, JumpAttackUsedBasic.Secondary => JumpAttackUsed.Secondary, _ => JumpAttackUsed.Primary1, }; } private static JumpAttackUsedBasic ToBasicJumpAttackUsed(JumpAttackUsed used) { return used switch { JumpAttackUsed.Primary2 => JumpAttackUsedBasic.Primary2, JumpAttackUsed.Primary3 => JumpAttackUsedBasic.Primary3, JumpAttackUsed.Secondary => JumpAttackUsedBasic.Secondary, _ => JumpAttackUsedBasic.Primary1, }; } private static JumpAttackUsed NormalizeJumpAttackUsed(WeaponCategory category, JumpAttackUsed used) { if (used != JumpAttackUsed.Primary4 || !IsMinimalRangedConfigCategory(category)) { return used; } return DefaultJumpAttackUsed(category); } private static JumpAttackUsed DefaultJumpAttackUsed(WeaponCategory category) { return category switch { WeaponCategory.OneHandedAxe => JumpAttackUsed.Secondary, WeaponCategory.TwoHandedAxe => JumpAttackUsed.Primary3, WeaponCategory.DualAxes => JumpAttackUsed.Primary4, _ => JumpAttackUsed.Primary3, }; } private static AttackRole RoleFromJumpAttackUsed(JumpAttackUsed used) { return used switch { JumpAttackUsed.Primary2 => AttackRole.Primary2, JumpAttackUsed.Primary3 => AttackRole.Primary3, JumpAttackUsed.Primary4 => AttackRole.Primary4, JumpAttackUsed.Secondary => AttackRole.Secondary, _ => AttackRole.Primary1, }; } private static AttackRole DefaultJumpAttackRole(WeaponCategory category) { return RoleFromJumpAttackUsed(DefaultJumpAttackUsed(category)); } private static float DefaultPvpStaggerDuration(WeaponCategory category, AttackRole role) { switch (category) { case WeaponCategory.Bow: case WeaponCategory.Crossbow: case WeaponCategory.Magic: return 0f; case WeaponCategory.Knife: if (role != AttackRole.Secondary) { return 0.33f; } return 0.83f; case WeaponCategory.DualAxes: if (role != AttackRole.Secondary) { return 0.33f; } return 0.83f; case WeaponCategory.OneHandedSword: case WeaponCategory.Club: if (role != AttackRole.Secondary) { return 0.83f; } return 1.17f; case WeaponCategory.OneHandedAxe: if (role != AttackRole.Secondary) { return 0.83f; } return 1.17f; case WeaponCategory.Atgeir: case WeaponCategory.Spear: if (role != AttackRole.Secondary) { return 0.83f; } return 1.17f; case WeaponCategory.TwoHandedAxe: if (role != AttackRole.Secondary) { return 1.17f; } return 0.83f; case WeaponCategory.TwoHandedSword: case WeaponCategory.Sledge: return 1.17f; case WeaponCategory.Unarmed: if (role != AttackRole.Secondary) { return 0.83f; } return 1.17f; case WeaponCategory.Pickaxe: case WeaponCategory.Other: return 0.83f; default: return 0.83f; } } private static float DefaultPvpStaggerPowerMultiplier(WeaponCategory category, AttackRole role) { float num = DefaultPvpStaggerDuration(category, role); if (num <= 0f) { return 0f; } if (num >= 1.16f) { return 20f; } if (num >= 0.82f) { return 10f; } return 5f; } private static float DefaultGooPvpBlockArmorMultiplier(WeaponCategory category) { return 1f; } private static AttackRole RoleForForcedPrimaryChainLevel(int forcedChainLevel) { if (forcedChainLevel <= 0) { return AttackRole.Primary1; } return forcedChainLevel switch { 1 => AttackRole.Primary2, 2 => AttackRole.Primary3, _ => AttackRole.Primary4, }; } private static bool TryGetRunningAttackConfigForPlayer(Player player, WeaponCategory category, out RunningAttackConfig cfg) { cfg = null; if ((Object)(object)player == (Object)null) { return false; } if (TryGetCurrentHitAttackContext((Character?)(object)player, category, out var context) && context.AttackInstance != null && RunningAttackInstances.Contains(context.AttackInstance)) { return TryGetRunningAttackConfig(context.Category, out cfg); } if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.AttackInstance != null && Time.time <= value.EndTime && SamePublicWeaponCategory(value.Category, category) && RunningAttackInstances.Contains(value.AttackInstance)) { return TryGetRunningAttackConfig(value.Category, out cfg); } return false; } private static bool TryGetJumpAttackConfig(WeaponCategory category, out JumpAttackConfig config) { return JumpAttackConfigs.TryGetValue(category, out config); } private static bool TryGetJumpAttackConfigForPlayer(Player player, WeaponCategory category, out JumpAttackConfig cfg) { cfg = null; if ((Object)(object)player == (Object)null) { return false; } if (TryGetCurrentHitAttackContext((Character?)(object)player, category, out var context) && context.AttackInstance != null && JumpAttackInstances.Contains(context.AttackInstance)) { return TryGetJumpAttackConfig(context.Category, out cfg); } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.AttackInstance != null && fieldValueRaw != null && SamePublicWeaponCategory(value.Category, category) && value.AttackInstance == fieldValueRaw && JumpAttackInstances.Contains(value.AttackInstance)) { return TryGetJumpAttackConfig(value.Category, out cfg); } return false; } private static bool TryGetJumpAttackDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.DamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackChopDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.ChopDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackPickaxeDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PickaxeDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackBackstabDamageMultiplier(Player player, WeaponCategory category, out float rate) { rate = DefaultVanillaBackstabDamageMultiplier(category); if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } rate = Mathf.Clamp(cfg.BackstabDamageMultiplier.Value, 1f, 20f); return true; } private static bool TryGetJumpAttackStaggerMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Staggering) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.StaggerMultiplier.Value, 0f, 999f); return true; } private static bool TryGetJumpAttackAnimationSpeedMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.AnimationSpeedMultiplier.Value, 0.05f, 5f); return true; } private static bool TryGetJumpAttackRecoveryAnimationSpeedMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.RecoveryAnimationSpeedMultiplier.Value, 0.05f, 5f); return true; } private static bool TryGetJumpAttackRotationFactor(Player player, WeaponCategory category, out float factor) { factor = -1f; if (!IsFeatureApplied(category, ModFeature.AttackRotation) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } factor = Mathf.Clamp(cfg.RotationFactor.Value, 0f, 999f); return true; } private static bool TryGetJumpAttackPvpDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackPvpStaggerMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpStaggerMultiplier.Value, 0f, 9999f); return true; } private static bool TryGetJumpAttackPvpKnockbackForceMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpKnockbackForceMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackPvpStaggerDuration(Player player, WeaponCategory category, out float duration) { duration = DefaultPvpStaggerDuration(category, DefaultJumpAttackRole(category)); if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } duration = Mathf.Clamp(cfg.PvpStaggerDurationSeconds.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackKnockbackForceMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.KnockbackForceMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackAdrenalineMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Resources) || !TryGetJumpAttackConfigForPlayer(player, category, out JumpAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.AdrenalineMultiplier.Value, 0f, 10f); return true; } private static bool TryGetJumpAttackLungeSettings(Character character, WeaponCategory category, object? attackInstance, out float multiplier, out LungeApplicationMode mode) { multiplier = 1f; mode = LungeApplicationMode.BeforeHitbox; if (!(character is Player) || attackInstance == null) { return false; } if (!JumpAttackInstances.Contains(attackInstance)) { return false; } if (!IsFeatureApplied(category, ModFeature.AttackMovement)) { return false; } if (!TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return false; } multiplier = Mathf.Clamp(config.LungeDistanceMultiplier.Value, 0f, 5f); mode = config.LungeApplicationMode.Value; return true; } private static bool TryGetRunningAttackDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.DamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackChopDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.ChopDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackPickaxeDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PickaxeDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackBackstabDamageMultiplier(Player player, WeaponCategory category, out float rate) { rate = DefaultVanillaBackstabDamageMultiplier(category); if (!IsFeatureApplied(category, ModFeature.Damage) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } rate = Mathf.Clamp(cfg.BackstabDamageMultiplier.Value, 1f, 20f); return true; } private static bool TryGetRunningAttackStaggerMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Staggering) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.StaggerMultiplier.Value, 0f, 999f); return true; } private static bool TryGetRunningAttackAnimationSpeedMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.AnimationSpeedMultiplier.Value, 0.05f, 5f); return true; } private static bool TryGetRunningAttackRecoveryAnimationSpeedMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.RecoveryAnimationSpeedMultiplier.Value, 0.05f, 5f); return true; } private static bool TryGetRunningAttackRotationFactor(Player player, WeaponCategory category, out float factor) { factor = -1f; if (!IsFeatureApplied(category, ModFeature.AttackRotation) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } factor = Mathf.Clamp(cfg.RotationFactor.Value, -1f, 999f); return factor >= 0f; } private static bool TryGetRunningAttackPvpDamageMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpDamageMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackPvpStaggerMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpStaggerMultiplier.Value, 0f, 9999f); return true; } private static bool TryGetRunningAttackPvpKnockbackForceMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.PvpKnockbackForceMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackPvpStaggerDuration(Player player, WeaponCategory category, out float duration) { duration = DefaultPvpStaggerDuration(category, AttackRole.Primary1); if (!IsFeatureApplied(category, ModFeature.Pvp) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } duration = Mathf.Clamp(cfg.PvpStaggerDurationSeconds.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackKnockbackForceMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.KnockbackForceMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackAdrenalineMultiplier(Player player, WeaponCategory category, out float multiplier) { multiplier = 1f; if (!IsFeatureApplied(category, ModFeature.Resources) || !TryGetRunningAttackConfigForPlayer(player, category, out RunningAttackConfig cfg)) { return false; } multiplier = Mathf.Clamp(cfg.AdrenalineMultiplier.Value, 0f, 10f); return true; } private static bool TryGetRunningAttackLungeSettings(Character character, WeaponCategory category, object? attackInstance, out float multiplier, out LungeApplicationMode mode) { multiplier = 1f; mode = LungeApplicationMode.BeforeHitbox; if (!(character is Player) || attackInstance == null) { return false; } if (!RunningAttackInstances.Contains(attackInstance)) { return false; } if (!IsFeatureApplied(category, ModFeature.AttackMovement)) { return false; } if (!TryGetRunningAttackConfig(category, out RunningAttackConfig config)) { return false; } multiplier = Mathf.Clamp(config.LungeDistanceMultiplier.Value, 0f, 5f); mode = config.LungeApplicationMode.Value; return true; } private static void MarkLungeHitboxTriggered(object? attackInstance) { if (attackInstance != null) { LungeHitboxTriggeredAttacks.Add(attackInstance); } } private static bool PlayerIsActuallyRunning(Player player) { if ((Object)(object)player == (Object)null) { return false; } MethodInfo methodInfo = GetCachedMethod(((object)player).GetType(), "IsRunning") ?? GetCachedMethod(typeof(Character), "IsRunning"); bool flag = false; try { object obj = methodInfo?.Invoke(player, Array.Empty()); bool flag2 = default(bool); int num; if (obj is bool) { flag2 = (bool)obj; num = 1; } else { num = 0; } flag = (byte)((uint)num & (flag2 ? 1u : 0u)) != 0; } catch { flag = false; } if (!flag) { return false; } return true; } private static float GetPlayerVelocityMagnitude(Player player) { //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_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } Vector3 val = Vector3.zero; MethodInfo methodInfo = GetCachedMethod(((object)player).GetType(), "GetVelocity") ?? GetCachedMethod(typeof(Character), "GetVelocity"); try { if (methodInfo?.Invoke(player, Array.Empty()) is Vector3 val2) { val = val2; } } catch { val = Vector3.zero; } val.y = 0f; return ((Vector3)(ref val)).magnitude; } private static bool PlayerMeetsRunningAttackVelocity(Player player, float minimumVelocity) { //IL_0078: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return false; } minimumVelocity = Mathf.Max(0f, minimumVelocity); if (minimumVelocity <= 0f) { return true; } Vector3 val = Vector3.zero; MethodInfo methodInfo = GetCachedMethod(((object)player).GetType(), "GetVelocity") ?? GetCachedMethod(typeof(Character), "GetVelocity"); try { if (methodInfo?.Invoke(player, Array.Empty()) is Vector3 val2) { val = val2; } } catch { val = Vector3.zero; } val.y = 0f; return ((Vector3)(ref val)).magnitude >= minimumVelocity; } private static bool IsMeleeWeaponCategory(WeaponCategory category) { if (category != WeaponCategory.Bow && category != WeaponCategory.Crossbow) { return category != WeaponCategory.Magic; } return false; } private static bool IsAirborneBonusWeaponCategory(WeaponCategory category) { if (IsMeleeWeaponCategory(category)) { return category != WeaponCategory.Other; } return false; } private static bool PlayerIsSwimming(Player player) { if ((Object)(object)player == (Object)null) { return false; } return ((Character)player).IsSwimming(); } private static float GetPlayerAirborneTimeAtAttackStart(Player player) { if ((Object)(object)player == (Object)null || PlayerIsSwimming(player)) { return 0f; } FieldInfo fieldInfo = GetCachedField(typeof(Character), "m_lastGroundTouch") ?? GetCachedField(((object)player).GetType(), "m_lastGroundTouch"); if (fieldInfo == null) { if (!((Character)player).IsOnGround()) { return 999f; } return 0f; } try { if (fieldInfo.GetValue(player) is float num) { return Mathf.Max(0f, num); } } catch { } if (!((Character)player).IsOnGround()) { return 999f; } return 0f; } private static bool PlayerStartedAttackAirborne(Player player) { if ((Object)(object)player == (Object)null) { return false; } if (((Character)player).IsOnGround()) { return false; } if (PlayerIsSwimming(player)) { return false; } float num = ((AirborneAttackMinimumAirTimeSeconds != null) ? Mathf.Max(0f, AirborneAttackMinimumAirTimeSeconds.Value) : 1f); return GetPlayerAirborneTimeAtAttackStart(player) >= num; } private static bool TryGetStaffConfig(ItemData? item, out StaffConfig cfg) { cfg = null; if (item?.m_shared == null) { return false; } string text = SafeLower(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty); string text2 = SafeLower(item.m_shared.m_name); string text3 = SafeLower(item.m_shared.m_name + " " + ((object)Unsafe.As(ref item.m_shared.m_skillType)/*cast due to .constrained prefix*/).ToString() + " " + ((object)Unsafe.As(ref item.m_shared.m_animationState)/*cast due to .constrained prefix*/).ToString()); foreach (KeyValuePair staffConfig in StaffConfigs) { string value = SafeLower(staffConfig.Key); if (text.Contains(value) || text2.Contains(value) || text3.Contains(value)) { cfg = staffConfig.Value; return true; } } return false; } private static bool TryGetAttackerStaffConfig(Character? attacker, out StaffConfig cfg) { cfg = null; if (!(attacker is Player)) { return false; } return TryGetStaffConfig(GetCurrentWeapon(attacker), out cfg); } private static bool IsStaffGreenRoots(StaffConfig cfg) { if (cfg != null) { return string.Equals(cfg.PrefabName, "StaffGreenRoots", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsStaffSkeleton(StaffConfig cfg) { if (cfg != null) { return string.Equals(cfg.PrefabName, "StaffSkeleton", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsStaffLightning(StaffConfig cfg) { if (cfg != null) { return string.Equals(cfg.PrefabName, "StaffLightning", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsStaffIceShardsPrefabName(string prefabName) { return string.Equals(prefabName, "StaffIceShards", StringComparison.OrdinalIgnoreCase); } private static bool IsStaffShield(StaffConfig cfg) { if (cfg != null) { return string.Equals(cfg.PrefabName, "StaffShield", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsAbyssalHarpoon(ItemData? item) { if (item?.m_shared == null) { return false; } string text = SafeLower(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty); string text2 = SafeLower(item.m_shared.m_name); return (text + " " + text2).Contains("harpoon"); } private static bool TryGetTooltipBackstabDamageMultiplier(ItemData item, WeaponCategory category, out float multiplier) { multiplier = 1f; if (item?.m_shared == null || IsAbyssalHarpoon(item)) { return false; } if (TryGetStaffConfig(item, out StaffConfig cfg)) { multiplier = GetConfiguredStaffBackstabDamageMultiplier(cfg, Player.m_localPlayer); return true; } if (!IsBackstabConfigCategory(category)) { return false; } multiplier = GetConfiguredBackstabDamageMultiplier(Player.m_localPlayer, item, category, AttackRole.Primary1); return true; } private static float GetConfiguredStaffBackstabDamageMultiplier(StaffConfig cfg, Player? player = null) { if (cfg == null) { return 1f; } float num = ((GlobalSneakDamageMultiplier != null) ? Mathf.Clamp(GlobalSneakDamageMultiplier.Value, 0f, 20f) : 1f); float num2 = Mathf.Clamp(cfg.BackstabDamageMultiplier.Value, 0f, 20f); float sneakBackstabSkillMultiplier = GetSneakBackstabSkillMultiplier(player); return Mathf.Clamp(num * num2 * sneakBackstabSkillMultiplier, 1f, 100f); } private static float GetSneakBackstabSkillMultiplier(Player? player) { if ((Object)(object)player == (Object)null || SneakDamageScalesWithSneakLevel == null || !SneakDamageScalesWithSneakLevel.Value) { return 1f; } float num = ((SneakDamageScaleBaseMultiplier != null) ? Mathf.Clamp(SneakDamageScaleBaseMultiplier.Value, 0f, 20f) : 1f); float num2 = ((SneakDamageMultiplierPerSneakLevel != null) ? Mathf.Clamp(SneakDamageMultiplierPerSneakLevel.Value, -1f, 1f) : 0f); float num3 = 0f; try { num3 = Mathf.Clamp(((Character)player).GetSkillLevel((SkillType)101), 0f, 100f); } catch { } return Mathf.Clamp(num + num2 * num3, 0f, 100f); } private static float GetConfiguredBackstabDamageMultiplier(Player? player, ItemData? weapon, WeaponCategory category, AttackRole role) { if (weapon != null && IsAbyssalHarpoon(weapon)) { return Mathf.Clamp((weapon.m_shared != null) ? weapon.m_shared.m_backstabBonus : 1f, 1f, 100f); } if (TryGetStaffConfig(weapon, out StaffConfig cfg)) { return GetConfiguredStaffBackstabDamageMultiplier(cfg, player); } if (!IsBackstabConfigCategory(category) || !IsFeatureApplied(category, ModFeature.StealthAttributes)) { return DefaultVanillaBackstabDamageMultiplier(category); } float num = GetRoleMultiplier(BackstabDamageMultipliers, category, role, DefaultGooBackstabDamageMultiplier(category)); float rate2; if ((Object)(object)player != (Object)null && TryGetJumpAttackBackstabDamageMultiplier(player, category, out var rate)) { num = rate; } else if ((Object)(object)player != (Object)null && TryGetRunningAttackBackstabDamageMultiplier(player, category, out rate2)) { num = rate2; } float num2 = ((GlobalSneakDamageMultiplier != null) ? Mathf.Clamp(GlobalSneakDamageMultiplier.Value, 0f, 20f) : 1f); float sneakBackstabSkillMultiplier = GetSneakBackstabSkillMultiplier(player); return Mathf.Clamp(num2 * Mathf.Clamp(num, 0f, 20f) * sneakBackstabSkillMultiplier, 1f, 100f); } private static void ApplyBackstabDamageMultiplierIfConfigured(Character? attacker, HitData hit) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || hit == null || hit.m_ranged) { return; } ItemData currentWeapon = GetCurrentWeapon(attacker); if (currentWeapon != null && IsAbyssalHarpoon(currentWeapon)) { return; } WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); if (IsBackstabConfigCategory(category) || TryGetStaffConfig(currentWeapon, out StaffConfig _)) { AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, category); float num = GetConfiguredBackstabDamageMultiplier(val, currentWeapon, category, currentAttackRole); if (!ShouldApplyStackableDamageBonus(hit, DamageBonusKind.Backstab, BackstabDamageStackableWithOtherBonuses != null && BackstabDamageStackableWithOtherBonuses.Value)) { num = 1f; } SetFieldValueRaw(hit, "m_backstabBonus", num); } } private static void ApplyProjectileBackstabDamageMultiplierIfConfigured(object projectileInstance, Character? owner, HitData? originalHitData, ItemData? weapon) { if (projectileInstance == null) { return; } Player val = (Player)(object)((owner is Player) ? owner : null); if (val == null || weapon?.m_shared == null || IsAbyssalHarpoon(weapon)) { return; } WeaponCategory category = ClassifyWeapon(weapon); if (IsBackstabConfigCategory(category) || TryGetStaffConfig(weapon, out StaffConfig _)) { AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, category); float configuredBackstabDamageMultiplier = GetConfiguredBackstabDamageMultiplier(val, weapon, category, currentAttackRole); SetFieldValueRaw(projectileInstance, "m_backstabBonus", configuredBackstabDamageMultiplier); if (originalHitData != null) { SetFieldValueRaw(originalHitData, "m_backstabBonus", configuredBackstabDamageMultiplier); } } } private static void ApplyStaffShieldActiveMitigationIfNeeded(Character victim, Character? attacker, HitData hit) { if (ModActive && !((Object)(object)victim == (Object)null) && hit != null && StaffShieldActiveSettings != null && HasStaffShieldActive(victim)) { float num = Mathf.Clamp(StaffShieldActiveSettings.KnockbackMitigationMultiplier.Value, 0f, 20f); if (attacker is Player && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { num *= Mathf.Clamp(StaffShieldActiveSettings.PvpKnockbackMitigationMultiplier.Value, 0f, 20f); } if (!Mathf.Approximately(num, 1f)) { hit.m_pushForce *= num; } } } private static bool HasStaffShieldActive(Character character) { if ((Object)(object)character == (Object)null) { return false; } object fieldValueRaw = GetFieldValueRaw(character, "m_seman"); if (fieldValueRaw == null) { return false; } if (GetFieldValueRaw(fieldValueRaw, "m_statusEffects") is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null && IsStaffShieldStatusEffect(item)) { return true; } } } return false; } private static WeaponCategory ResolveActiveAttackCategory(Character attacker, ItemData? weapon) { WeaponCategory result = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); if ((Object)(object)attacker == (Object)null) { return result; } try { if (AttackStates.TryGetValue(attacker, out var value)) { object fieldValueRaw = GetFieldValueRaw(attacker, "m_currentAttack"); if (value.AttackInstance != null && fieldValueRaw != null && value.AttackInstance == fieldValueRaw) { return value.Category; } } } catch { } return result; } private static void ApplyAlwaysMaxDamageInPvpIfNeeded(Character victim, Character? attacker, HitData hit) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || AlwaysDoMaxDamageInPvp == null || !AlwaysDoMaxDamageInPvp.Value || (Object)(object)victim == (Object)null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || hit == null || (Object)(object)attacker == (Object)(object)victim || !IsPvpLikeTarget(victim) || !HitSkillDamageFactors.TryGetValue(hit, out SkillDamageFactorState value) || value.SkillDamageFactor <= 0f) { return; } try { SkillType val2 = hit.m_skill; if ((int)val2 == 0) { ItemData currentWeapon = GetCurrentWeapon(attacker); if (currentWeapon?.m_shared != null) { val2 = currentWeapon.m_shared.m_skillType; } } if ((int)val2 == 0) { return; } float num = default(float); float num2 = default(float); ((Character)val).GetSkills().GetRandomSkillRange(ref num, ref num2, val2); if (!(num2 <= 0f) && !(value.SkillDamageFactor <= 0f)) { float num3 = Mathf.Clamp(num2 / value.SkillDamageFactor, 0f, 10f); if (!Mathf.Approximately(num3, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num3); hit.m_pushForce *= num3; } } } catch { } } private static void ApplyOutgoingDamageAndPvpCalibration(Character victim, Character? attacker, HitData hit) { if ((Object)(object)attacker == (Object)null || hit == null) { return; } ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); CategoryConfig config = GetConfig(category); ApplyOutgoingAttackDamageCalibration(attacker, hit, category, currentAttackRole, config); if (TryGetAttackerStaffConfig(attacker, out StaffConfig cfg) && cfg.DirectAttack && cfg.DamageMultiplier != null) { float num = Mathf.Clamp(cfg.DamageMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } } } private static void ApplyAlternateDamageCurveAfterConditionalBonusesIfNeeded(Character victim, Character? attacker, HitData hit) { if (!((Object)(object)victim == (Object)null) && hit != null) { if (attacker is Player && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); ApplyAlternateDamageCurveIfNeeded(victim, attacker, hit, category, currentAttackRole, pvpDamageContext: true); } else { ApplyIncomingPveDamageCurveIfNeeded(victim, attacker, hit); } } } private static void ApplyPvpDamageCalibrationIfNeeded(Character victim, Character? attacker, HitData hit) { if ((Object)(object)victim == (Object)null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || hit == null || victim == attacker || EnablePvpDamageModifiers == null || !EnablePvpDamageModifiers.Value || !IsPvpLikeTarget(victim)) { return; } ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); CategoryConfig config = GetConfig(category); TryGetAttackerStaffConfig(attacker, out StaffConfig cfg); if (cfg != null && IsStaffGreenRoots(cfg) && !StaffGreenRootsAttackPlayersInPvp.Value) { ((DamageTypes)(ref hit.m_damage)).Modify(0f); return; } if (cfg != null && IsStaffSkeleton(cfg) && !StaffSkeletonAttackPlayersInPvp.Value) { ((DamageTypes)(ref hit.m_damage)).Modify(0f); return; } float num = ((cfg != null && cfg.DirectAttack && cfg.PvpDamageMultiplier != null) ? Mathf.Clamp(cfg.PvpDamageMultiplier.Value, 0f, 10f) : (TryGetJumpAttackPvpDamageMultiplier(val, category, out var multiplier) ? multiplier : ((!TryGetRunningAttackPvpDamageMultiplier(val, category, out var multiplier2)) ? Mathf.Clamp(config.GetPvpDamageMultiplier(currentAttackRole), 0f, 10f) : multiplier2))); num *= Mathf.Clamp((GlobalPvpDamageMultiplier != null) ? GlobalPvpDamageMultiplier.Value : 1f, 0f, 10f); num *= Mathf.Clamp((GlobalPvpDamageTakenMultiplier != null) ? GlobalPvpDamageTakenMultiplier.Value : 1f, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } } private static void ApplyIncomingPveDamageCurveIfNeeded(Character victim, Character? attacker, HitData hit) { if (victim is Player && hit != null && !(attacker is Player) && UseDamageCurveInPve != null && UseDamageCurveInPve.Value) { float anchor = ((PveAlternateDamageCurveBaseRawDamage != null) ? PveAlternateDamageCurveBaseRawDamage.Value : 100f); float exponent = ((PveAlternateDamageCurveExponent != null) ? PveAlternateDamageCurveExponent.Value : 0.5f); float minimum = ((PveAlternateDamageCurveMinimumMultiplier != null) ? PveAlternateDamageCurveMinimumMultiplier.Value : 0f); float maximum = ((PveAlternateDamageCurveMaximumMultiplier != null) ? PveAlternateDamageCurveMaximumMultiplier.Value : 1f); float rawDamage = EstimateHitRawDamageForDamageCurve(hit); float num = EvaluateAlternateDamageCurve(rawDamage, anchor, exponent, minimum, maximum); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"damage-curve:PvEIncoming:{((Object)victim).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, "PvE incoming alternate damage curve for " + SafeName(attacker) + " -> " + SafeName(victim) + ": rawDamage=" + rawDamage.ToString("0.###", CultureInfo.InvariantCulture) + ", anchor=" + anchor.ToString("0.###", CultureInfo.InvariantCulture) + ", exponent=" + exponent.ToString("0.###", CultureInfo.InvariantCulture) + ", min=" + minimum.ToString("0.###", CultureInfo.InvariantCulture) + ", max=" + maximum.ToString("0.###", CultureInfo.InvariantCulture) + ", curve=" + num.ToString("0.###", CultureInfo.InvariantCulture) + "."); } } } private static void ApplyAlternateDamageCurveIfNeeded(Character victim, Character? attacker, HitData hit, WeaponCategory category, AttackRole role, bool pvpDamageContext) { if ((Object)(object)victim == (Object)null || !(attacker is Player) || hit == null || victim == attacker || IsDamageCurveExempt(category, role)) { return; } bool flag = false; float num = 100f; float num2 = 0.5f; float num3 = 0f; float num4 = 2f; if (pvpDamageContext) { if (EnablePvpDamageModifiers == null || !EnablePvpDamageModifiers.Value || UseDamageCurveInPvp == null || !UseDamageCurveInPvp.Value) { return; } flag = true; num = ((PvpAlternateDamageCurveBaseRawDamage != null) ? PvpAlternateDamageCurveBaseRawDamage.Value : 100f); num2 = ((PvpAlternateDamageCurveExponent != null) ? PvpAlternateDamageCurveExponent.Value : 0.5f); num3 = ((PvpAlternateDamageCurveMinimumMultiplier != null) ? PvpAlternateDamageCurveMinimumMultiplier.Value : 0f); num4 = ((PvpAlternateDamageCurveMaximumMultiplier != null) ? PvpAlternateDamageCurveMaximumMultiplier.Value : 2f); } else { if (UseDamageCurveInPve == null || !UseDamageCurveInPve.Value) { return; } flag = true; num = ((PveAlternateDamageCurveBaseRawDamage != null) ? PveAlternateDamageCurveBaseRawDamage.Value : 100f); num2 = ((PveAlternateDamageCurveExponent != null) ? PveAlternateDamageCurveExponent.Value : 0.5f); num3 = ((PveAlternateDamageCurveMinimumMultiplier != null) ? PveAlternateDamageCurveMinimumMultiplier.Value : 0f); num4 = ((PveAlternateDamageCurveMaximumMultiplier != null) ? PveAlternateDamageCurveMaximumMultiplier.Value : 1f); } if (flag) { float num5 = EstimateHitRawDamageForDamageCurve(hit); float num6 = EstimatePendingVanillaDamageCurveBonusMultiplier(victim, attacker, hit, category, role); if (!Mathf.Approximately(num6, 1f)) { num5 *= num6; } float num7 = EvaluateAlternateDamageCurve(num5, num, num2, num3, num4); if (!Mathf.Approximately(num7, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num7); } if (Debug(DebugAttackLifecycle)) { string text = (pvpDamageContext ? "PvP" : "PvE"); DebugLogThrottled($"damage-curve:{text}:{((Object)victim).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, string.Format("{0} alternate damage curve for {1} -> {2} using {3}/{4}: rawAfterConditionalBonuses={5}, pendingVanillaBonus={6}, anchor={7}, exponent={8}, min={9}, max={10}, curve={11}.", text, SafeName(attacker), SafeName(victim), category, role, num5.ToString("0.###", CultureInfo.InvariantCulture), num6.ToString("0.###", CultureInfo.InvariantCulture), num.ToString("0.###", CultureInfo.InvariantCulture), num2.ToString("0.###", CultureInfo.InvariantCulture), num3.ToString("0.###", CultureInfo.InvariantCulture), num4.ToString("0.###", CultureInfo.InvariantCulture), num7.ToString("0.###", CultureInfo.InvariantCulture))); } } } private static float EstimatePendingVanillaDamageCurveBonusMultiplier(Character victim, Character? attacker, HitData hit, WeaponCategory category, AttackRole role) { float num = 1f; if (IsBackstabBonusLikelyEligible(victim, attacker, hit)) { num *= Mathf.Max(1f, hit?.m_backstabBonus ?? 1f); } if ((Object)(object)victim != (Object)null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && IsStaggeredPveEnemyBonusEligible(victim, attacker)) { bool stackable = StaggeredEnemyDamageStackableWithOtherBonuses != null && StaggeredEnemyDamageStackableWithOtherBonuses.Value; if (ShouldApplyStackableDamageBonus(hit, DamageBonusKind.StaggeredEnemy, stackable)) { float num2 = ((GlobalDamageToStaggeredEnemiesMultiplier != null) ? Mathf.Clamp(GlobalDamageToStaggeredEnemiesMultiplier.Value, 0f, 20f) : 2f); float localDamageToStaggeredEnemyMultiplier = GetLocalDamageToStaggeredEnemyMultiplier(val, category, role); num *= Mathf.Clamp(num2 * localDamageToStaggeredEnemyMultiplier, 0f, 100f); } } } return Mathf.Clamp(num, 0f, 10000f); } private static bool IsDamageCurveExempt(WeaponCategory category, AttackRole role) { if (role == AttackRole.Secondary) { if (category != WeaponCategory.Atgeir) { return category == WeaponCategory.Unarmed; } return true; } return false; } private static float EvaluateAlternateDamageCurve(float rawDamage, float anchor, float exponent, float minimum, float maximum) { if (rawDamage <= 0f || anchor <= 0f) { return 1f; } minimum = Mathf.Max(0f, minimum); maximum = Mathf.Max(0f, maximum); if (maximum < minimum) { float num = minimum; minimum = maximum; maximum = num; } float num2 = Mathf.Pow(anchor / rawDamage, exponent); if (float.IsNaN(num2) || float.IsInfinity(num2)) { return 1f; } return Mathf.Clamp(num2, minimum, maximum); } private static float EstimateHitRawDamageForDamageCurve(HitData hit) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (hit == null) { return 0f; } try { DamageTypes damage = hit.m_damage; float num = 0f; num += Mathf.Max(0f, damage.m_damage); num += Mathf.Max(0f, damage.m_blunt); num += Mathf.Max(0f, damage.m_slash); num += Mathf.Max(0f, damage.m_pierce); num += Mathf.Max(0f, damage.m_chop); num += Mathf.Max(0f, damage.m_pickaxe); num += Mathf.Max(0f, damage.m_fire); num += Mathf.Max(0f, damage.m_frost); num += Mathf.Max(0f, damage.m_lightning); num += Mathf.Max(0f, damage.m_poison); num += Mathf.Max(0f, damage.m_spirit); if (num > 0f) { return num; } } catch { } float num2 = TryEstimateHitTotalDamage(hit); if (!(num2 > 0f) || !(num2 < 999999f)) { return 0f; } return num2; } private static void ApplyOutgoingAttackDamageCalibration(Character? attacker, HitData hit) { if (!((Object)(object)attacker == (Object)null) && hit != null) { ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); ApplyOutgoingAttackDamageCalibration(attacker, hit, category, currentAttackRole, GetConfig(category)); } } private static void ApplyOutgoingAttackDamageCalibration(Character attacker, HitData hit, WeaponCategory category, AttackRole role, CategoryConfig cfg) { if ((Object)(object)attacker == (Object)null || hit == null || cfg == null || !MarkOutgoingDamageCalibration(hit)) { return; } float num = Mathf.Clamp(cfg.GetBaseDamageMultiplier(), 0f, 10f) * Mathf.Clamp(GlobalDamageMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && TryGetJumpAttackDamageMultiplier(val, category, out var multiplier)) { if (!Mathf.Approximately(multiplier, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(multiplier); } } else { Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (val2 != null && TryGetRunningAttackDamageMultiplier(val2, category, out var multiplier2)) { if (!Mathf.Approximately(multiplier2, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(multiplier2); } } else { float num2 = Mathf.Clamp(cfg.GetDamageRateMultiplier(role), 0f, 10f); if (!Mathf.Approximately(num2, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num2); } } } float multiplier3 = 1f; Player val3 = (Player)(object)((attacker is Player) ? attacker : null); if (val3 != null && TryGetAirborneMeleeDamageMultiplier(val3, category, role, out multiplier3) && !Mathf.Approximately(multiplier3, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(multiplier3); } Player val4 = (Player)(object)((attacker is Player) ? attacker : null); if (val4 != null) { ItemData currentWeapon = GetCurrentWeapon((Character)(object)val4); if (TryGetOriginatingAttackInstance(val4, out object attackInstance) && IsThrowableAttack(attackInstance, currentWeapon)) { float num3 = ((ThrowableDamageMultiplier != null) ? Mathf.Clamp(ThrowableDamageMultiplier.Value, 0f, 100f) : 1f); if (!Mathf.Approximately(num3, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num3); } if (Debug(DebugRanged)) { DebugLogThrottled($"throwable-damage:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, $"Applied bomb-style throwable damage multiplier x{num3.ToString(CultureInfo.InvariantCulture)} once to propagated projectile/AOE HitData for {SafeName((Character)(object)val4)}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } } ApplyUtilityDamageChannelMultipliers(attacker, hit, category, cfg, role); } private static void ApplyUtilityDamageChannelMultipliers(Character attacker, HitData hit, WeaponCategory category, CategoryConfig cfg, AttackRole role) { if (hit == null || cfg == null) { return; } float num = Mathf.Clamp(cfg.GetChopDamageMultiplier(role), 0f, 10f); float num2 = Mathf.Clamp(cfg.GetPickaxeDamageMultiplier(role), 0f, 10f); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && TryGetJumpAttackChopDamageMultiplier(val, category, out var multiplier)) { num = multiplier; if (TryGetJumpAttackPickaxeDamageMultiplier(val, category, out var multiplier2)) { num2 = multiplier2; } } else { Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (val2 != null && TryGetRunningAttackChopDamageMultiplier(val2, category, out var multiplier3)) { num = multiplier3; if (TryGetRunningAttackPickaxeDamageMultiplier(val2, category, out var multiplier4)) { num2 = multiplier4; } } } if (category != WeaponCategory.Pickaxe && !Mathf.Approximately(hit.m_damage.m_pickaxe, 0f) && DefaultPickaxeDamageMultiplier(category) <= 0f) { num2 = 1f; } hit.m_damage.m_chop *= num; hit.m_damage.m_pickaxe *= num2; } private static bool MarkOutgoingDamageCalibration(HitData hit) { if (hit == null) { return false; } if (OutgoingDamageCalibratedHits.TryGetValue(hit, out ProcessedHitMarker _)) { return false; } try { OutgoingDamageCalibratedHits.Add(hit, ProcessedHitMarker.Instance); return true; } catch { return false; } } private static bool TryGetAirborneMeleeDamageMultiplier(Player player, WeaponCategory category, AttackRole role, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null || !IsAirborneBonusWeaponCategory(category)) { return false; } if (!AttackStates.TryGetValue((Character)(object)player, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead((Character)(object)player) || !CharacterReportsInAttack((Character)(object)player)) { AttackStates.Remove((Character)(object)player); return false; } if (!value.StartedAirborne) { return false; } if (!SamePublicWeaponCategory(value.Category, category)) { return false; } if (value.AttackInstance != null && !IsMeleeOrAreaAttackInstance(value.AttackInstance)) { return false; } AttackRole attackRole = value.Role; if (attackRole != role && role != AttackRole.Primary1) { attackRole = role; } multiplier = Mathf.Clamp(GetConfig(value.Category).GetAirborneDamageMultiplier(attackRole), 0f, 10f); return true; } private static bool TryGetAirborneMeleeStaggerMultiplier(Player player, WeaponCategory category, AttackRole role, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null || !IsAirborneBonusWeaponCategory(category) || !IsFeatureApplied(category, ModFeature.Staggering)) { return false; } if (!AttackStates.TryGetValue((Character)(object)player, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead((Character)(object)player) || !CharacterReportsInAttack((Character)(object)player)) { AttackStates.Remove((Character)(object)player); return false; } if (!value.StartedAirborne || !SamePublicWeaponCategory(value.Category, category)) { return false; } if (value.AttackInstance != null && !IsMeleeOrAreaAttackInstance(value.AttackInstance)) { return false; } AttackRole attackRole = value.Role; if (attackRole != role && role != AttackRole.Primary1) { attackRole = role; } multiplier = Mathf.Clamp(GetConfig(value.Category).GetAirborneStaggerMultiplier(attackRole), 0f, 10f); return true; } private static bool TryGetAirborneMeleeRotationMultiplier(Player player, WeaponCategory category, AttackRole role, out float multiplier) { multiplier = 1f; if ((Object)(object)player == (Object)null || !IsAirborneBonusWeaponCategory(category)) { return false; } if (!AttackStates.TryGetValue((Character)(object)player, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead((Character)(object)player) || !CharacterReportsInAttack((Character)(object)player)) { AttackStates.Remove((Character)(object)player); return false; } if (!value.StartedAirborne || !SamePublicWeaponCategory(value.Category, category)) { return false; } if (value.AttackInstance != null && !IsMeleeOrAreaAttackInstance(value.AttackInstance)) { return false; } AttackRole attackRole = value.Role; if (attackRole != role && role != AttackRole.Primary1) { attackRole = role; } multiplier = Mathf.Clamp(GetConfig(value.Category).GetAirborneRotationFactor(attackRole), 0f, 10f); return true; } private static void ApplyKnockbackForceModifiers(Character victim, Character? attacker, HitData hit) { if (hit == null || (Object)(object)victim == (Object)null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { ItemData currentWeapon = GetCurrentWeapon((Character)(object)val); WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole((Character)(object)val, category); float num = (TryGetJumpAttackKnockbackForceMultiplier(val, category, out var multiplier) ? multiplier : ((!TryGetRunningAttackKnockbackForceMultiplier(val, category, out var multiplier2)) ? Mathf.Clamp(GetConfig(category).GetKnockbackForceMultiplier(currentAttackRole), 0f, 10f) : multiplier2)); num *= Mathf.Clamp(GlobalPlayerKnockbackMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f)) { hit.m_pushForce *= num; } if (!EnablePvpDamageModifiers.Value || !IsPvpLikeTarget(victim) || !((Object)(object)attacker != (Object)(object)victim)) { return; } if (TryGetStaffConfig(currentWeapon, out StaffConfig cfg) && cfg.DirectAttack && cfg.PvpKnockbackForceMultiplier != null) { float num2 = Mathf.Clamp(cfg.PvpKnockbackForceMultiplier.Value, 0f, 10f); hit.m_pushForce *= num2; return; } float num3 = (TryGetJumpAttackPvpKnockbackForceMultiplier(val, category, out var multiplier3) ? multiplier3 : ((!TryGetRunningAttackPvpKnockbackForceMultiplier(val, category, out var multiplier4)) ? Mathf.Clamp(GetConfig(category).GetPvpKnockbackForceMultiplier(currentAttackRole), 0f, 10f) : multiplier4)); num3 *= ((GlobalPvpKnockbackMultiplier != null) ? Mathf.Clamp(GlobalPvpKnockbackMultiplier.Value, 0f, 10f) : 1f); if (!Mathf.Approximately(num3, 1f)) { hit.m_pushForce *= num3; } } else if (victim is Player && (Object)(object)attacker != (Object)null && (Object)(object)attacker != (Object)(object)victim) { float num4 = Mathf.Clamp(EnemyKnockbackDealtMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num4, 1f)) { hit.m_pushForce *= num4; } } } private static int GetStaffSkeletonConfiguredSpawnCap(int quality) { if (quality <= 1) { return Mathf.Clamp((StaffSkeletonBaseSpawnCapLevel1 == null) ? 1 : StaffSkeletonBaseSpawnCapLevel1.Value, 0, 200); } return quality switch { 2 => Mathf.Clamp((StaffSkeletonBaseSpawnCapLevel2 != null) ? StaffSkeletonBaseSpawnCapLevel2.Value : 2, 0, 200), 3 => Mathf.Clamp((StaffSkeletonBaseSpawnCapLevel3 != null) ? StaffSkeletonBaseSpawnCapLevel3.Value : 3, 0, 200), _ => Mathf.Clamp((StaffSkeletonBaseSpawnCapLevel4 != null) ? StaffSkeletonBaseSpawnCapLevel4.Value : 4, 0, 200), }; } private static int GetStaffSkeletonConfiguredSpawnCapForItem(ItemData item) { int num = ((item?.m_shared != null) ? Mathf.Max(1, item.m_shared.m_maxQuality) : 4); return GetStaffSkeletonConfiguredSpawnCap(Mathf.Clamp(item?.m_quality ?? 1, 1, Mathf.Max(4, num))); } public static void SpawnAbilitySetupPrefix(object __instance, object[] __args) { if (!ModActive || __instance == null || __args == null || __args.Length < 5) { return; } object obj = __args[4]; ItemData val = (ItemData)((obj is ItemData) ? obj : null); if (val != null && TryGetStaffConfig(val, out StaffConfig cfg)) { ApplyNativeStaffSpawnCapFields(__instance, val, cfg); object obj2 = __args[0]; Character val2 = (Character)((obj2 is Character) ? obj2 : null); if (val2 != null && val2 is Player && (IsStaffGreenRoots(cfg) || IsStaffSkeleton(cfg))) { StaffSpawnAbilityOrigins[__instance] = new StaffSpawnOrigin(val2, cfg, val); } } } private static void ApplyNativeStaffSpawnCapFields(object spawnAbility, ItemData item, StaffConfig staffCfg) { if (spawnAbility == null || item == null || staffCfg == null) { return; } if (IsStaffGreenRoots(staffCfg)) { SetFieldValueRaw(spawnAbility, "m_maxSpawned", 0); } else { if (!IsStaffSkeleton(staffCfg)) { return; } int staffSkeletonConfiguredSpawnCapForItem = GetStaffSkeletonConfiguredSpawnCapForItem(item); SetFieldValueRaw(spawnAbility, "m_maxSpawned", 0); if (GetFieldValueRaw(spawnAbility, "m_levelUpSettings") is IEnumerable enumerable) { foreach (object item2 in enumerable) { SetFieldValueRaw(item2, "m_maxSpawns", staffSkeletonConfiguredSpawnCapForItem); } } SetFieldValueRaw(spawnAbility, "m_setMaxInstancesFromWeaponLevel", false); } } public static void SpawnAbilitySetupPostfix(object __instance, object[] __args) { if (!ModActive || __instance == null || StaffSpawnAbilityOrigins.ContainsKey(__instance) || __args == null || __args.Length < 5) { return; } object obj = __args[0]; Character val = (Character)((obj is Character) ? obj : null); if (val != null && val is Player) { object obj2 = __args[4]; ItemData val2 = (ItemData)((obj2 is ItemData) ? obj2 : null); if (val2 != null && TryGetStaffConfig(val2, out StaffConfig cfg) && (IsStaffGreenRoots(cfg) || IsStaffSkeleton(cfg))) { StaffSpawnAbilityOrigins[__instance] = new StaffSpawnOrigin(val, cfg, val2); } } } public static void SpawnAbilitySpawnPostfix(object __instance, ref IEnumerator __result) { if (ModActive && __instance != null && __result != null && StaffSpawnAbilityOrigins.TryGetValue(__instance, out StaffSpawnOrigin value)) { __result = RunStaffSpawnAbilityWithOrigin(__instance, __result, value); } } private static IEnumerator RunStaffSpawnAbilityWithOrigin(object ability, IEnumerator original, StaffSpawnOrigin origin) { try { while (true) { StaffSpawnOrigin currentSpawnAbilityRuntimeOrigin = CurrentSpawnAbilityRuntimeOrigin; CurrentSpawnAbilityRuntimeOrigin = origin; bool flag; try { flag = original.MoveNext(); } finally { CurrentSpawnAbilityRuntimeOrigin = currentSpawnAbilityRuntimeOrigin; } if (flag) { yield return original.Current; continue; } break; } } finally { if (ability != null) { StaffSpawnAbilityOrigins.Remove(ability); } } } public static void SpawnAbilitySetupProjectilePostfix(object __instance, object[] __args) { if (ModActive && __instance != null && __args != null && __args.Length >= 1 && StaffSpawnAbilityOrigins.TryGetValue(__instance, out StaffSpawnOrigin value)) { object obj = __args[0]; Projectile val = (Projectile)((obj is Projectile) ? obj : null); if (val != null) { TagStaffProjectile(val, value); } } } public static void SpawnAbilitySetupAoePrefix(object __instance, object[] __args) { CurrentSpawnAbilityAoeSetupOrigin = null; if (ModActive && __instance != null && StaffSpawnAbilityOrigins.TryGetValue(__instance, out StaffSpawnOrigin value)) { CurrentSpawnAbilityAoeSetupOrigin = value; } } public static void SpawnAbilitySetupAoePostfix(object __instance, object[] __args) { CurrentSpawnAbilityAoeSetupOrigin = null; } private static void ApplyStaffFireballProjectileAoeRadiusIfConfigured(Projectile projectile, StaffConfig staffCfg) { if (!((Object)(object)projectile == (Object)null) && staffCfg != null && string.Equals(staffCfg.PrefabName, "StaffFireball", StringComparison.OrdinalIgnoreCase) && staffCfg.AoeRadiusMultiplier != null && !StaffFireballProjectileAoeRadiusApplied.TryGetValue(projectile, out ProcessedHitMarker _)) { StaffFireballProjectileAoeRadiusApplied.Add(projectile, ProcessedHitMarker.Instance); float num = Mathf.Clamp(staffCfg.AoeRadiusMultiplier.Value, 0f, 20f); float aoe = projectile.m_aoe; if (!Mathf.Approximately(num, 1f) && aoe > 0f) { projectile.m_aoe = aoe * num; } if (Debug(DebugRanged)) { DebugLogThrottled($"staff-fireball-projectile-aoe:{((Object)projectile).GetInstanceID()}", 0.25f, "Staff of Embers native Projectile.m_aoe x" + num.ToString(CultureInfo.InvariantCulture) + ": " + aoe.ToString(CultureInfo.InvariantCulture) + " -> " + projectile.m_aoe.ToString(CultureInfo.InvariantCulture) + ". This field is the radius used by decompiled Projectile.DoAOE/Physics.OverlapSphere on impact."); } } } public static void ProjectileSetupPostfix(object __instance, object[] __args) { if (!ModActive || __instance == null) { return; } Projectile val = (Projectile)((__instance is Projectile) ? __instance : null); if (val != null) { if (CurrentStaffSpawnDamageOrigin != null) { TagStaffProjectile(val, CurrentStaffSpawnDamageOrigin); } if (__args != null && __args.Length >= 5) { object obj = __args[4]; ItemData val2 = (ItemData)((obj is ItemData) ? obj : null); if (val2 != null && TryGetStaffConfig(val2, out StaffConfig cfg)) { if (IsStaffGreenRoots(cfg) || IsStaffSkeleton(cfg)) { object obj2 = __args[0]; Character val3 = (Character)((obj2 is Character) ? obj2 : null); if (val3 != null) { TagStaffProjectile(val, new StaffSpawnOrigin(val3, cfg, val2)); } } if (string.Equals(cfg.PrefabName, "StaffFireball", StringComparison.OrdinalIgnoreCase)) { StaffFireballProjectileSources.Remove(__instance); StaffFireballProjectileSources.Add(__instance, cfg); ApplyStaffFireballProjectileAoeRadiusIfConfigured(val, cfg); } } } } if (__args != null && __args.Length >= 5) { object obj3 = __args[0]; Character val4 = (Character)((obj3 is Character) ? obj3 : null); HitData originalHitData = (HitData)((__args.Length >= 4) ? /*isinst with value type is only supported in some contexts*/: null); object obj4 = __args[4]; ItemData val5 = (ItemData)((obj4 is ItemData) ? obj4 : null); ApplyProjectileBackstabDamageMultiplierIfConfigured(__instance, val4, originalHitData, val5); if ((Object)(object)val4 != (Object)null && val5 != null) { ApplySpearLoyaltyProjectileDropSuppression(__instance, val4, val5); } } if (CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count > 0) { ProjectileAttackContexts[__instance] = CurrentAttackHitContexts.Peek(); } } public static void ProjectileOnHitPrefix(object __instance, object[] __args) { CurrentStaffFireballAoeSource = null; if (!ModActive || __instance == null) { return; } PushIncomingParryStaggerSource(IncomingParryStaggerSource.Ranged); if (StaffFireballProjectileSources.TryGetValue(__instance, out StaffConfig value)) { CurrentStaffFireballAoeSource = value; } if (StaffSpawnProjectileOrigins.TryGetValue(__instance, out StaffSpawnOrigin value2)) { CurrentStaffSpawnDamageOrigin = value2; } if (ProjectileAttackContexts.TryGetValue(__instance, out var value3)) { if (CurrentAttackHitContexts == null) { CurrentAttackHitContexts = new Stack(); } CurrentAttackHitContexts.Push(value3); } } public static void ProjectileOnHitPostfix(object __instance, object[] __args) { if (__instance != null && ProjectileAttackContexts.ContainsKey(__instance)) { PopAttackHitContext(); } if (__instance != null) { PopIncomingParryStaggerSource(); } CurrentStaffSpawnDamageOrigin = null; CurrentStaffFireballAoeSource = null; } private static int ScaleStaffFireballAoeColliders(Aoe aoe, float multiplier) { if ((Object)(object)aoe == (Object)null || Mathf.Approximately(multiplier, 1f)) { return 0; } HashSet scaled = new HashSet(); int count = 0; if ((Object)(object)aoe.m_useCollider != (Object)null) { ScaleCollider((Collider)(object)aoe.m_useCollider); } if (aoe.m_useTriggers) { Transform transform = ((Component)aoe).transform; ZNetView componentInParent = ((Component)aoe).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { transform = ((Component)componentInParent).transform; } Collider[] componentsInChildren = ((Component)transform).GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)val != (Object)null) { ScaleCollider(val); } } } return count; void ScaleCollider(Collider collider) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)collider == (Object)null) && scaled.Add(collider)) { SphereCollider val2 = (SphereCollider)(object)((collider is SphereCollider) ? collider : null); if (val2 == null) { CapsuleCollider val3 = (CapsuleCollider)(object)((collider is CapsuleCollider) ? collider : null); if (val3 == null) { BoxCollider val4 = (BoxCollider)(object)((collider is BoxCollider) ? collider : null); if (val4 != null) { val4.size *= multiplier; count++; } } else { val3.radius *= multiplier; val3.height *= multiplier; count++; } } else { val2.radius *= multiplier; count++; } } } } private static void ApplyStaffFireballAoeRadiusIfConfigured(object aoeInstance, object[]? args) { Aoe val = (Aoe)((aoeInstance is Aoe) ? aoeInstance : null); if (val == null) { return; } StaffConfig staffConfig = CurrentStaffFireballAoeSource; string text = ((staffConfig != null) ? "projectile-hit context" : "Aoe.Setup item"); if (staffConfig == null && args != null && args.Length >= 5) { object obj = args[4]; ItemData val2 = (ItemData)((obj is ItemData) ? obj : null); if (val2 != null && TryGetStaffConfig(val2, out StaffConfig cfg)) { staffConfig = cfg; } } if (staffConfig == null || !string.Equals(staffConfig.PrefabName, "StaffFireball", StringComparison.OrdinalIgnoreCase) || staffConfig.AoeRadiusMultiplier == null || StaffFireballAoeRadiusApplied.TryGetValue(val, out ProcessedHitMarker _)) { return; } float num = Mathf.Clamp(staffConfig.AoeRadiusMultiplier.Value, 0f, 20f); StaffFireballAoeRadiusApplied.Add(val, ProcessedHitMarker.Instance); if (!Mathf.Approximately(num, 1f)) { float radius = val.m_radius; val.m_radius = radius * num; int num2 = ScaleStaffFireballAoeColliders(val, num); if (Debug(DebugRanged)) { DebugLogThrottled($"staff-fireball-aoe:{((Object)val).GetInstanceID()}", 0.25f, $"Staff of Embers AOE radius x{num.ToString(CultureInfo.InvariantCulture)} via {text}: radius={radius.ToString(CultureInfo.InvariantCulture)} -> {val.m_radius.ToString(CultureInfo.InvariantCulture)}, useTriggers={val.m_useTriggers}, useCollider={(Object)(object)val.m_useCollider != (Object)null}, scaledPhysicalColliders={num2}."); } } } public static void AoeSetupPostfix(object __instance, object[] __args) { if (ModActive && __instance != null) { ApplyStaffFireballAoeRadiusIfConfigured(__instance, __args); if (CurrentSpawnAbilityAoeSetupOrigin != null) { StaffSpawnAoeOrigins[__instance] = CurrentSpawnAbilityAoeSetupOrigin; } if (CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count > 0) { AoeAttackContexts[__instance] = CurrentAttackHitContexts.Peek(); } IncomingParryStaggerSource currentIncomingParryStaggerSource = GetCurrentIncomingParryStaggerSource(); if (currentIncomingParryStaggerSource != IncomingParryStaggerSource.None) { AoeParryStaggerSources[__instance] = currentIncomingParryStaggerSource; } } } public static void AoeOnHitPrefix(object __instance, object[] __args) { if (!ModActive || __instance == null) { return; } PushIncomingParryStaggerSource(AoeParryStaggerSources.TryGetValue(__instance, out var value) ? value : IncomingParryStaggerSource.Aoe); if (StaffSpawnAoeOrigins.TryGetValue(__instance, out StaffSpawnOrigin value2)) { CurrentStaffSpawnDamageOrigin = value2; } if (AoeAttackContexts.TryGetValue(__instance, out var value3)) { if (CurrentAttackHitContexts == null) { CurrentAttackHitContexts = new Stack(); } CurrentAttackHitContexts.Push(value3); } } public static void AoeOnHitPostfix(object __instance, object[] __args) { if (__instance != null && AoeAttackContexts.ContainsKey(__instance)) { PopAttackHitContext(); } if (__instance != null) { PopIncomingParryStaggerSource(); } CurrentStaffSpawnDamageOrigin = null; } public static void BaseAIFindEnemyPostfix(object __instance, ref Character __result) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!ModActive || __instance == null) { return; } Character fieldValue = GetFieldValue(__instance, "m_character"); if (!((Object)(object)fieldValue == (Object)null) && StaffSpawnCharacterOrigins.TryGetValue(fieldValue, out StaffSpawnOrigin value) && IsPvpStaffSpawnAllowed(value)) { Character val = FindClosestPvpTargetForStaffSpawn(value, ((Component)fieldValue).transform.position, 200f); if ((Object)(object)val != (Object)null) { __result = val; } } } private static bool IsLikelyStaffSpawnedObject(GameObject go, StaffSpawnOrigin origin) { if ((Object)(object)go == (Object)null || origin == null) { return false; } string text = SafeLower(GetPrefabLikeName(go) + " " + ((Object)go).name); if (origin.IsGreenRoots) { if (!text.Contains("root") && !text.Contains("vine") && !text.Contains("greenroots")) { return text.Contains("wild"); } return true; } if (origin.IsSkeleton) { if (!text.Contains("skeleton") && !text.Contains("deadraiser") && !text.Contains("dead_raiser")) { return text.Contains("dead raiser"); } return true; } return false; } private static void TagStaffProjectile(Projectile projectile, StaffSpawnOrigin origin) { if (!((Object)(object)projectile == (Object)null) && origin != null) { StaffSpawnProjectileOrigins[projectile] = origin; } } private static void TagStaffCharacter(Character character, StaffSpawnOrigin origin) { if (!((Object)(object)character == (Object)null) && origin != null) { StaffSpawnCharacterOrigins[character] = origin; if (origin.IsGreenRoots) { ApplyStaffGreenRootsVineRuntimeTweaks(character); RegisterAndEnforceStaffGreenRootCap(character, origin); } if (origin.IsSkeleton) { ApplyStaffSkeletonRuntimeTweaks(character); } if (IsPvpStaffSpawnAllowed(origin)) { EnableStaffSpawnPvpTargeting(character); } } } private static long GetStaffGreenRootOwnerKey(StaffSpawnOrigin origin) { return origin?.OwnerPlayerId ?? 0; } private static void RegisterAndEnforceStaffGreenRootCap(Character character, StaffSpawnOrigin origin) { if ((Object)(object)character == (Object)null || origin == null || !origin.IsGreenRoots) { return; } long staffGreenRootOwnerKey = GetStaffGreenRootOwnerKey(origin); if (staffGreenRootOwnerKey == 0L) { return; } ZNetView component = ((Component)character).GetComponent(); long num = DateTime.UtcNow.Ticks; if ((Object)(object)component != (Object)null && component.IsValid()) { ZDO zDO = component.GetZDO(); if (zDO != null) { long num2 = zDO.GetLong("GCO_StaffGreenRootSpawnTicks", 0L); if (num2 > 0) { num = num2; } if (component.IsOwner()) { zDO.Set("GCO_StaffGreenRootOwnerId", staffGreenRootOwnerKey); zDO.Set("GCO_StaffGreenRootSpawnTicks", num); } } } RegisterStaffGreenRoot(character, staffGreenRootOwnerKey, num); if (!((Object)(object)component != (Object)null) || !component.IsValid() || component.IsOwner()) { EnforceStaffGreenRootCap(staffGreenRootOwnerKey, character); } } private static void TryRegisterPersistedStaffGreenRoot(Character character) { if ((Object)(object)character == (Object)null) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } ZDO zDO = component.GetZDO(); if (zDO == null) { return; } long num = zDO.GetLong("GCO_StaffGreenRootOwnerId", 0L); if (num != 0L) { long spawnTicks = zDO.GetLong("GCO_StaffGreenRootSpawnTicks", DateTime.UtcNow.Ticks); RegisterStaffGreenRoot(character, num, spawnTicks); if (component.IsOwner()) { EnforceStaffGreenRootCap(num, character); } } } private static void RegisterStaffGreenRoot(Character character, long ownerKey, long spawnTicks) { UnregisterStaffGreenRoot(character); if (!StaffGreenRootsByOwner.TryGetValue(ownerKey, out List value)) { value = new List(); StaffGreenRootsByOwner[ownerKey] = value; } value.RemoveAll((StaffGreenRootSpawnRecord record) => (Object)(object)record.Character == (Object)null || (Object)(object)((Component)record.Character).gameObject == (Object)null); value.Add(new StaffGreenRootSpawnRecord(character, spawnTicks)); StaffGreenRootOwnerByCharacter[character] = ownerKey; } private static void UnregisterStaffGreenRoot(Character character) { if ((Object)(object)character == (Object)null || !StaffGreenRootOwnerByCharacter.TryGetValue(character, out var value)) { return; } StaffGreenRootOwnerByCharacter.Remove(character); if (StaffGreenRootsByOwner.TryGetValue(value, out List value2)) { value2.RemoveAll((StaffGreenRootSpawnRecord record) => (Object)(object)record.Character == (Object)null || record.Character == character); if (value2.Count == 0) { StaffGreenRootsByOwner.Remove(value); } } } private static void EnforceStaffGreenRootCap(long ownerKey, Character newest) { int num = Mathf.Clamp((StaffGreenRootsMaxVinesSpawned != null) ? StaffGreenRootsMaxVinesSpawned.Value : 10, 0, 200); if (num <= 0 || !StaffGreenRootsByOwner.TryGetValue(ownerKey, out List value)) { return; } value.RemoveAll((StaffGreenRootSpawnRecord record) => (Object)(object)record.Character == (Object)null || (Object)(object)((Component)record.Character).gameObject == (Object)null); value.Sort((StaffGreenRootSpawnRecord a, StaffGreenRootSpawnRecord b) => a.SpawnTicks.CompareTo(b.SpawnTicks)); while (value.Count > num) { StaffGreenRootSpawnRecord staffGreenRootSpawnRecord = value.FirstOrDefault((StaffGreenRootSpawnRecord candidate) => CanAuthoritativelyDestroyStaffGreenRoot(candidate.Character)); if (staffGreenRootSpawnRecord == null) { staffGreenRootSpawnRecord = value.FirstOrDefault((StaffGreenRootSpawnRecord candidate) => candidate.Character == newest); } if (staffGreenRootSpawnRecord == null) { break; } value.Remove(staffGreenRootSpawnRecord); Character character = staffGreenRootSpawnRecord.Character; StaffGreenRootOwnerByCharacter.Remove(character); DestroyStaffGreenRoot(character); } if (value.Count == 0) { StaffGreenRootsByOwner.Remove(ownerKey); } } private static bool CanAuthoritativelyDestroyStaffGreenRoot(Character character) { if ((Object)(object)character == (Object)null || (Object)(object)((Component)character).gameObject == (Object)null) { return false; } ZNetView component = ((Component)character).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { return component.IsOwner(); } return true; } private static void DestroyStaffGreenRoot(Character character) { if ((Object)(object)character == (Object)null || (Object)(object)((Component)character).gameObject == (Object)null) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid()) { if (component.IsOwner()) { ZNetScene.instance.Destroy(((Component)character).gameObject); } } else { Object.Destroy((Object)(object)((Component)character).gameObject); } } private static void ApplyStaffGreenRootsVineRuntimeTweaks(Character character) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)character == (Object)null) && StaffGreenRootsRuntimeTweakedCharacters.Add(character)) { float num = Mathf.Clamp(StaffGreenRootsVineSizeMultiplier.Value, 0.05f, 10f); if (!Mathf.Approximately(num, 1f)) { Transform transform = ((Component)character).transform; transform.localScale *= num; } float num2 = Mathf.Clamp(StaffGreenRootsVineAttackSpeedMultiplier.Value, 0.05f, 10f); if (!Mathf.Approximately(num2, 1f)) { MultiplyCharacterAnimators(character, num2); } TryApplyStaffSpawnForcedLevel(character, (StaffGreenRootsVineLevel != null) ? StaffGreenRootsVineLevel.Value : 0); } } private static void ApplyStaffSkeletonRuntimeTweaks(Character character) { //IL_0053: 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 (!((Object)(object)character == (Object)null) && StaffSkeletonRuntimeTweakedCharacters.Add(character)) { float num = Mathf.Clamp((StaffSkeletonSizeMultiplier != null) ? StaffSkeletonSizeMultiplier.Value : 1f, 0.05f, 10f); if (!Mathf.Approximately(num, 1f)) { Transform transform = ((Component)character).transform; transform.localScale *= num; } float num2 = Mathf.Clamp((StaffSkeletonAttackSpeedMultiplier != null) ? StaffSkeletonAttackSpeedMultiplier.Value : 1f, 0.05f, 10f); if (!Mathf.Approximately(num2, 1f)) { MultiplyCharacterAnimators(character, num2); } TryApplyStaffSpawnForcedLevel(character, (StaffSkeletonLevel != null) ? StaffSkeletonLevel.Value : 0); } } private static void MultiplyCharacterAnimators(Character character, float speed) { if ((Object)(object)character == (Object)null) { return; } Animator[] componentsInChildren = ((Component)character).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if ((Object)(object)val != (Object)null) { val.speed *= speed; } } } private static void TryApplyStaffSpawnForcedLevel(Character character, int level) { if ((Object)(object)character == (Object)null || level <= 0) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component != (Object)null && component.IsValid() && !component.IsOwner()) { return; } try { character.SetLevel(Mathf.Clamp(level, 1, 200)); } catch { } } private static void EnableStaffSpawnPvpTargeting(Character character) { if ((Object)(object)character == (Object)null) { return; } BaseAI[] components = ((Component)character).GetComponents(); foreach (BaseAI val in components) { if (!((Object)(object)val == (Object)null)) { try { val.SetHuntPlayer(true); } catch { } try { val.Alert(); } catch { } } } } private static Character? FindClosestPvpTargetForStaffSpawn(StaffSpawnOrigin origin, Vector3 point, float maxDistance) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (origin != null) { Character owner = origin.Owner; Player val = (Player)(object)((owner is Player) ? owner : null); if (val != null && ((Character)val).IsPVPEnabled()) { Character result = null; float num = maxDistance; { foreach (Character allCharacter in Character.GetAllCharacters()) { Player val2 = (Player)(object)((allCharacter is Player) ? allCharacter : null); if (val2 != null && val2 != val && ((Character)val2).IsPVPEnabled() && !((Character)val2).IsDead() && (origin.OwnerPlayerId == 0L || val2.GetPlayerID() != origin.OwnerPlayerId)) { float num2 = Vector3.Distance(((Component)val2).transform.position, point); if (num2 < num) { num = num2; result = (Character)(object)val2; } } } return result; } } } return null; } private static void ApplyStaffSpawnDamageIfNeeded(Character victim, Character? attacker, HitData hit) { if (!ModActive || (Object)(object)victim == (Object)null || hit == null || !TryGetCurrentStaffSpawnOrigin(attacker, out StaffSpawnOrigin origin)) { return; } float num = 1f; float num2 = 1f; if (origin.IsGreenRoots && StaffGreenRootsVineDamageMultiplier != null) { num = Mathf.Clamp(StaffGreenRootsVineDamageMultiplier.Value, 0f, 10f); } else { if (!origin.IsSkeleton || !StaffConfigs.TryGetValue("StaffSkeleton", out StaffConfig value)) { return; } if (value.DamageMultiplier != null) { num = Mathf.Clamp(value.DamageMultiplier.Value, 0f, 10f); } if (EnablePvpDamageModifiers.Value && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)null && attacker != victim && IsPvpStaffSpawnAllowed(origin) && value.PvpDamageMultiplier != null) { num2 = Mathf.Clamp(value.PvpDamageMultiplier.Value, 0f, 10f); } } if (!Mathf.Approximately(num, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num); } if (!Mathf.Approximately(num2, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(num2); } } private static bool TryGetCurrentStaffSpawnOrigin(Character? attacker, out StaffSpawnOrigin origin) { origin = null; if (CurrentStaffSpawnDamageOrigin != null) { origin = CurrentStaffSpawnDamageOrigin; return true; } if ((Object)(object)attacker != (Object)null && StaffSpawnCharacterOrigins.TryGetValue(attacker, out origin)) { return true; } return false; } private static bool IsPvpStaffSpawnAllowed(StaffSpawnOrigin origin) { if (origin == null) { return false; } if (origin.IsGreenRoots) { if (StaffGreenRootsAttackPlayersInPvp != null) { return StaffGreenRootsAttackPlayersInPvp.Value; } return false; } if (origin.IsSkeleton) { if (StaffSkeletonAttackPlayersInPvp != null) { return StaffSkeletonAttackPlayersInPvp.Value; } return false; } return false; } private static bool IsPvpStaffSpawnBlockedForHit(Character victim, Character? attacker, HitData hit) { if (!EnablePvpDamageModifiers.Value || (Object)(object)victim == (Object)null || hit == null) { return false; } if (!IsPvpLikeTarget(victim)) { return false; } if (!TryGetCurrentStaffSpawnOrigin(attacker, out StaffSpawnOrigin origin)) { return false; } if ((Object)(object)attacker != (Object)null && attacker == victim) { return false; } return !IsPvpStaffSpawnAllowed(origin); } private static void SuppressBlockedPvpStaffSpawnDamageIfNeeded(Character victim, Character? attacker, HitData hit) { if (IsPvpStaffSpawnBlockedForHit(victim, attacker, hit)) { ((DamageTypes)(ref hit.m_damage)).Modify(0f); } } private static void ApplyAdrenalineMultiplierIfEligible(Character? attacker, HitData hit) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && hit != null) { ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); float multiplier; float multiplier2; float num = (TryGetJumpAttackAdrenalineMultiplier(val, category, out multiplier) ? multiplier : ((!TryGetRunningAttackAdrenalineMultiplier(val, category, out multiplier2)) ? Mathf.Max(0f, GetConfig(category).GetAdrenalineMultiplier(currentAttackRole)) : multiplier2)); if (!(num <= 1.0001f)) { float amount = num - 1f; TryAddAdrenaline(val, amount); } } } private static void TryAddAdrenaline(Player player, float amount) { if ((Object)(object)player == (Object)null || amount <= 0f) { return; } Type type = ((object)player).GetType(); MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { string text = SafeLower(methodInfo2.Name); if (!text.Contains("adrenaline")) { continue; } ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length != 1) { continue; } Type parameterType = parameters[0].ParameterType; if (!(parameterType != typeof(float)) || !(parameterType != typeof(int)) || !(parameterType != typeof(double))) { if (text.Contains("add") || text.Contains("increase") || text.Contains("gain") || text.Contains("raise")) { methodInfo = methodInfo2; break; } if ((object)methodInfo == null) { methodInfo = methodInfo2; } } } if (methodInfo != null) { try { Type parameterType2 = methodInfo.GetParameters()[0].ParameterType; object obj = ((parameterType2 == typeof(int)) ? ((object)Mathf.RoundToInt(amount)) : ((!(parameterType2 == typeof(double))) ? ((object)amount) : ((object)(double)amount))); methodInfo.Invoke(player, new object[1] { obj }); return; } catch { } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!SafeLower(fieldInfo.Name).Contains("adrenaline")) { continue; } try { if (fieldInfo.FieldType == typeof(float)) { float num = (float)fieldInfo.GetValue(player); fieldInfo.SetValue(player, num + amount); return; } if (fieldInfo.FieldType == typeof(int)) { int num2 = (int)fieldInfo.GetValue(player); fieldInfo.SetValue(player, num2 + Mathf.RoundToInt(amount)); return; } } catch { } } if (DebugLogging != null && DebugLogging.Value) { Log.LogWarning((object)"Could not find a usable Player adrenaline method/field on this Valheim build."); } } private static bool TryGetActiveMeleeLungeMultiplier(Character character, out WeaponCategory category, out AttackRole role, out float multiplier) { category = WeaponCategory.Other; role = AttackRole.Primary1; multiplier = 1f; if ((Object)(object)character == (Object)null || !AttackStates.TryGetValue(character, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead(character)) { if (value.AttackInstance != null) { LungeHitboxTriggeredAttacks.Remove(value.AttackInstance); } AttackStates.Remove(character); return false; } if (!CharacterReportsInAttack(character)) { return false; } if (!IsMeleeWeaponCategory(value.Category)) { return false; } if (value.AttackInstance != null && !IsMeleeOrAreaAttackInstance(value.AttackInstance)) { return false; } category = value.Category; role = value.Role; LungeApplicationMode lungeApplicationMode; float multiplier3; LungeApplicationMode mode2; if (TryGetJumpAttackLungeSettings(character, category, value.AttackInstance, out var multiplier2, out var mode)) { multiplier = multiplier2; lungeApplicationMode = mode; } else if (TryGetRunningAttackLungeSettings(character, category, value.AttackInstance, out multiplier3, out mode2)) { multiplier = multiplier3; lungeApplicationMode = mode2; } else { CategoryConfig config = GetConfig(category); multiplier = Mathf.Clamp(config.GetLungeDistanceMultiplier(role), 0f, 5f); lungeApplicationMode = config.GetLungeApplicationMode(role); } if (lungeApplicationMode == LungeApplicationMode.BeforeHitbox && value.AttackInstance != null && LungeHitboxTriggeredAttacks.Contains(value.AttackInstance)) { return false; } return !Mathf.Approximately(multiplier, 1f); } private static bool ShouldApplyAttackMovementOverride(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && AttackInstanceWeapons.TryGetValue(attackInstance, out ItemData value) && TryGetStaffBlockConfig(value, out StaffBlockConfig _)) { return true; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig _)) { return IsFeatureApplied(category, ModFeature.AttackMovement); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { TryGetRunningAttackConfig(category, out RunningAttackConfig _); return IsFeatureApplied(category, ModFeature.AttackMovement); } return IsFeatureApplied(category, ModFeature.AttackMovement); } private static float GetConfiguredAttackMovementValue(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); float num = GetConfig(category).GetAttackMovementModifier(role); if (attackInstance != null && AttackInstanceWeapons.TryGetValue(attackInstance, out ItemData value) && TryGetStaffBlockConfig(value, out StaffBlockConfig cfg)) { num = cfg.AttackMovementModifier.Value; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { num = config.MovementSpeedMultiplier.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { num = config2.MovementSpeedMultiplier.Value; } return Mathf.Clamp(num, 0f, 5f); } private static float GetGlobalAttackMovementStackMultiplier() { if (GlobalAttackMovementMultiplier == null) { return 1f; } return Mathf.Clamp(GlobalAttackMovementMultiplier.Value, 0f, 10f); } private static float GetEffectiveAttackMovementModifier(object? attackInstance, WeaponCategory category, AttackRole role) { return Mathf.Clamp(GetConfiguredAttackMovementValue(attackInstance, category, role) * GetGlobalAttackMovementStackMultiplier(), 0f, 5f); } private static bool UsesAdditiveAttackMovementFactor(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (category != WeaponCategory.SledgeAlt || role != AttackRole.Secondary) { return false; } if (attackInstance != null && (RunningAttackInstances.Contains(attackInstance) || JumpAttackInstances.Contains(attackInstance))) { return false; } return true; } private static float ResolveAttackMovementResult(float nativeResult, object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); float configuredAttackMovementValue = GetConfiguredAttackMovementValue(attackInstance, category, role); float globalAttackMovementStackMultiplier = GetGlobalAttackMovementStackMultiplier(); if (UsesAdditiveAttackMovementFactor(attackInstance, category, role)) { return Mathf.Clamp((nativeResult + configuredAttackMovementValue) * globalAttackMovementStackMultiplier, 0f, 5f); } if (UsesConfiguredAttackMovementBaseFactor(attackInstance, category, role)) { return Mathf.Clamp(configuredAttackMovementValue * globalAttackMovementStackMultiplier, 0f, 5f); } return Mathf.Clamp(nativeResult * configuredAttackMovementValue * globalAttackMovementStackMultiplier, 0f, 5f); } private static AttackMovementApplicationMode GetEffectiveAttackMovementApplicationMode(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.MovementApplicationMode.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.MovementApplicationMode.Value; } return GetConfig(category).GetAttackMovementApplicationMode(role); } private static void ApplyAttackMovementSpeedIfConfigured(object attackInstance, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null || !ShouldApplyAttackMovementOverride(attackInstance, category, role)) { return; } float effectiveAttackMovementModifier = GetEffectiveAttackMovementModifier(attackInstance, category, role); bool flag = UsesConfiguredAttackMovementBaseFactor(attackInstance, category, role); bool flag2 = UsesAdditiveAttackMovementFactor(attackInstance, category, role); if (AttackInstanceOwners.TryGetValue(attackInstance, out Character value)) { Player val = (Player)(object)((value is Player) ? value : null); if (val != null) { WeaponCategory weaponCategory = ResolveAttackSettingsCategory(attackInstance, category, role); if (weaponCategory == WeaponCategory.SledgeAlt && DebugSledgeAlternate()) { DebugSledgeAlternateLog($"movement-runtime-only:{((Object)val).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Deferred alternate-sledge movement to the live Humanoid movement resolver without mutating Attack.m_speedFactor; role={role}, running={RunningAttackInstances.Contains(attackInstance)}, configuredLocal={GetConfiguredAttackMovementValue(attackInstance, weaponCategory, role).ToString(CultureInfo.InvariantCulture)}, additiveMode={UsesAdditiveAttackMovementFactor(attackInstance, weaponCategory, role)}, globalStack={GetGlobalAttackMovementStackMultiplier().ToString(CultureInfo.InvariantCulture)}, configuredTimesGlobal={effectiveAttackMovementModifier.ToString(CultureInfo.InvariantCulture)}, featureApplied={IsFeatureApplied(WeaponCategory.Sledge, ModFeature.AttackMovement)}, application={GetEffectiveAttackMovementApplicationMode(attackInstance, weaponCategory, role)}."); } return; } } if ((!flag && !flag2 && Mathf.Approximately(effectiveAttackMovementModifier, 1f)) || (AttackMovementGroundingModeConfig.Value == AttackMovementGroundingMode.GroundOnly && AttackInstanceOwners.TryGetValue(attackInstance, out Character value2) && (Object)(object)value2 != (Object)null && !value2.IsOnGround()) || AttackMovementSpeedContexts.ContainsKey(attackInstance)) { return; } List list = new List(); SetKnownAttackMovementSpeedFields(attackInstance, category, role, list); if (list.Count > 0) { AttackMovementSpeedContexts[attackInstance] = list; WeaponCategory weaponCategory2 = ResolveAttackSettingsCategory(attackInstance, category, role); if (weaponCategory2 == WeaponCategory.SledgeAlt && DebugSledgeAlternate()) { float configuredAttackMovementValue = GetConfiguredAttackMovementValue(attackInstance, weaponCategory2, role); float globalAttackMovementStackMultiplier = GetGlobalAttackMovementStackMultiplier(); float privateFloat = GetPrivateFloat(attackInstance, "m_speedFactor", float.NaN); DebugSledgeAlternateLog($"movement-field-applied:{RuntimeHelpers.GetHashCode(attackInstance)}", $"Applied alternate-sledge movement to live Attack.m_speedFactor: role={role}, running={RunningAttackInstances.Contains(attackInstance)}, configuredLocal={configuredAttackMovementValue.ToString(CultureInfo.InvariantCulture)}, additiveMode={flag2}, globalStack={globalAttackMovementStackMultiplier.ToString(CultureInfo.InvariantCulture)}, configuredTimesGlobal={effectiveAttackMovementModifier.ToString(CultureInfo.InvariantCulture)}, liveField={privateFloat.ToString(CultureInfo.InvariantCulture)}, application={GetEffectiveAttackMovementApplicationMode(attackInstance, weaponCategory2, role)}, originals={list.Count}.", 0.05f); } if (Debug(DebugMovement)) { Log.LogInfo((object)$"AttackMovementModifier set movement factor {effectiveAttackMovementModifier.ToString(CultureInfo.InvariantCulture)} with mode {GetEffectiveAttackMovementApplicationMode(attackInstance, category, role)} for {category} {role} to {list.Count} known Attack field(s)."); } } else if (Debug(DebugMovement)) { Log.LogInfo((object)$"AttackMovementModifier was set for {category} {role}, but known Attack movement field m_speedFactor was not found on {attackInstance.GetType().FullName}."); } } private static void SetKnownAttackMovementSpeedFields(object target, WeaponCategory category, AttackRole role, List originals) { if (target != null) { SetAttackMovementSpeedField(target, "m_speedFactor", category, role, originals); } } private static bool UsesConfiguredAttackMovementBaseFactor(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.SledgeAlt) { return false; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { return true; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { return false; } switch (role) { case AttackRole.Secondary: return category == WeaponCategory.TwoHandedAxe; default: return false; case AttackRole.Primary1: case AttackRole.Primary2: case AttackRole.Primary3: case AttackRole.Primary4: return true; } } private static void SetAttackMovementSpeedField(object target, string fieldName, WeaponCategory category, AttackRole role, List originals) { FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.IsInitOnly || cachedField.IsLiteral || cachedField.FieldType != typeof(float)) { return; } try { object value = cachedField.GetValue(target); if (value is float nativeResult) { float num = ResolveAttackMovementResult(nativeResult, target, category, role); originals.Add(new SuppressedFieldValue(target, cachedField, value)); cachedField.SetValue(target, num); } } catch { } } private static void RestoreAttackMovementSpeedAtHitboxIfConfigured(object attackInstance, WeaponCategory category, AttackRole role) { if (GetEffectiveAttackMovementApplicationMode(attackInstance, category, role) == AttackMovementApplicationMode.BeforeHitbox) { RestoreAttackMovementSpeedTweaks(attackInstance); } } private static void RestoreAllAttackMovementSpeedTweaks() { foreach (object item in AttackMovementSpeedContexts.Keys.ToList()) { RestoreAttackMovementSpeedTweaks(item); } AttackMovementSpeedContexts.Clear(); } private static void RestoreAttackMovementSpeedTweaks(object attackInstance) { if (attackInstance == null || !AttackMovementSpeedContexts.TryGetValue(attackInstance, out List value)) { return; } AttackMovementSpeedContexts.Remove(attackInstance); foreach (SuppressedFieldValue item in value) { try { item.Field.SetValue(item.Target, item.OriginalValue); } catch { } } } private static void ApplyAttackRotationOverrideIfConfigured(object attackInstance, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null) { return; } category = ResolveAttackSettingsCategory(attackInstance, category, role); StaffBlockConfig cfg = null; bool flag = weapon != null && TryGetStaffBlockConfig(weapon, out cfg); if (!flag && (!IsMeleeWeaponCategory(category) || !IsMeleeOrAreaAttackInstance(attackInstance))) { return; } float num = ((flag && cfg != null) ? cfg.AttackRotationFactor.Value : GetConfig(category).GetAttackRotationFactor(role)); if (!flag && AttackInstanceOwners.TryGetValue(attackInstance, out Character value)) { Player val = (Player)(object)((value is Player) ? value : null); if (val != null) { float factor2; if (TryGetJumpAttackRotationFactor(val, category, out var factor)) { num = factor; } else if (TryGetRunningAttackRotationFactor(val, category, out factor2)) { num = factor2; } if (num >= 0f && TryGetAirborneMeleeRotationMultiplier(val, category, role, out var multiplier)) { num *= multiplier; } } } if (!(num < 0f)) { num *= ((GlobalAttackRotationMultiplier != null) ? Mathf.Clamp(GlobalAttackRotationMultiplier.Value, 0f, 10f) : 1f); SetAttackRotationFactor(attackInstance, Mathf.Clamp(num, 0f, 999f), category, role, "attack start rotation override"); } } private static void ApplyAttackRotationLockAfterTriggerIfConfigured(object attackInstance, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null) { return; } category = ResolveAttackSettingsCategory(attackInstance, category, role); if (!IsMeleeWeaponCategory(category) || !IsMeleeOrAreaAttackInstance(attackInstance)) { return; } if (AttackInstanceOwners.TryGetValue(attackInstance, out Character value)) { Player val = (Player)(object)((value is Player) ? value : null); if (val != null) { if (TryGetJumpAttackConfigForPlayer(val, category, out JumpAttackConfig cfg)) { if (cfg.LockRotationAfterAttackTrigger.Value) { SetAttackRotationFactor(attackInstance, 0f, category, role, "jump attack post-trigger recovery rotation lock"); } return; } if (TryGetRunningAttackConfigForPlayer(val, category, out RunningAttackConfig cfg2)) { if (cfg2.LockRotationAfterAttackTrigger.Value) { SetAttackRotationFactor(attackInstance, 0f, category, role, "running attack post-trigger recovery rotation lock"); } return; } } } if (GetConfig(category).GetLockRotationAfterAttackTrigger(role)) { SetAttackRotationFactor(attackInstance, 0f, category, role, "post-trigger recovery rotation lock"); } } private static bool IsMeleeOrAreaAttackInstance(object attackInstance) { string text = SafeLower(GetFieldValueRaw(attackInstance, "m_attackType")?.ToString()); if (!text.Contains("horizontal") && !text.Contains("vertical")) { return text.Contains("area"); } return true; } private static void SetAttackRotationFactor(object attackInstance, float factor, WeaponCategory category, AttackRole role, string reason) { FieldInfo field = GetCachedField(attackInstance.GetType(), "m_speedFactorRotation"); if (field == null || field.IsInitOnly || field.IsLiteral || field.FieldType != typeof(float)) { if (Debug(DebugMovement)) { Log.LogInfo((object)$"Attack rotation factor was set for {category} {role}, but Attack.m_speedFactorRotation was not found on {attackInstance.GetType().FullName}."); } return; } try { if (!AttackRotationContexts.TryGetValue(attackInstance, out List value)) { value = new List(); AttackRotationContexts[attackInstance] = value; } if (!value.Any((SuppressedFieldValue v) => v.Target == attackInstance && v.Field == field)) { value.Add(new SuppressedFieldValue(attackInstance, field, field.GetValue(attackInstance))); } field.SetValue(attackInstance, factor); if (Debug(DebugMovement)) { Log.LogInfo((object)$"Attack rotation factor set to {factor.ToString(CultureInfo.InvariantCulture)} for {category} {role} ({reason})."); } } catch { } } private static void RestoreAllAttackRotationTweaks() { foreach (object item in AttackRotationContexts.Keys.ToList()) { RestoreAttackRotationTweaks(item); } AttackRotationContexts.Clear(); } private static void RestoreAttackRotationTweaks(object attackInstance) { if (attackInstance == null || !AttackRotationContexts.TryGetValue(attackInstance, out List value)) { return; } AttackRotationContexts.Remove(attackInstance); foreach (SuppressedFieldValue item in value) { try { item.Field.SetValue(item.Target, item.OriginalValue); } catch { } } } private static float GetRoleMultiplier(Dictionary> map, WeaponCategory category, AttackRole role, float fallback = 1f) { if (!map.TryGetValue(category, out RoleSettings value)) { return fallback; } return value.Get(role).Value; } private static float GetEffectiveAttackStartNoiseMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.StartNoiseMultiplier.Value * ((GlobalAttackStartNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackStartNoiseMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.StartNoiseMultiplier.Value * ((GlobalAttackStartNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackStartNoiseMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackStartNoiseMultipliers, category, role) * ((GlobalAttackStartNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackStartNoiseMultiplier.Value, 0f, 20f) : 1f); } private static float GetEffectiveAttackHitNoiseMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.HitNoiseMultiplier.Value * ((GlobalAttackHitNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackHitNoiseMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.HitNoiseMultiplier.Value * ((GlobalAttackHitNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackHitNoiseMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackHitNoiseMultipliers, category, role) * ((GlobalAttackHitNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackHitNoiseMultiplier.Value, 0f, 20f) : 1f); } private static float GetVanillaWeaponPrefabHitRayRangeMultiplier(Character character, WeaponCategory category) { Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return 1f; } string itemPrefabId = GetItemPrefabId(GetCurrentWeapon((Character)(object)val)); if (string.IsNullOrWhiteSpace(itemPrefabId)) { return 1f; } Dictionary> dictionary = category switch { WeaponCategory.OneHandedSword => OneHandedSwordPrefabHitRayRangeMultipliers, WeaponCategory.Spear => SpearPrefabHitRayRangeMultipliers, _ => null, }; if (dictionary == null || !dictionary.TryGetValue(itemPrefabId, out var value)) { return 1f; } return Mathf.Clamp(value.Value, 0f, 20f); } private static string GetItemPrefabId(ItemData? item) { if (item == null) { return string.Empty; } try { if ((Object)(object)item.m_dropPrefab != (Object)null && !string.IsNullOrWhiteSpace(((Object)item.m_dropPrefab).name)) { return ((Object)item.m_dropPrefab).name; } } catch { } return string.Empty; } private static float GetEffectiveAttackRangeMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.RangeMultiplier.Value * ((GlobalHitRayRangeMultiplier != null) ? Mathf.Clamp(GlobalHitRayRangeMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.RangeMultiplier.Value * ((GlobalHitRayRangeMultiplier != null) ? Mathf.Clamp(GlobalHitRayRangeMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackRangeMultipliers, category, role) * ((GlobalHitRayRangeMultiplier != null) ? Mathf.Clamp(GlobalHitRayRangeMultiplier.Value, 0f, 20f) : 1f); } private static float GetEffectiveAttackRayWidthMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.RayWidthMultiplier.Value * ((GlobalHitRayThicknessMultiplier != null) ? Mathf.Clamp(GlobalHitRayThicknessMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.RayWidthMultiplier.Value * ((GlobalHitRayThicknessMultiplier != null) ? Mathf.Clamp(GlobalHitRayThicknessMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackRayWidthMultipliers, category, role) * ((GlobalHitRayThicknessMultiplier != null) ? Mathf.Clamp(GlobalHitRayThicknessMultiplier.Value, 0f, 20f) : 1f); } private static float GetEffectiveAttackRayWidthAddedThicknessMeters(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (category != WeaponCategory.OneHandedAxe) { return 0f; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { if (OneHandedAxeJumpAttackHitRayAddedThicknessMeters == null) { return 0f; } return Mathf.Clamp(OneHandedAxeJumpAttackHitRayAddedThicknessMeters.Value, 0f, 5f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { return 0f; } if (role != AttackRole.Secondary) { return 0f; } if (OneHandedAxeSecondaryHitRayAddedThicknessMeters == null) { return 0f; } return Mathf.Clamp(OneHandedAxeSecondaryHitRayAddedThicknessMeters.Value, 0f, 5f); } private static float GetEffectiveAttackHitboxHeightMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.HitboxHeightMultiplier.Value * ((GlobalHitRayStartHeightMultiplier != null) ? Mathf.Clamp(GlobalHitRayStartHeightMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.HitboxHeightMultiplier.Value * ((GlobalHitRayStartHeightMultiplier != null) ? Mathf.Clamp(GlobalHitRayStartHeightMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackHitboxHeightMultipliers, category, role) * ((GlobalHitRayStartHeightMultiplier != null) ? Mathf.Clamp(GlobalHitRayStartHeightMultiplier.Value, 0f, 20f) : 1f); } private static float GetEffectiveAttackOffsetMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.OffsetMultiplier.Value * ((GlobalHitRayOffsetMultiplier != null) ? Mathf.Clamp(GlobalHitRayOffsetMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.OffsetMultiplier.Value * ((GlobalHitRayOffsetMultiplier != null) ? Mathf.Clamp(GlobalHitRayOffsetMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackOffsetMultipliers, category, role) * ((GlobalHitRayOffsetMultiplier != null) ? Mathf.Clamp(GlobalHitRayOffsetMultiplier.Value, 0f, 20f) : 1f); } private static float GetEffectiveAttackAngleMultiplier(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.AngleMultiplier.Value * ((GlobalHitRayAngleMultiplier != null) ? Mathf.Clamp(GlobalHitRayAngleMultiplier.Value, 0f, 20f) : 1f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.AngleMultiplier.Value * ((GlobalHitRayAngleMultiplier != null) ? Mathf.Clamp(GlobalHitRayAngleMultiplier.Value, 0f, 20f) : 1f); } return GetRoleMultiplier(AttackAngleMultipliers, category, role) * ((GlobalHitRayAngleMultiplier != null) ? Mathf.Clamp(GlobalHitRayAngleMultiplier.Value, 0f, 20f) : 1f); } private static AttackHitboxType GetRoleHitboxType(Dictionary> map, WeaponCategory category, AttackRole role) { if (!map.TryGetValue(category, out RoleSettings value)) { return DefaultVanillaHitboxType(category, role); } return value.Get(role).Value; } private static AttackHitboxType GetEffectiveAttackHitboxType(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.HitboxType.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.HitboxType.Value; } return GetRoleHitboxType(AttackHitboxTypes, category, role); } private static AttackMultiTargetDamagePenaltyMode GetEffectiveMultiTargetDamagePenaltyMode(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (category == WeaponCategory.Sledge) { return AttackMultiTargetDamagePenaltyMode.Enabled; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.MultiTargetDamagePenalty.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.MultiTargetDamagePenalty.Value; } if (!AttackMultiTargetDamagePenaltyModes.TryGetValue(category, out RoleSettings value)) { return AttackMultiTargetDamagePenaltyMode.Enabled; } return value.Get(role).Value; } private static bool GetEffectiveAttackMultiHitEnabled(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { if (!JumpAttackMultiHitEnabled.TryGetValue(category, out ConfigEntry value)) { return DefaultGooJumpAttackMultiHitEnabled(category); } return value.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { if (!RunningAttackMultiHitEnabled.TryGetValue(category, out ConfigEntry value2)) { return DefaultGooRunningAttackMultiHitEnabled(category); } return value2.Value; } if (!AttackMultiHitEnabled.TryGetValue(category, out RoleSettings value3)) { return DefaultGooMultiHitEnabled(category, role); } return value3.Get(role).Value; } private static bool GetEffectiveAttackHitThroughTargetsEnabled(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (GetEffectiveMultiTargetDamagePenaltyMode(attackInstance, category, role) == AttackMultiTargetDamagePenaltyMode.Enabled) { return false; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { if (!JumpAttackHitThroughTargetsEnabled.TryGetValue(category, out ConfigEntry value)) { return DefaultGooJumpAttackHitThroughMobsEnabled(category); } return value.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { if (!RunningAttackHitThroughTargetsEnabled.TryGetValue(category, out ConfigEntry value2)) { return DefaultGooRunningAttackHitThroughMobsEnabled(category); } return value2.Value; } if (!AttackHitThroughTargetsEnabled.TryGetValue(category, out RoleSettings value3)) { return DefaultGooHitThroughMobsEnabled(category, role); } return value3.Get(role).Value; } private static bool GetEffectiveAttackHitThroughWallsEnabled(object? attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { if (JumpAttackHitThroughWallsEnabled.TryGetValue(category, out ConfigEntry value)) { return value.Value; } return false; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { if (RunningAttackHitThroughWallsEnabled.TryGetValue(category, out ConfigEntry value2)) { return value2.Value; } return false; } if (AttackHitThroughWallsEnabled.TryGetValue(category, out RoleSettings value3)) { return value3.Get(role).Value; } return false; } private static void ApplyMeleeStealthAndHitboxIfConfigured(object attackInstance, Character character, WeaponCategory category, AttackRole role) { if (attackInstance == null || !(character is Player) || !IsMeleeWeaponCategory(category) || !IsMeleeOrAreaAttackInstance(attackInstance)) { return; } bool flag = IsFeatureApplied(category, ModFeature.HitRay); bool flag2 = IsFeatureApplied(category, ModFeature.StealthAttributes); bool flag3 = IsFeatureApplied(category, ModFeature.MultiTargetPenalty); if (!flag && !flag2 && !flag3) { return; } float num = (flag2 ? Mathf.Clamp(GetEffectiveAttackStartNoiseMultiplier(attackInstance, category, role), 0f, 20f) : 1f); float num2 = (flag2 ? Mathf.Clamp(GetEffectiveAttackHitNoiseMultiplier(attackInstance, category, role), 0f, 20f) : 1f); if (category == WeaponCategory.Sledge || category == WeaponCategory.Pickaxe) { if (flag2) { bool flag4 = false; if (!Mathf.Approximately(num, 1f)) { flag4 |= TryMultiplyFloatField(attackInstance, "m_attackStartNoise", num); } if (!Mathf.Approximately(num2, 1f)) { flag4 |= TryMultiplyFloatField(attackInstance, "m_attackHitNoise", num2); } if (flag4 && Debug(DebugAttackLifecycle)) { DebugLogThrottled($"attack-stealth-no-hitray:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Stealth/noise multipliers applied without hit-ray mutation for {SafeName(character)} {category} {role}: startNoise={num.ToString(CultureInfo.InvariantCulture)}, hitNoise={num2.ToString(CultureInfo.InvariantCulture)}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } return; } float num3 = (flag ? Mathf.Clamp(GetEffectiveAttackRangeMultiplier(attackInstance, category, role), 0f, 20f) : 1f); float num4 = (flag ? Mathf.Clamp(GetVanillaWeaponPrefabHitRayRangeMultiplier(character, category), 0f, 20f) : 1f); if (!Mathf.Approximately(num4, 1f)) { num3 *= num4; } float rayWidth = (flag ? Mathf.Clamp(GetEffectiveAttackRayWidthMultiplier(attackInstance, category, role), 0f, 20f) : 1f); float rayWidthAddMeters = (flag ? GetEffectiveAttackRayWidthAddedThicknessMeters(attackInstance, category, role) : 0f); float num5 = (flag ? Mathf.Clamp(GetEffectiveAttackHitboxHeightMultiplier(attackInstance, category, role), 0f, 20f) : 1f); float num6 = (flag ? Mathf.Clamp(GetEffectiveAttackOffsetMultiplier(attackInstance, category, role), 0f, 20f) : 1f); float num7 = (flag ? Mathf.Clamp(GetEffectiveAttackAngleMultiplier(attackInstance, category, role), 0f, 20f) : 1f); AttackHitboxType attackHitboxType = (flag ? GetEffectiveAttackHitboxType(attackInstance, category, role) : DefaultVanillaHitboxType(category, role)); AttackMultiTargetDamagePenaltyMode attackMultiTargetDamagePenaltyMode = (flag3 ? GetEffectiveMultiTargetDamagePenaltyMode(attackInstance, category, role) : AttackMultiTargetDamagePenaltyMode.Enabled); bool value = (flag3 ? GetEffectiveAttackMultiHitEnabled(attackInstance, category, role) : DefaultGooMultiHitEnabled(category, role)); bool value2 = flag3 && GetEffectiveAttackHitThroughWallsEnabled(attackInstance, category, role); bool flag5 = false; if (!Mathf.Approximately(num, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackStartNoise", num); } if (!Mathf.Approximately(num2, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackHitNoise", num2); } if (!Mathf.Approximately(num3, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackRange", num3); } if (UsesZeroWidthHitRayBase(category, role)) { flag5 |= TrySetFloatFieldFromOriginal(attackInstance, "m_attackRayWidth", (float original) => (original + 1f) * rayWidth); flag5 |= TrySetFloatFieldFromOriginal(attackInstance, "m_attackRayWidthCharExtra", (float original) => (original + 1f) * rayWidth); } else if (!Mathf.Approximately(rayWidth, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackRayWidth", rayWidth); flag5 |= TryMultiplyFloatField(attackInstance, "m_attackRayWidthCharExtra", rayWidth); } if (rayWidthAddMeters > 0f) { flag5 |= TrySetFloatFieldFromOriginal(attackInstance, "m_attackRayWidth", (float current) => current + rayWidthAddMeters); flag5 |= TrySetFloatFieldFromOriginal(attackInstance, "m_attackRayWidthCharExtra", (float current) => current + rayWidthAddMeters); } if (!Mathf.Approximately(num5, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackHeight", num5); } if (!Mathf.Approximately(num6, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackOffset", num6); } if (!Mathf.Approximately(num7, 1f)) { flag5 |= TryMultiplyFloatField(attackInstance, "m_attackAngle", num7); } flag5 |= TrySetAttackTypeField(attackInstance, attackHitboxType); if (category != WeaponCategory.Sledge) { flag5 |= TrySetBoolField(attackInstance, "m_multiHit", value); flag5 |= TrySetBoolField(attackInstance, "m_hitThroughWalls", value2); switch (attackMultiTargetDamagePenaltyMode) { case AttackMultiTargetDamagePenaltyMode.Enabled: flag5 |= TrySetBoolField(attackInstance, "m_lowerDamagePerHit", value: true); break; case AttackMultiTargetDamagePenaltyMode.Disabled: flag5 |= TrySetBoolField(attackInstance, "m_lowerDamagePerHit", value: false); break; } } if (flag5 && Debug(DebugAttackLifecycle)) { DebugLogThrottled($"attack-hitbox-stealth:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Attack stealth/hitbox multipliers applied for {SafeName(character)} {category} {role}: startNoise={num.ToString(CultureInfo.InvariantCulture)}, hitNoise={num2.ToString(CultureInfo.InvariantCulture)}, range={num3.ToString(CultureInfo.InvariantCulture)}, prefabRange={num4.ToString(CultureInfo.InvariantCulture)}, thickness={rayWidth.ToString(CultureInfo.InvariantCulture)}, addedThicknessMeters={rayWidthAddMeters.ToString(CultureInfo.InvariantCulture)}, startHeight={num5.ToString(CultureInfo.InvariantCulture)}, offset={num6.ToString(CultureInfo.InvariantCulture)}, angle={num7.ToString(CultureInfo.InvariantCulture)}, type={attackHitboxType}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } private static void ApplyEnemyAttackHitRayIfConfigured(object attackInstance, Character character) { if (attackInstance != null && !((Object)(object)character == (Object)null) && !(character is Player) && IsMeleeOrAreaAttackInstance(attackInstance)) { float num = Mathf.Clamp((EnemySizeMultiplier != null) ? EnemySizeMultiplier.Value : 1f, 0.2f, 5f); float num2 = Mathf.Clamp(((EnemyAttackHitRayRangeMultiplier != null) ? EnemyAttackHitRayRangeMultiplier.Value : 1f) * num, 0f, 20f); float num3 = Mathf.Clamp(((EnemyAttackHitRaySizeMultiplier != null) ? EnemyAttackHitRaySizeMultiplier.Value : 1f) * num, 0f, 20f); float num4 = Mathf.Clamp(((EnemyAttackHitRayHeightMultiplier != null) ? EnemyAttackHitRayHeightMultiplier.Value : 1f) * num, 0f, 20f); float num5 = Mathf.Clamp(((EnemyAttackHitRayOffsetMultiplier != null) ? EnemyAttackHitRayOffsetMultiplier.Value : 1f) * num, 0f, 20f); bool flag = false; if (!Mathf.Approximately(num2, 1f)) { flag |= TryMultiplyFloatField(attackInstance, "m_attackRange", num2); } if (!Mathf.Approximately(num3, 1f)) { flag |= TryMultiplyFloatField(attackInstance, "m_attackRayWidth", num3); flag |= TryMultiplyFloatField(attackInstance, "m_attackRayWidthCharExtra", num3); } if (!Mathf.Approximately(num4, 1f)) { flag |= TryMultiplyFloatField(attackInstance, "m_attackHeight", num4); flag |= TryMultiplyFloatField(attackInstance, "m_attackHeightChar1", num4); flag |= TryMultiplyFloatField(attackInstance, "m_attackHeightChar2", num4); } if (!Mathf.Approximately(num5, 1f)) { flag |= TryMultiplyFloatField(attackInstance, "m_attackOffset", num5); } if (flag && Debug(DebugAttackLifecycle)) { DebugLogThrottled($"enemy-hitray:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Enemy attack hit-ray scaled for {SafeName(character)}: range={num2.ToString(CultureInfo.InvariantCulture)}, size={num3.ToString(CultureInfo.InvariantCulture)}, height={num4.ToString(CultureInfo.InvariantCulture)}, offset={num5.ToString(CultureInfo.InvariantCulture)}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } } private static void ApplyPlayerSizeAttackRangeIfConfigured(object attackInstance, Character character, WeaponCategory category) { if (attackInstance != null && character is Player && IsMeleeWeaponCategory(category) && category != WeaponCategory.Sledge && category != WeaponCategory.Pickaxe) { float num = Mathf.Clamp(PlayerSizeMultiplier.Value, 0.2f, 5f); if (!Mathf.Approximately(num, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_attackRange", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackRayWidth", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackRayWidthCharExtra", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackHeight", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackHeightChar1", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackHeightChar2", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_attackOffset", num) ? 1u : 0u)) != 0 && Debug(DebugAttackLifecycle)) { DebugLogThrottled($"player-size-attack-range:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Player-size attack reach scaled by {num.ToString(CultureInfo.InvariantCulture)} for {SafeName(character)} {category}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } } private static void ApplyRangedStealthIfConfigured(object attackInstance, Character character, ItemData weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null || weapon == null || !(character is Player)) { return; } float num = 1f; float num2 = 1f; bool flag = false; if (TryGetStaffConfig(weapon, out StaffConfig cfg)) { num = Mathf.Clamp(cfg.AttackStartNoiseMultiplier.Value, 0f, 20f) * ((GlobalAttackStartNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackStartNoiseMultiplier.Value, 0f, 20f) : 1f); num2 = Mathf.Clamp(cfg.AttackHitNoiseMultiplier.Value, 0f, 20f) * ((GlobalAttackHitNoiseMultiplier != null) ? Mathf.Clamp(GlobalAttackHitNoiseMultiplier.Value, 0f, 20f) : 1f); flag = true; } else if (IsFeatureApplied(category, ModFeature.StealthAttributes)) { num = Mathf.Clamp(GetEffectiveAttackStartNoiseMultiplier(attackInstance, category, role), 0f, 20f); num2 = Mathf.Clamp(GetEffectiveAttackHitNoiseMultiplier(attackInstance, category, role), 0f, 20f); flag = true; } if (flag) { bool flag2 = false; if (!Mathf.Approximately(num, 1f)) { flag2 |= TryMultiplyFloatField(attackInstance, "m_attackStartNoise", num); } if (!Mathf.Approximately(num2, 1f)) { flag2 |= TryMultiplyFloatField(attackInstance, "m_attackHitNoise", num2); } if (flag2 && Debug(DebugAttackLifecycle)) { DebugLogThrottled($"ranged-stealth-noise:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Ranged/staff stealth noise multipliers applied for {SafeName(character)} {category} {role}: startNoise={num.ToString(CultureInfo.InvariantCulture)}, hitNoise={num2.ToString(CultureInfo.InvariantCulture)}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } } private static bool IsBombThrowableProjectile(GameObject? projectilePrefab) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 if ((Object)(object)projectilePrefab == (Object)null) { return false; } try { Projectile val = projectilePrefab.GetComponent() ?? projectilePrefab.GetComponentInChildren(true); return (Object)(object)val != (Object)null && (val.m_type & 4) > 0; } catch { return false; } } private static bool IsMagicProjectileAttack(object? attackInstance) { if (attackInstance == null) { return false; } if (!string.Equals(GetFieldValueRaw(attackInstance, "m_attackType")?.ToString() ?? string.Empty, "Projectile", StringComparison.OrdinalIgnoreCase)) { return false; } object? fieldValueRaw = GetFieldValueRaw(attackInstance, "m_attackProjectile"); GameObject val = (GameObject)((fieldValueRaw is GameObject) ? fieldValueRaw : null); if ((Object)(object)val == (Object)null) { return false; } try { return val.GetComponent() != null || val.GetComponentInChildren(true) != null; } catch { return false; } } private static bool IsThrowableAttack(object? attackInstance, ItemData? weapon) { object obj = attackInstance; if (obj == null && weapon?.m_shared != null) { obj = weapon.m_shared.m_attack; } if (obj == null) { return false; } if (!string.Equals(GetFieldValueRaw(obj, "m_attackType")?.ToString() ?? string.Empty, "Projectile", StringComparison.OrdinalIgnoreCase)) { return false; } object? fieldValueRaw = GetFieldValueRaw(obj, "m_attackProjectile"); return IsBombThrowableProjectile((GameObject?)((fieldValueRaw is GameObject) ? fieldValueRaw : null)); } private static bool TryGetOriginatingAttackInstance(Player player, out object? attackInstance) { attackInstance = null; if (CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count > 0) { AttackRuntimeState attackRuntimeState = CurrentAttackHitContexts.Peek(); if (attackRuntimeState.AttackInstance != null) { attackInstance = attackRuntimeState.AttackInstance; return true; } } if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.AttackInstance != null && value.EndTime >= Time.time) { attackInstance = value.AttackInstance; return true; } attackInstance = GetFieldValueRaw(player, "m_currentAttack"); return attackInstance != null; } private static void ApplyRangedAttackTweaksIfConfigured(object attackInstance, Character character, ItemData weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null || weapon == null || !(character is Player)) { return; } ApplyRangedStealthIfConfigured(attackInstance, character, weapon, category, role); if (IsThrowableAttack(attackInstance, weapon)) { float num = ((ThrowableProjectileSpeedMultiplier != null) ? Mathf.Clamp(ThrowableProjectileSpeedMultiplier.Value, 0f, 20f) : 1f); bool flag = false; if (!Mathf.Approximately(num, 1f)) { flag |= TryMultiplyFloatField(attackInstance, "m_projectileVel", num); flag |= TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num); } if (Debug(DebugRanged)) { DebugLogThrottled($"throwable-ranged:{((Object)character).GetInstanceID()}:{RuntimeHelpers.GetHashCode(attackInstance)}", 0.25f, $"Throwable launch tuning resolved for {SafeName(character)}: projectileSpeed={num.ToString(CultureInfo.InvariantCulture)}, changed={flag}, attackHash={RuntimeHelpers.GetHashCode(attackInstance)}. Damage is applied once at the propagated projectile/AOE HitData stage."); } } if (TryGetStaffConfig(weapon, out StaffConfig cfg)) { if (cfg.SpreadMultiplier != null) { float num2 = Mathf.Clamp(cfg.SpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num2, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num2) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num2) ? 1u : 0u)) != 0 && Debug(DebugRanged)) { DebugLogThrottled("staff-spread:" + ((Object)character).GetInstanceID(), 1f, "Staff spread fields multiplied by " + num2.ToString(CultureInfo.InvariantCulture) + " for " + cfg.PrefabName + "."); } } if (cfg.ProjectileSpeedMultiplier != null && IsMagicProjectileAttack(attackInstance)) { float num3 = Mathf.Clamp(cfg.ProjectileSpeedMultiplier.Value, 0f, 20f); if (!Mathf.Approximately(num3, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_projectileVel", num3) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num3) ? 1u : 0u)) != 0 && Debug(DebugRanged)) { DebugLogThrottled($"staff-projectile-speed:{cfg.PrefabName}:{((Object)character).GetInstanceID()}", 0.5f, $"Magic projectile-speed fields multiplied by {num3.ToString(CultureInfo.InvariantCulture)} for {cfg.PrefabName}; attackHash={RuntimeHelpers.GetHashCode(attackInstance)}."); } } if (IsStaffLightning(cfg)) { float num4 = Mathf.Clamp(StaffLightningProjectileCountMultiplier.Value, 0.01f, 10f); if (!Mathf.Approximately(num4, 1f)) { TryMultiplyIntField(attackInstance, "m_projectiles", num4); } float num5 = Mathf.Clamp(StaffLightningRecoilStrengthMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num5, 1f)) { TryMultiplyFloatField(attackInstance, "m_recoilPushback", num5); } } } switch (category) { case WeaponCategory.Bow: { float num10 = Mathf.Clamp(BowSpreadMultiplier.Value * BowBaseSpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num10, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num10) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num10) ? 1u : 0u)) != 0 && Debug(DebugRanged)) { DebugLogThrottled("bow-spread:" + ((Object)character).GetInstanceID(), 1f, "Bow accuracy/spread fields multiplied by " + num10.ToString(CultureInfo.InvariantCulture) + " on cloned Attack for " + SafeName(character) + "."); } float num11 = Mathf.Clamp(BowProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num11, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num11); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num11); } break; } case WeaponCategory.Crossbow: { float num7 = Mathf.Clamp(CrossbowBaseSpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num7, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num7); TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num7); } float num8 = Mathf.Clamp(CrossbowProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num8, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num8); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num8); } float num9 = Mathf.Clamp(CrossbowRecoilStrengthMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num9, 1f)) { TryMultiplyFloatField(attackInstance, "m_recoilPushback", num9); } break; } case WeaponCategory.Spear: if (role == AttackRole.Secondary) { float num6 = Mathf.Clamp(SpearProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num6, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num6); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num6); } } break; } } private static void RestoreAllAttackRangedFieldTweaks() { foreach (object item in AttackRangedFieldContexts.Keys.ToList()) { RestoreAttackRangedFieldTweaks(item); } AttackRangedFieldContexts.Clear(); } private static void RestoreAttackRangedFieldTweaks(object attackInstance) { if (attackInstance == null || !AttackRangedFieldContexts.TryGetValue(attackInstance, out List value)) { return; } AttackRangedFieldContexts.Remove(attackInstance); foreach (SuppressedFieldValue item in value) { try { item.Field.SetValue(item.Target, item.OriginalValue); } catch { } } } private static void CaptureRangedFieldOriginal(object target, FieldInfo field) { if (target != null && !(field == null)) { if (!AttackRangedFieldContexts.TryGetValue(target, out List value)) { value = new List(); AttackRangedFieldContexts[target] = value; } if (!value.Any((SuppressedFieldValue v) => v.Target == target && v.Field == field)) { value.Add(new SuppressedFieldValue(target, field, field.GetValue(target))); } } } private static bool TrySetBoolField(object target, string fieldName, bool value) { if (target == null) { return false; } FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(bool) || cachedField.IsInitOnly || cachedField.IsLiteral) { return false; } try { CaptureRangedFieldOriginal(target, cachedField); cachedField.SetValue(target, value); return true; } catch { return false; } } private static bool TrySetAttackTypeField(object target, AttackHitboxType hitboxType) { if (target == null) { return false; } FieldInfo cachedField = GetCachedField(target.GetType(), "m_attackType"); if (cachedField == null || !cachedField.FieldType.IsEnum || cachedField.IsInitOnly || cachedField.IsLiteral) { return false; } string value = ((hitboxType == AttackHitboxType.Vertical) ? "Vertical" : "Horizontal"); try { object value2 = Enum.Parse(cachedField.FieldType, value); CaptureRangedFieldOriginal(target, cachedField); cachedField.SetValue(target, value2); return true; } catch { return false; } } private static bool UsesZeroWidthHitRayBase(WeaponCategory category, AttackRole role) { if (role == AttackRole.Secondary) { return category == WeaponCategory.DualAxes; } return false; } private static bool TrySetFloatFieldFromOriginal(object target, string fieldName, Func transform) { if (target == null || transform == null) { return false; } FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(float) || cachedField.IsInitOnly || cachedField.IsLiteral) { return false; } try { CaptureRangedFieldOriginal(target, cachedField); float arg = (float)cachedField.GetValue(target); cachedField.SetValue(target, transform(arg)); return true; } catch { return false; } } private static bool TryMultiplyFloatField(object target, string fieldName, float multiplier) { if (target == null) { return false; } FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(float) || cachedField.IsInitOnly || cachedField.IsLiteral) { return false; } try { CaptureRangedFieldOriginal(target, cachedField); float num = (float)cachedField.GetValue(target); cachedField.SetValue(target, num * multiplier); return true; } catch { return false; } } private static bool TryMultiplyIntField(object target, string fieldName, float multiplier) { if (target == null) { return false; } FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(int) || cachedField.IsInitOnly || cachedField.IsLiteral) { return false; } try { CaptureRangedFieldOriginal(target, cachedField); int num = (int)cachedField.GetValue(target); int num2 = Mathf.Max(1, Mathf.RoundToInt((float)num * multiplier)); cachedField.SetValue(target, num2); return true; } catch { return false; } } private static void ApplyDodgeAnimationSpeedIfConfigured(Player player, float multiplier) { if ((Object)(object)player == (Object)null || DodgeAnimationSpeedContexts.ContainsKey(player)) { return; } List list = new List(); try { Animator[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { float num = NormalizeAnimationOriginalSpeed((Character)(object)player, val.speed); val.speed = num * multiplier; list.Add(new AnimatorSpeedValue(val, num, multiplier)); } } } catch { } object obj2 = GetFieldValue(player, "m_zanim") ?? FindFirstFieldValueByName(player, "zanim"); if (obj2 != null) { TryMultiplyWrapperAnimationSpeed(obj2, multiplier, list); } if (list.Count > 0) { DodgeAnimationSpeedContexts[player] = list; if (Debug(DebugPlayerSystems)) { DebugLogThrottled("dodge-anim:" + ((Object)player).GetInstanceID(), 1f, "RollAnimationSpeed x" + multiplier.ToString(CultureInfo.InvariantCulture) + " applied to " + SafeName((Character)(object)player) + "."); } } } private static void RestoreAllDodgeAnimationSpeedTweaks() { foreach (Player item in DodgeAnimationSpeedContexts.Keys.ToList()) { RestoreDodgeAnimationSpeedTweaks(item); } DodgeAnimationSpeedContexts.Clear(); } private static void RestoreDodgeAnimationSpeedTweaks(Player player) { if ((Object)(object)player == (Object)null || !DodgeAnimationSpeedContexts.TryGetValue(player, out List value)) { return; } DodgeAnimationSpeedContexts.Remove(player); foreach (AnimatorSpeedValue item in value) { try { item.Restore(); } catch { } } } private static void ApplyEnemyAttackAnimationSpeedIfConfigured(object attackInstance, Character character) { if (attackInstance == null || (Object)(object)character == (Object)null || character is Player) { return; } float num = Mathf.Clamp(((EnemySpeedMultiplier != null) ? EnemySpeedMultiplier.Value : 1f) * EnemyAttackAnimationSpeedMultiplier.Value, 0.05f, 10f); if (Mathf.Approximately(num, 1f) || AttackAnimationSpeedContexts.ContainsKey(attackInstance)) { return; } List list = new List(); try { Animator[] componentsInChildren = ((Component)character).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { float num2 = NormalizeAnimationOriginalSpeed(character, val.speed); val.speed = num2 * num; list.Add(new AnimatorSpeedValue(val, num2, num)); } } } catch { } object obj2 = GetFieldValue(character, "m_zanim") ?? FindFirstFieldValueByName(character, "zanim"); if (obj2 != null) { TryMultiplyWrapperAnimationSpeed(obj2, num, list); } if (list.Count > 0) { AttackAnimationSpeedContexts[attackInstance] = list; } } private static bool UseCharacterAnimEventSpeedMultiplier(WeaponCategory category, AttackRole role) { if ((category != WeaponCategory.OneHandedSword || role != AttackRole.Secondary) && (category != WeaponCategory.DualAxes || role != AttackRole.Secondary)) { return category == WeaponCategory.Unarmed; } return true; } private static bool UseCharacterAnimEventOnlySpeedMultiplier(WeaponCategory category, AttackRole role) { if (category != WeaponCategory.OneHandedSword || role != AttackRole.Secondary) { return category == WeaponCategory.Unarmed; } return true; } private static float GetResolvedAttackAnimationSpeedMultiplierForContext(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); Player val = (Player)(object)((character is Player) ? character : null); float num; if (val != null && TryGetJumpAttackAnimationSpeedMultiplier(val, category, out var multiplier)) { num = multiplier; } else { Player val2 = (Player)(object)((character is Player) ? character : null); num = ((val2 == null || !TryGetRunningAttackAnimationSpeedMultiplier(val2, category, out var multiplier2)) ? Mathf.Clamp(GetConfig(category).GetAttackAnimationSpeedMultiplier(role), 0.05f, 5f) : multiplier2); } if (TryGetStaffConfig(weapon, out StaffConfig cfg)) { num *= Mathf.Clamp(cfg.AttackAnimationSpeedMultiplier.Value, 0.05f, 5f); } if (IsThrowableAttack(attackInstance, weapon)) { num *= ((ThrowableThrowAnimationSpeedMultiplier != null) ? Mathf.Clamp(ThrowableThrowAnimationSpeedMultiplier.Value, 0.05f, 10f) : 1f); } num *= ((GlobalAttackAnimationSpeedMultiplier != null) ? Mathf.Clamp(GlobalAttackAnimationSpeedMultiplier.Value, 0.05f, 10f) : 1f); return Mathf.Clamp(num, 0.05f, 5f); } private static float GetResolvedRecoveryAnimationSpeedMultiplier(Player player, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(GetFieldValueRaw(player, "m_currentAttack"), category, role); float num = (TryGetJumpAttackRecoveryAnimationSpeedMultiplier(player, category, out var multiplier) ? multiplier : ((!TryGetRunningAttackRecoveryAnimationSpeedMultiplier(player, category, out var multiplier2)) ? Mathf.Clamp(GetConfig(category).GetRecoveryAnimationSpeedMultiplier(role), 0.05f, 5f) : multiplier2)); num *= ((GlobalRecoveryAnimationSpeedMultiplier != null) ? Mathf.Clamp(GlobalRecoveryAnimationSpeedMultiplier.Value, 0.05f, 10f) : 1f); return Mathf.Clamp(num, 0.05f, 5f); } private static void ApplyRecoveryAnimationSpeedIfConfigured(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val != null && !RecoveryAnimationSpeedAppliedAttacks.Contains(attackInstance) && !BlockCanceledAttacks.Contains(attackInstance)) { float resolvedRecoveryAnimationSpeedMultiplier = GetResolvedRecoveryAnimationSpeedMultiplier(val, category, role); if (!Mathf.Approximately(resolvedRecoveryAnimationSpeedMultiplier, 1f)) { float multiplier = Mathf.Clamp(GetResolvedAttackAnimationSpeedMultiplierForContext(attackInstance, character, weapon, category, role) * resolvedRecoveryAnimationSpeedMultiplier, 0.05f, 5f); RestoreAttackAnimationSpeedTweaks(attackInstance); RecoveryAnimationSpeedAppliedAttacks.Add(attackInstance); ApplyAttackAnimationSpeedDirect(attackInstance, character, multiplier, $"RecoveryAnimationSpeedMultiplier x{resolvedRecoveryAnimationSpeedMultiplier.ToString(CultureInfo.InvariantCulture)} applied for {SafeName(character)} {category} {role}; final animation speed x{multiplier.ToString(CultureInfo.InvariantCulture)}."); } } } private static void TryHandleBlockCancelDuringAttack(Player player, object attackInstance, ItemData? weapon, WeaponCategory category, AttackRole role) { if ((Object)(object)player == (Object)null || attackInstance == null || weapon == null || BlockCanceledAttacks.Contains(attackInstance) || !IsMeleeWeaponCategory(category) || !IsMeleeOrAreaAttackInstance(attackInstance) || !BlockCancelRequestedAttacks.Contains(attackInstance)) { return; } BlockCancelMode effectiveBlockCancelMode = GetEffectiveBlockCancelMode(player, attackInstance, category, role); bool flag = LungeHitboxTriggeredAttacks.Contains(attackInstance); if (effectiveBlockCancelMode == BlockCancelMode.Disabled) { BlockCancelRequestedAttacks.Remove(attackInstance); return; } if (!CanStartBlockCancel(effectiveBlockCancelMode, attackInstance)) { if (effectiveBlockCancelMode == BlockCancelMode.BeforeHitbox && flag) { BlockCancelRequestedAttacks.Remove(attackInstance); } return; } if (GetActiveBlocker(player)?.m_shared == null) { BlockCancelRequestedAttacks.Remove(attackInstance); return; } float num = GetResolvedAttackAnimationSpeedMultiplierForContext(attackInstance, (Character)(object)player, weapon, category, role); if (RecoveryAnimationSpeedAppliedAttacks.Contains(attackInstance)) { num *= GetResolvedRecoveryAnimationSpeedMultiplier(player, category, role); } float effectiveBlockCancelAnimationSpeedMultiplier = GetEffectiveBlockCancelAnimationSpeedMultiplier(player, attackInstance, category, role); num = Mathf.Clamp(num * effectiveBlockCancelAnimationSpeedMultiplier, 0.05f, 5f); RestoreAttackAnimationSpeedTweaks(attackInstance); BlockCancelRequestedAttacks.Remove(attackInstance); BlockCanceledAttacks.Add(attackInstance); BlockCancelAnimationSpeedByAttack[attackInstance] = effectiveBlockCancelAnimationSpeedMultiplier; ApplyAttackAnimationSpeedDirect(attackInstance, (Character)(object)player, num, $"BlockCancelMode {effectiveBlockCancelMode} accelerated {SafeName((Character)(object)player)} {category} {role} attack animation by x{effectiveBlockCancelAnimationSpeedMultiplier.ToString(CultureInfo.InvariantCulture)}; final animation speed x{num.ToString(CultureInfo.InvariantCulture)}."); } private static float GetEffectiveBlockCancelAnimationSpeedMultiplier(Player player, object attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return Mathf.Clamp(config.BlockCancelAnimationSpeedMultiplier.Value, 0.05f, 5f); } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return Mathf.Clamp(config2.BlockCancelAnimationSpeedMultiplier.Value, 0.05f, 5f); } return Mathf.Clamp(GetConfig(category).GetBlockCancelAnimationSpeedMultiplier(role), 0.05f, 5f); } private static BlockCancelMode GetEffectiveBlockCancelMode(Player player, object attackInstance, WeaponCategory category, AttackRole role) { category = ResolveAttackSettingsCategory(attackInstance, category, role); if (attackInstance != null && JumpAttackInstances.Contains(attackInstance) && TryGetJumpAttackConfig(category, out JumpAttackConfig config)) { return config.BlockCancelMode.Value; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance) && TryGetRunningAttackConfig(category, out RunningAttackConfig config2)) { return config2.BlockCancelMode.Value; } return GetConfig(category).GetBlockCancelMode(role); } private static bool CanStartBlockCancel(BlockCancelMode mode, object attackInstance) { if (attackInstance == null) { return false; } bool flag = LungeHitboxTriggeredAttacks.Contains(attackInstance); return mode switch { BlockCancelMode.BeforeHitbox => !flag, BlockCancelMode.AfterHitbox => flag, BlockCancelMode.FullAnimation => true, _ => false, }; } private static bool ShouldSuppressHitboxForBlockCancel(object attackInstance) { if (attackInstance != null && BlockCanceledAttacks.Contains(attackInstance)) { return !LungeHitboxTriggeredAttacks.Contains(attackInstance); } return false; } private static void ApplyAttackAnimationSpeedDirect(object attackInstance, Character character, float multiplier, string debugMessage) { if (attackInstance == null || (Object)(object)character == (Object)null) { return; } multiplier = Mathf.Clamp(multiplier, 0.05f, 5f); if (Mathf.Approximately(multiplier, 1f) || AttackAnimationSpeedContexts.ContainsKey(attackInstance)) { return; } List list = new List(); try { Animator[] componentsInChildren = ((Component)character).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { float num = NormalizeAnimationOriginalSpeed(character, val.speed); val.speed = num * multiplier; list.Add(new AnimatorSpeedValue(val, num, multiplier)); } } } catch { } object obj2 = GetFieldValue(character, "m_zanim") ?? FindFirstFieldValueByName(character, "zanim"); if (obj2 != null) { TryMultiplyWrapperAnimationSpeed(obj2, multiplier, list); } if (list.Count > 0) { AttackAnimationSpeedContexts[attackInstance] = list; if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)debugMessage); } } else if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)"Animation speed multiplier was configured, but no Animator/ZSyncAnimation speed target was found."); } } private static float NormalizeAnimationOriginalSpeed(Character character, float originalSpeed) { if (originalSpeed > 0.001f) { return originalSpeed; } try { object fieldValueRaw = GetFieldValueRaw(character, "m_zanim"); if (fieldValueRaw != null && GetFieldValueRaw(fieldValueRaw, "m_animSpeed") is float num && num > 0.001f) { return num; } } catch { } return 1f; } private static void ApplyAttackAnimationSpeedIfConfigured(object attackInstance, Character character, ItemData? weapon, WeaponCategory category, AttackRole role) { if (attackInstance == null || (Object)(object)character == (Object)null) { return; } float resolvedAttackAnimationSpeedMultiplierForContext = GetResolvedAttackAnimationSpeedMultiplierForContext(attackInstance, character, weapon, category, role); if (Mathf.Approximately(resolvedAttackAnimationSpeedMultiplierForContext, 1f) || (character is Player && UseCharacterAnimEventOnlySpeedMultiplier(category, role)) || AttackAnimationSpeedContexts.ContainsKey(attackInstance)) { return; } List list = new List(); try { Animator[] componentsInChildren = ((Component)character).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { float num = NormalizeAnimationOriginalSpeed(character, val.speed); val.speed = num * resolvedAttackAnimationSpeedMultiplierForContext; list.Add(new AnimatorSpeedValue(val, num, resolvedAttackAnimationSpeedMultiplierForContext)); } } } catch { } object obj2 = GetFieldValue(character, "m_zanim") ?? FindFirstFieldValueByName(character, "zanim"); if (obj2 != null) { TryMultiplyWrapperAnimationSpeed(obj2, resolvedAttackAnimationSpeedMultiplierForContext, list); } if (list.Count > 0) { AttackAnimationSpeedContexts[attackInstance] = list; if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)$"AttackAnimationSpeedMultiplier x{resolvedAttackAnimationSpeedMultiplierForContext.ToString(CultureInfo.InvariantCulture)} applied for {SafeName(character)} {category} {role} to {list.Count} animator/wrapper target(s)."); } } else if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)$"AttackAnimationSpeedMultiplier was set for {category} {role}, but no Animator/ZSyncAnimation speed target was found."); } } private static void ReapplyAttackAnimationSpeedTweaksIfConfigured(object attackInstance) { if (attackInstance == null || !AttackAnimationSpeedContexts.TryGetValue(attackInstance, out List value)) { return; } foreach (AnimatorSpeedValue item in value) { try { item.Reapply(); } catch { } } } private static void TryMultiplyWrapperAnimationSpeed(object target, float multiplier, List originals) { if (target == null) { return; } FieldInfo[] fields = target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.IsInitOnly || fieldInfo.IsLiteral || fieldInfo.FieldType != typeof(float)) { continue; } string text = SafeLower(fieldInfo.Name).Replace("_", string.Empty).Replace("-", string.Empty); if (!text.Contains("animationspeed") && !text.Contains("animspeed") && !(text == "speed") && !(text == "mspeed")) { continue; } try { if (fieldInfo.GetValue(target) is float num) { fieldInfo.SetValue(target, num * multiplier); originals.Add(new AnimatorSpeedValue(target, fieldInfo, num, multiplier)); } } catch { } } } private static object? FindFirstFieldValueByName(object instance, string nameContains) { if (instance == null) { return null; } string value = SafeLower(nameContains); try { FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!SafeLower(fieldInfo.Name).Contains(value)) { continue; } try { object value2 = fieldInfo.GetValue(instance); if (value2 != null) { return value2; } } catch { } } } catch { } return null; } private static void RestoreAllAttackAnimationSpeedTweaks() { foreach (object item in AttackAnimationSpeedContexts.Keys.ToList()) { RestoreAttackAnimationSpeedTweaks(item); } AttackAnimationSpeedContexts.Clear(); } private static void RestoreAttackAnimationSpeedTweaks(object attackInstance) { if (attackInstance == null || !AttackAnimationSpeedContexts.TryGetValue(attackInstance, out List value)) { return; } AttackAnimationSpeedContexts.Remove(attackInstance); foreach (AnimatorSpeedValue item in value) { try { item.Restore(); } catch { } } } private static void ApplyOutgoingStaggerCalibration(Character? attacker, HitData hit) { if ((Object)(object)attacker == (Object)null || !(attacker is Player)) { return; } ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory(attacker, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && TryGetJumpAttackStaggerMultiplier(val, category, out var multiplier)) { if (!Mathf.Approximately(multiplier, 1f)) { hit.m_staggerMultiplier *= multiplier; } } else { Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (val2 != null && TryGetRunningAttackStaggerMultiplier(val2, category, out var multiplier2)) { if (!Mathf.Approximately(multiplier2, 1f)) { hit.m_staggerMultiplier *= multiplier2; } } else { CategoryConfig config = GetConfig(category); StaggerApplicationMode staggerMode = config.GetStaggerMode(currentAttackRole); float num = Mathf.Max(0f, config.GetStaggerPowerValue(currentAttackRole)); if (staggerMode == StaggerApplicationMode.SetFinalMultiplier) { if (!Mathf.Approximately(hit.m_staggerMultiplier, num)) { hit.m_staggerMultiplier = num; } } else if (!Mathf.Approximately(num, 1f)) { hit.m_staggerMultiplier *= num; } } } Player val3 = (Player)(object)((attacker is Player) ? attacker : null); if (val3 != null && TryGetAirborneMeleeStaggerMultiplier(val3, category, currentAttackRole, out var multiplier3) && !Mathf.Approximately(multiplier3, 1f)) { hit.m_staggerMultiplier *= multiplier3; } if (attacker is Player) { hit.m_staggerMultiplier *= Mathf.Clamp(GlobalPlayerStaggerPowerMultiplier.Value, 0f, 9999f); } if (TryGetAttackerStaffConfig(attacker, out StaffConfig cfg) && cfg.DirectAttack && cfg.StaggerMultiplier != null) { float num2 = Mathf.Clamp(cfg.StaggerMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num2, 1f)) { hit.m_staggerMultiplier *= num2; } } } private static void ApplyPvpStaggerCalibration(Character victim, Character? attacker, HitData hit) { if (!EnablePvpDamageModifiers.Value || (Object)(object)attacker == (Object)null || hit == null) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || !IsPvpLikeTarget(victim) || (Object)(object)attacker == (Object)(object)victim) { return; } ItemData currentWeapon = GetCurrentWeapon(attacker); WeaponCategory category = ResolveActiveAttackCategory((Character)(object)val, currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); float configuredPvpStaggerDuration = GetConfiguredPvpStaggerDuration(val, category, currentAttackRole); if (TryGetAttackerStaffConfig(attacker, out StaffConfig cfg) && cfg.DirectAttack && cfg.PvpStaggerMultiplier != null) { if (configuredPvpStaggerDuration <= 0f) { hit.m_staggerMultiplier = 0f; return; } StoreConfiguredPvpStaggerDuration(hit, configuredPvpStaggerDuration); hit.m_staggerMultiplier *= Mathf.Clamp(cfg.PvpStaggerMultiplier.Value, 0f, 9999f); return; } if (configuredPvpStaggerDuration <= 0f) { hit.m_staggerMultiplier = 0f; return; } StoreConfiguredPvpStaggerDuration(hit, configuredPvpStaggerDuration); float multiplier; float multiplier2; float num = (TryGetJumpAttackPvpStaggerMultiplier(val, category, out multiplier) ? multiplier : ((!TryGetRunningAttackPvpStaggerMultiplier(val, category, out multiplier2)) ? Mathf.Clamp(GetConfig(category).GetPvpStaggerMultiplier(currentAttackRole), 0f, 9999f) : multiplier2)); if (!Mathf.Approximately(num, 1f)) { hit.m_staggerMultiplier *= num; } } private static float GetConfiguredPvpStaggerDuration(Player playerAttacker, WeaponCategory category, AttackRole role) { if ((Object)(object)playerAttacker != (Object)null && TryGetJumpAttackPvpStaggerDuration(playerAttacker, category, out var duration)) { return duration; } if ((Object)(object)playerAttacker != (Object)null && TryGetRunningAttackPvpStaggerDuration(playerAttacker, category, out var duration2)) { return duration2; } return Mathf.Clamp(GetConfig(category).GetPvpStaggerDurationSeconds(role), 0f, 10f); } private static DamagePatchState? ApplyHyperArmorIfActive(Character victim, Character? attacker, HitData hit) { if (!(victim is Player)) { return null; } if (hit == null) { return null; } if (!TryGetActiveHyperArmor(victim, out WeaponCategory category, out AttackRole role, out object attackInstance)) { if (DebugHyperArmorDamageMitigation != null && DebugHyperArmorDamageMitigation.Value) { Log.LogInfo((object)("Hyperarmor mitigation skipped: no active hyperarmor for " + SafeName(victim) + ".")); } return null; } if ((!((Object)(object)attacker != (Object)null) || !((Object)(object)attacker != (Object)(object)victim)) && !MitigateUnknownAttackerDamageDuringHyperArmor.Value) { if (DebugHyperArmorDamageMitigation != null && DebugHyperArmorDamageMitigation.Value) { Log.LogInfo((object)("Hyperarmor mitigation skipped for " + SafeName(victim) + ": attacker unknown and unknown-source mitigation disabled.")); } return null; } float num = Mathf.Clamp(GetEffectiveHyperArmorDamageTakenMultiplier(attackInstance, category, role), 0f, 10f) * Mathf.Clamp(GlobalHyperArmorDamageTakenMultiplier.Value, 0f, 10f); float num2 = Mathf.Max(0f, GetEffectiveHyperArmorKnockbackTakenMultiplier(attackInstance, category, role)) * Mathf.Clamp(GlobalHyperArmorKnockbackTakenMultiplier.Value, 0f, 10f); if (attacker is Player && IsPvpLikeTarget(victim) && (Object)(object)attacker != (Object)(object)victim) { num *= Mathf.Clamp(GetEffectivePvpHyperArmorDamageTakenMultiplier(attackInstance, category, role), 0f, 10f); float num3 = Mathf.Max(0f, GetEffectivePvpHyperArmorStaggerTakenMultiplier(attackInstance, category, role)); if (!Mathf.Approximately(num3, 1f)) { hit.m_staggerMultiplier *= num3; } num2 *= Mathf.Max(0f, GetEffectivePvpHyperArmorKnockbackTakenMultiplier(attackInstance, category, role)); } DamagePatchState damagePatchState = null; if (!Mathf.Approximately(num2, 1f)) { hit.m_pushForce *= num2; } if (Mathf.Approximately(num, 1f)) { return null; } float characterHealth = GetCharacterHealth(victim); if (characterHealth <= 0f) { if (DebugHyperArmorDamageMitigation != null && DebugHyperArmorDamageMitigation.Value) { Log.LogInfo((object)("Hyperarmor mitigation skipped for " + SafeName(victim) + ": could not read pre-hit health.")); } return null; } damagePatchState = new DamagePatchState(victim, characterHealth, num); if (HyperArmorMitigationMode.Value == HyperArmorDamageMitigationMode.RawHitData || HyperArmorMitigationMode.Value == HyperArmorDamageMitigationMode.RawHitDataAndFinalHealthRestore) { ((DamageTypes)(ref hit.m_damage)).Modify(num); if (DebugHyperArmorDamageMitigation != null && DebugHyperArmorDamageMitigation.Value) { Log.LogInfo((object)$"Hyperarmor raw HitData mitigation x{num.ToString(CultureInfo.InvariantCulture)} for {SafeName(victim)} using {category} {role}."); } } return damagePatchState; } private static void ApplyHyperArmorFinalDamageReduction(DamagePatchState state) { if (state != null && !((Object)(object)state.Victim == (Object)null) && HyperArmorMitigationMode.Value != HyperArmorDamageMitigationMode.RawHitData) { ApplyHyperArmorFinalDamageReductionNow(state); if ((Object)(object)Instance != (Object)null && ((Behaviour)Instance).isActiveAndEnabled) { ((MonoBehaviour)Instance).StartCoroutine(ApplyHyperArmorFinalDamageReductionDelayed(state)); } } } private static void ApplyHyperArmorFinalDamageReductionNow(DamagePatchState state) { Character victim = state.Victim; if ((Object)(object)victim == (Object)null || IsCharacterDead(victim)) { return; } float characterHealth = GetCharacterHealth(victim); if (state.HealthBefore <= 0f || characterHealth < 0f) { return; } float num = state.HealthBefore - characterHealth; if (num <= 0.0001f) { return; } float num2 = Mathf.Clamp(state.DamageMultiplier, 0f, 10f); if (Mathf.Approximately(num2, 1f)) { return; } float num3 = num * num2; float num4 = Mathf.Min(state.HealthBefore, state.HealthBefore - num3); if (!(num4 <= characterHealth + 0.0001f)) { state.TargetHealthAfterMitigation = num4; SetCharacterHealthDirect(victim, num4); if (DebugLogging != null && DebugLogging.Value) { Log.LogInfo((object)("Hyperarmor mitigation for " + SafeName(victim) + ": loss " + num.ToString(CultureInfo.InvariantCulture) + " -> " + num3.ToString(CultureInfo.InvariantCulture) + "; health " + characterHealth.ToString(CultureInfo.InvariantCulture) + " -> " + num4.ToString(CultureInfo.InvariantCulture) + ".")); } } } private static IEnumerator ApplyHyperArmorFinalDamageReductionDelayed(DamagePatchState state) { yield return (object)new WaitForEndOfFrame(); if (state != null && !((Object)(object)state.Victim == (Object)null) && !IsCharacterDead(state.Victim) && !(state.TargetHealthAfterMitigation <= 0f)) { float characterHealth = GetCharacterHealth(state.Victim); if (characterHealth >= 0f && characterHealth + 0.0001f < state.TargetHealthAfterMitigation) { SetCharacterHealthDirect(state.Victim, state.TargetHealthAfterMitigation); } } } private static float GetCharacterHealth(Character character) { if ((Object)(object)character == (Object)null) { return -1f; } try { return character.GetHealth(); } catch { } FieldInfo fieldInfo = GetCachedField(typeof(Character), "m_health") ?? GetCachedField(((object)character).GetType(), "m_health"); if (fieldInfo != null) { try { object value = fieldInfo.GetValue(character); if (value is float result) { return result; } if (value is double num) { return (float)num; } } catch { } } return -1f; } private static void SetCharacterHealthDirect(Character character, float newHealth) { if ((Object)(object)character == (Object)null || newHealth < 0f) { return; } try { character.SetHealth(newHealth); } catch { } FieldInfo fieldInfo = GetCachedField(typeof(Character), "m_health") ?? GetCachedField(((object)character).GetType(), "m_health"); if (!(fieldInfo != null)) { return; } try { fieldInfo.SetValue(character, newHealth); } catch { } } private static bool TryGetActiveHyperArmor(Character player, out WeaponCategory category, out AttackRole role, out object? attackInstance) { category = WeaponCategory.Other; role = AttackRole.Primary1; attackInstance = null; ItemData currentWeapon = GetCurrentWeapon(player); if (currentWeapon != null) { WeaponCategory weaponCategory = ClassifyWeapon(currentWeapon); AttackRole currentAttackRole = GetCurrentAttackRole(player, weaponCategory); if (GetConfig(weaponCategory).GetHyperArmorMode(currentAttackRole) == HyperArmorMode.WhileHoldingWeaponType) { category = weaponCategory; role = currentAttackRole; attackInstance = null; return true; } } if (!HyperArmorStates.TryGetValue(player, out var value)) { return false; } if (Time.time > value.EndTime || IsCharacterDead(player)) { HyperArmorStates.Remove(player); return false; } if (currentWeapon == null || !SamePublicWeaponCategory(ClassifyWeapon(currentWeapon), value.Category)) { HyperArmorStates.Remove(player); return false; } category = value.Category; role = value.Role; attackInstance = value.AttackInstance; return true; } private static void SetHyperArmorIfChanged(Character character, WeaponCategory category, AttackRole role, HyperArmorMode mode, float endTime, object? attackInstance) { if (!((Object)(object)character == (Object)null) && (!HyperArmorStates.TryGetValue(character, out var value) || value.Category != category || value.Role != role || value.Mode != mode || value.AttackInstance != attackInstance || !(Time.time <= value.EndTime))) { SetHyperArmor(character, category, role, mode, endTime, attackInstance); } } private static void SetHyperArmor(Character character, WeaponCategory category, AttackRole role, HyperArmorMode mode, float endTime, object? attackInstance) { HyperArmorStates[character] = new HyperArmorRuntimeState(category, role, mode, endTime, attackInstance); if (Debug(DebugHyperArmorLifecycle)) { DebugLogThrottled($"hyper-start:{((Object)character).GetInstanceID()}:{category}:{role}:{mode}", 1f, $"Hyperarmor active for {SafeName(character)}: {category}, {role}, {mode}, until {endTime.ToString(CultureInfo.InvariantCulture)}."); } } private static void EndHyperArmorForAttack(Character character, object? attackInstance, HyperArmorMode? mode) { if (HyperArmorStates.TryGetValue(character, out var value) && (!mode.HasValue || value.Mode == mode.Value) && (value.AttackInstance == null || attackInstance == null || value.AttackInstance == attackInstance)) { HyperArmorStates.Remove(character); if (Debug(DebugHyperArmorLifecycle)) { DebugLogThrottled($"hyper-end:{((Object)character).GetInstanceID()}:{value.Category}:{value.Role}:{value.Mode}", 1f, $"Hyperarmor ended for {SafeName(character)}: {value.Category}, {value.Role}, {value.Mode}."); } } } private static bool IsCharacterDead(Character character) { MethodInfo cachedMethod = GetCachedMethod(((object)character).GetType(), "IsDead"); if (cachedMethod == null) { return false; } try { object obj = cachedMethod.Invoke(character, Array.Empty()); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool TryGetAttackContext(object instance, object[] args, out Character? character, out ItemData? weapon) { character = null; weapon = null; foreach (object obj in args) { Character val = (Character)((obj is Character) ? obj : null); if (val != null && character == null) { character = val; } ItemData val2 = (ItemData)((obj is ItemData) ? obj : null); if (val2 != null && weapon == null) { weapon = val2; } } if ((Object)(object)character == (Object)null && instance != null && AttackInstanceOwners.TryGetValue(instance, out Character value)) { character = value; } if (weapon == null && instance != null && AttackInstanceWeapons.TryGetValue(instance, out ItemData value2)) { weapon = value2; } if (character == null) { character = GetFieldValue(instance, "m_character") ?? GetFieldValue(instance, "m_owner") ?? GetFieldValue(instance, "m_player") ?? GetFieldValue(instance, "m_humanoid") ?? FindFirstFieldValue(instance); } if (weapon == null) { weapon = GetFieldValue(instance, "m_weapon") ?? GetFieldValue(instance, "m_currentWeapon") ?? FindFirstFieldValue(instance) ?? (((Object)(object)character != (Object)null) ? GetCurrentWeapon(character) : null); } if (instance != null && (Object)(object)character != (Object)null) { AttackInstanceOwners[instance] = character; } if (instance != null && weapon != null) { AttackInstanceWeapons[instance] = weapon; } return (Object)(object)character != (Object)null; } private static bool CallStackLooksLikeHud() { try { StackTrace stackTrace = new StackTrace(fNeedFileInfo: false); for (int i = 1; i < stackTrace.FrameCount; i++) { string text = (stackTrace.GetFrame(i)?.GetMethod())?.DeclaringType?.Name ?? string.Empty; if (text.IndexOf("Hud", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Gui", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Stamina", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } catch { } return false; } private static ItemData? GetEquippedItem(Player player, string fieldName) { return GetFieldValue(player, fieldName); } private static float GetConfiguredMovementModifier(ItemData? item) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 if (item == null || item.m_shared == null) { return 0f; } float movementModifier = item.m_shared.m_movementModifier; if ((int)item.m_shared.m_itemType == 5) { float num = Mathf.Clamp(GetShieldTypeBlockConfig(item).MovementModifierPercent.Value, -100f, 100f); if (!Mathf.Approximately(num, 0f)) { return num / 100f; } return movementModifier; } if (TryGetStaffConfig(item, out StaffConfig _)) { return movementModifier; } if (!TryGetMovementModifierOverride(item, movementModifier, out var percent)) { return movementModifier; } return Mathf.Clamp(percent, -100f, 100f) / 100f; } private static bool TryGetMovementModifierOverride(ItemData item, float vanilla, out float percent) { GetMovementModifierConfig(item, vanilla, out ConfigEntry useCustom, out ConfigEntry value); percent = value.Value; return useCustom.Value; } private static void GetMovementModifierConfig(ItemData item, float vanilla, out ConfigEntry useCustom, out ConfigEntry value) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected I4, but got Unknown ItemType itemType = item.m_shared.m_itemType; switch (itemType - 3) { case 3: case 4: case 8: case 14: case 15: case 21: GetArmorMovementModifierConfig(vanilla, out useCustom, out value); break; case 12: case 16: useCustom = UseCustomToolMovementModifier; value = ToolMovementModifierPercent; break; case 0: case 1: case 11: case 19: GetWeaponMovementModifierConfig(ClassifyWeapon(item), out useCustom, out value); break; default: useCustom = UseCustomOtherEquipmentMovementModifier; value = OtherEquipmentMovementModifierPercent; break; } } private static void GetArmorMovementModifierConfig(float vanilla, out ConfigEntry useCustom, out ConfigEntry value) { if (vanilla >= -0.005f) { useCustom = UseCustomLightArmorMovementModifier; value = LightArmorMovementModifierPercent; } else if (vanilla > -0.035f) { useCustom = UseCustomMediumArmorMovementModifier; value = MediumArmorMovementModifierPercent; } else { useCustom = UseCustomHeavyArmorMovementModifier; value = HeavyArmorMovementModifierPercent; } } private static void GetWeaponMovementModifierConfig(WeaponCategory category, out ConfigEntry useCustom, out ConfigEntry value) { switch (category) { case WeaponCategory.OneHandedSword: useCustom = UseCustomOneHandedSwordMovementModifier; value = OneHandedSwordMovementModifierPercent; break; case WeaponCategory.OneHandedAxe: useCustom = UseCustomOneHandedAxeMovementModifier; value = OneHandedAxeMovementModifierPercent; break; case WeaponCategory.Club: useCustom = UseCustomClubMovementModifier; value = ClubMovementModifierPercent; break; case WeaponCategory.Knife: useCustom = UseCustomKnifeMovementModifier; value = KnifeMovementModifierPercent; break; case WeaponCategory.DualKnives: useCustom = UseCustomDualKnivesMovementModifier; value = DualKnivesMovementModifierPercent; break; case WeaponCategory.Spear: useCustom = UseCustomSpearMovementModifier; value = SpearMovementModifierPercent; break; case WeaponCategory.Atgeir: useCustom = UseCustomAtgeirMovementModifier; value = AtgeirMovementModifierPercent; break; case WeaponCategory.TwoHandedSword: useCustom = UseCustomTwoHandedSwordMovementModifier; value = TwoHandedSwordMovementModifierPercent; break; case WeaponCategory.TwoHandedAxe: useCustom = UseCustomTwoHandedAxeMovementModifier; value = TwoHandedAxeMovementModifierPercent; break; case WeaponCategory.Sledge: useCustom = UseCustomSledgeMovementModifier; value = SledgeMovementModifierPercent; break; case WeaponCategory.DualAxes: useCustom = UseCustomDualAxesMovementModifier; value = DualAxesMovementModifierPercent; break; case WeaponCategory.Bow: useCustom = UseCustomBowMovementModifier; value = BowMovementModifierPercent; break; case WeaponCategory.Crossbow: useCustom = UseCustomCrossbowMovementModifier; value = CrossbowMovementModifierPercent; break; default: useCustom = UseCustomOtherEquipmentMovementModifier; value = OtherEquipmentMovementModifierPercent; break; } } private static void ResetMovementModifierOverridesToVanilla() { foreach (ConfigEntry useCustomMovementModifierEntry in GetUseCustomMovementModifierEntries()) { useCustomMovementModifierEntry.Value = false; } LightArmorBaseArmorMultiplier.Value = 1f; MediumArmorBaseArmorMultiplier.Value = 1f; HeavyArmorBaseArmorMultiplier.Value = 1f; LightArmorMovementModifierPercent.Value = 0f; MediumArmorMovementModifierPercent.Value = 0f; HeavyArmorMovementModifierPercent.Value = 0f; OneHandedSwordMovementModifierPercent.Value = 0f; OneHandedAxeMovementModifierPercent.Value = 0f; ClubMovementModifierPercent.Value = 0f; KnifeMovementModifierPercent.Value = 0f; SpearMovementModifierPercent.Value = 0f; AtgeirMovementModifierPercent.Value = 0f; TwoHandedSwordMovementModifierPercent.Value = 0f; TwoHandedAxeMovementModifierPercent.Value = 0f; SledgeMovementModifierPercent.Value = 0f; DualAxesMovementModifierPercent.Value = 0f; BowMovementModifierPercent.Value = 0f; CrossbowMovementModifierPercent.Value = 0f; ToolMovementModifierPercent.Value = 0f; OtherEquipmentMovementModifierPercent.Value = 0f; } private static void SetGooMovementModifierOverrides() { ResetMovementModifierOverridesToVanilla(); UseCustomLightArmorMovementModifier.Value = true; LightArmorMovementModifierPercent.Value = 0f; UseCustomMediumArmorMovementModifier.Value = true; MediumArmorMovementModifierPercent.Value = 0f; UseCustomHeavyArmorMovementModifier.Value = true; HeavyArmorMovementModifierPercent.Value = 0f; UseCustomOneHandedSwordMovementModifier.Value = true; OneHandedSwordMovementModifierPercent.Value = -3f; UseCustomClubMovementModifier.Value = true; ClubMovementModifierPercent.Value = -3f; UseCustomTwoHandedAxeMovementModifier.Value = true; TwoHandedAxeMovementModifierPercent.Value = -8f; UseCustomSledgeMovementModifier.Value = true; SledgeMovementModifierPercent.Value = -8f; } private static void ResetBuildingDamageMultipliersToVanilla() { foreach (KeyValuePair> attackBuildingDamageMultiplier in AttackBuildingDamageMultipliers) { if (attackBuildingDamageMultiplier.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(attackBuildingDamageMultiplier.Key, attackBuildingDamageMultiplier.Value, 1f, 1f); } } } private static void ResetBuildingDamageMultipliersToGoo() { ResetBuildingDamageMultipliersToVanilla(); } private static void ResetFlankingDamageMultipliersToVanilla() { if (EnableFlankingDamage != null) { EnableFlankingDamage.Value = false; } if (FlankingDamageStackableWithOtherBonuses != null) { FlankingDamageStackableWithOtherBonuses.Value = true; } if (SuccessfulFlankDing != null) { SuccessfulFlankDing.Value = false; } if (FlankingPveRearAngleDegrees != null) { FlankingPveRearAngleDegrees.Value = 120f; } if (FlankingPvpRearAngleDegrees != null) { FlankingPvpRearAngleDegrees.Value = 90f; } if (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 1f; } if (GlobalPvpBackstabDamageMultiplier != null) { GlobalPvpBackstabDamageMultiplier.Value = 1f; } foreach (KeyValuePair> item in AttackFlankingDamageEnabled) { if (item.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(item.Key, item.Value, primaryValue: false, secondaryValue: false); } } foreach (KeyValuePair> item2 in AttackFlankingDingEnabled) { if (item2.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(item2.Key, item2.Value, primaryValue: false, secondaryValue: false); } } foreach (KeyValuePair> attackFlankingDamageMultiplier in AttackFlankingDamageMultipliers) { if (attackFlankingDamageMultiplier.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(attackFlankingDamageMultiplier.Key, attackFlankingDamageMultiplier.Value, 1f, 1f); } } foreach (KeyValuePair> attackPvpFlankingDamageMultiplier in AttackPvpFlankingDamageMultipliers) { if (attackPvpFlankingDamageMultiplier.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(attackPvpFlankingDamageMultiplier.Key, attackPvpFlankingDamageMultiplier.Value, 1f, 1f); } } } private static void ResetFlankingDamageMultipliersToGoo() { if (EnableFlankingDamage != null) { EnableFlankingDamage.Value = true; } if (FlankingDamageStackableWithOtherBonuses != null) { FlankingDamageStackableWithOtherBonuses.Value = true; } if (SuccessfulFlankDing != null) { SuccessfulFlankDing.Value = true; } if (FlankingPveRearAngleDegrees != null) { FlankingPveRearAngleDegrees.Value = 120f; } if (FlankingPvpRearAngleDegrees != null) { FlankingPvpRearAngleDegrees.Value = 90f; } if (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 1f; } if (GlobalPvpBackstabDamageMultiplier != null) { GlobalPvpBackstabDamageMultiplier.Value = 1f; } foreach (KeyValuePair> pair in AttackFlankingDamageEnabled) { SetRoleSettingsByRole(pair.Value, (AttackRole role) => DefaultGooFlankingDamageEnabled(pair.Key, role)); } foreach (KeyValuePair> pair2 in AttackFlankingDingEnabled) { SetRoleSettingsByRole(pair2.Value, (AttackRole role) => DefaultGooFlankingDamageEnabled(pair2.Key, role)); } foreach (KeyValuePair> pair3 in AttackFlankingDamageMultipliers) { SetRoleSettingsByRole(pair3.Value, (AttackRole role) => DefaultGooFlankingDamageMultiplier(pair3.Key, pvp: false)); } foreach (KeyValuePair> pair4 in AttackPvpFlankingDamageMultipliers) { SetRoleSettingsByRole(pair4.Value, (AttackRole role) => DefaultGooFlankingDamageMultiplier(pair4.Key, pvp: true)); } } private static void SetRoleSettingsByRole(RoleSettings settings, Func valueForRole) { if (settings != null && valueForRole != null) { settings.Primary1.Value = valueForRole(AttackRole.Primary1); settings.Primary2.Value = valueForRole(AttackRole.Primary2); settings.Primary3.Value = valueForRole(AttackRole.Primary3); settings.Primary4.Value = valueForRole(AttackRole.Primary4); settings.Secondary.Value = valueForRole(AttackRole.Secondary); } } private static void SetVanillaAttackRoleValues(WeaponCategory category, RoleSettings settings, Func valueForRole) { if (settings == null || valueForRole == null) { return; } AttackRole[] configurableAttackRoles = ConfigurableAttackRoles; foreach (AttackRole attackRole in configurableAttackRoles) { if (IsVanillaAttackRole(category, attackRole)) { settings.Get(attackRole).Value = valueForRole(attackRole); } } } private static void SetVanillaAttackRoleValues(WeaponCategory category, RoleSettings settings, T primaryValue, T secondaryValue) { SetVanillaAttackRoleValues(category, settings, (AttackRole role) => (role != AttackRole.Secondary) ? primaryValue : secondaryValue); } private static void ResetEffectiveBlockAngleMultipliersToVanilla() { if (GlobalEffectiveBlockAngleMultiplier != null) { GlobalEffectiveBlockAngleMultiplier.Value = 1f; } foreach (ConfigEntry value in WeaponEffectiveBlockAngleMultipliers.Values) { value.Value = 1f; } if (ShieldBucklerEffectiveBlockAngleMultiplier != null) { ShieldBucklerEffectiveBlockAngleMultiplier.Value = 1f; } if (ShieldMediumEffectiveBlockAngleMultiplier != null) { ShieldMediumEffectiveBlockAngleMultiplier.Value = 1f; } if (ShieldTowerEffectiveBlockAngleMultiplier != null) { ShieldTowerEffectiveBlockAngleMultiplier.Value = 1f; } foreach (ConfigEntry value2 in StaffEffectiveBlockAngleMultipliers.Values) { value2.Value = 1f; } } private static void ResetEffectiveBlockAngleMultipliersToGoo() { ResetEffectiveBlockAngleMultipliersToVanilla(); if (ShieldTowerEffectiveBlockAngleMultiplier != null) { ShieldTowerEffectiveBlockAngleMultiplier.Value = 1.2f; } } private static void ResetStaggeredEnemyDamageMultipliersToVanilla() { if (GlobalDamageToStaggeredEnemiesMultiplier != null) { GlobalDamageToStaggeredEnemiesMultiplier.Value = 2f; } if (StaggeredEnemyDamageStackableWithOtherBonuses != null) { StaggeredEnemyDamageStackableWithOtherBonuses.Value = true; } if (BackstabDamageStackableWithOtherBonuses != null) { BackstabDamageStackableWithOtherBonuses.Value = true; } if (CounterDamageStackableWithOtherBonuses != null) { CounterDamageStackableWithOtherBonuses.Value = true; } if (CounterDamageDing != null) { CounterDamageDing.Value = false; } foreach (KeyValuePair> attackStaggeredEnemyDamageMultiplier in AttackStaggeredEnemyDamageMultipliers) { if (attackStaggeredEnemyDamageMultiplier.Key != WeaponCategory.SledgeAlt) { SetVanillaAttackRoleValues(attackStaggeredEnemyDamageMultiplier.Key, attackStaggeredEnemyDamageMultiplier.Value, 1f, 1f); } } } private static void ResetStaggeredEnemyDamageMultipliersToGoo() { ResetStaggeredEnemyDamageMultipliersToVanilla(); if (CounterDamageDing != null) { CounterDamageDing.Value = true; } if (GlobalDamageToStaggeredEnemiesMultiplier != null) { GlobalDamageToStaggeredEnemiesMultiplier.Value = 2f; } foreach (KeyValuePair> attackStaggeredEnemyDamageMultiplier in AttackStaggeredEnemyDamageMultipliers) { attackStaggeredEnemyDamageMultiplier.Value.Primary1.Value = DefaultGooDamageToStaggeredEnemyMultiplier(attackStaggeredEnemyDamageMultiplier.Key, AttackRole.Primary1); attackStaggeredEnemyDamageMultiplier.Value.Primary2.Value = DefaultGooDamageToStaggeredEnemyMultiplier(attackStaggeredEnemyDamageMultiplier.Key, AttackRole.Primary2); attackStaggeredEnemyDamageMultiplier.Value.Primary3.Value = DefaultGooDamageToStaggeredEnemyMultiplier(attackStaggeredEnemyDamageMultiplier.Key, AttackRole.Primary3); attackStaggeredEnemyDamageMultiplier.Value.Primary4.Value = DefaultGooDamageToStaggeredEnemyMultiplier(attackStaggeredEnemyDamageMultiplier.Key, AttackRole.Primary4); attackStaggeredEnemyDamageMultiplier.Value.Secondary.Value = DefaultGooDamageToStaggeredEnemyMultiplier(attackStaggeredEnemyDamageMultiplier.Key, AttackRole.Secondary); } foreach (ConfigEntry value3 in RunningAttackStaggeredEnemyDamageMultipliers.Values) { value3.Value = 1f; } foreach (ConfigEntry value4 in JumpAttackStaggeredEnemyDamageMultipliers.Values) { value4.Value = 1f; } WeaponCategory[] array = new WeaponCategory[2] { WeaponCategory.Knife, WeaponCategory.DualKnives }; foreach (WeaponCategory key in array) { if (RunningAttackStaggeredEnemyDamageMultipliers.TryGetValue(key, out ConfigEntry value)) { value.Value = 2f; } if (JumpAttackStaggeredEnemyDamageMultipliers.TryGetValue(key, out ConfigEntry value2)) { value2.Value = 2f; } } } private static void ResetBackstabDamageMultipliersToVanilla() { if (GlobalSneakDamageMultiplier != null) { GlobalSneakDamageMultiplier.Value = 1f; } if (SneakDamageScalesWithSneakLevel != null) { SneakDamageScalesWithSneakLevel.Value = false; } if (SneakDamageScaleBaseMultiplier != null) { SneakDamageScaleBaseMultiplier.Value = 1f; } if (SneakDamageMultiplierPerSneakLevel != null) { SneakDamageMultiplierPerSneakLevel.Value = 0f; } foreach (KeyValuePair> backstabDamageMultiplier in BackstabDamageMultipliers) { if (backstabDamageMultiplier.Key != WeaponCategory.SledgeAlt) { float num = DefaultVanillaBackstabDamageMultiplier(backstabDamageMultiplier.Key); SetVanillaAttackRoleValues(backstabDamageMultiplier.Key, backstabDamageMultiplier.Value, num, num); } } foreach (StaffConfig value in StaffConfigs.Values) { value.BackstabDamageMultiplier.Value = 3f; } } private static void ResetBackstabDamageMultipliersToGoo() { if (GlobalSneakDamageMultiplier != null) { GlobalSneakDamageMultiplier.Value = 2f; } if (SneakDamageScalesWithSneakLevel != null) { SneakDamageScalesWithSneakLevel.Value = true; } if (SneakDamageScaleBaseMultiplier != null) { SneakDamageScaleBaseMultiplier.Value = 1f; } if (SneakDamageMultiplierPerSneakLevel != null) { SneakDamageMultiplierPerSneakLevel.Value = 0.01f; } foreach (KeyValuePair> backstabDamageMultiplier in BackstabDamageMultipliers) { backstabDamageMultiplier.Value.SetAll(DefaultGooBackstabDamageMultiplier(backstabDamageMultiplier.Key), DefaultGooBackstabDamageMultiplier(backstabDamageMultiplier.Key)); } foreach (KeyValuePair runningAttackConfig in RunningAttackConfigs) { runningAttackConfig.Value.BackstabDamageMultiplier.Value = DefaultGooBackstabDamageMultiplier(runningAttackConfig.Key); } foreach (KeyValuePair jumpAttackConfig in JumpAttackConfigs) { jumpAttackConfig.Value.BackstabDamageMultiplier.Value = DefaultGooBackstabDamageMultiplier(jumpAttackConfig.Key); } foreach (StaffConfig value in StaffConfigs.Values) { value.BackstabDamageMultiplier.Value = 1f; } } private static float DefaultGooDamageToStaggeredEnemyMultiplier(WeaponCategory category, AttackRole role) { if (category == WeaponCategory.Knife || category == WeaponCategory.DualKnives) { return 2f; } if (!IsVanillaAttackRole(category, role)) { return 1f; } if (category == WeaponCategory.TwoHandedAxe) { return 1f; } if (role != AttackRole.Secondary) { return 1f; } return 1.5f; } private static void ApplyDesignedAlternateSledgePresetValues() { foreach (ConfigEntryBase item in GcoConfigSyncEntries.Values.Distinct()) { if ((item.Definition.Section ?? string.Empty).StartsWith("Sledge - Alt Moveset", StringComparison.OrdinalIgnoreCase)) { item.BoxedValue = item.DefaultValue; } } if (CategoryConfigs.TryGetValue(WeaponCategory.SledgeAlt, out CategoryConfig value)) { value.BaseDamageMultiplier.Value = 1.5f; value.StaminaRateMultiplier.SetAll(1.5f, 1.5f); value.HitStopDurationMultiplier.SetAll(1f, 1f); value.AdrenalineMultiplier.Primary1.Value = 2f; value.AdrenalineMultiplier.Primary2.Value = 2f; value.PvpStaggerMultiplier.Primary1.Value = DefaultPvpStaggerPowerMultiplier(WeaponCategory.TwoHandedAxe, AttackRole.Primary1); value.PvpStaggerMultiplier.Primary2.Value = DefaultPvpStaggerPowerMultiplier(WeaponCategory.TwoHandedAxe, AttackRole.Primary2); value.PvpStaggerDurationSeconds.Primary1.Value = DefaultPvpStaggerDuration(WeaponCategory.TwoHandedAxe, AttackRole.Primary1); value.PvpStaggerDurationSeconds.Primary2.Value = DefaultPvpStaggerDuration(WeaponCategory.TwoHandedAxe, AttackRole.Primary2); value.PvpKnockbackForceMultiplier.Primary1.Value = 0.1f; value.PvpKnockbackForceMultiplier.Primary2.Value = 0.1f; value.AttackMovementModifier.Primary1.Value = 0f; value.AttackMovementModifier.Primary2.Value = 0f; value.AttackMovementModifier.Secondary.Value = 0.2f; } if (AttackRangeMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value2)) { value2.Primary1.Value = 1.1f; value2.Primary2.Value = 1f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value3)) { value3.Primary1.Value = 1.25f; value3.Primary2.Value = 1.25f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value4)) { value4.Primary1.Value = 1.3f; value4.Primary2.Value = 1.3f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value5)) { value5.Primary1.Value = 1.5f; value5.Primary2.Value = 1.5f; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value6)) { value6.Primary1.Value = AttackMultiTargetDamagePenaltyMode.Disabled; value6.Primary2.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (AttackMultiHitEnabled.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value7)) { value7.Primary1.Value = true; value7.Primary2.Value = true; } if (AttackHitThroughTargetsEnabled.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value8)) { value8.Primary1.Value = true; value8.Primary2.Value = true; } if (AttackHitThroughWallsEnabled.TryGetValue(WeaponCategory.SledgeAlt, out RoleSettings value9)) { value9.Primary1.Value = true; value9.Primary2.Value = true; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.SledgeAlt, out RunningAttackConfig value10)) { value10.Enabled.Value = true; value10.MinimumVelocity.Value = 5f; value10.UsedAttack.Value = RunningAttackUsed.Primary2; value10.DamageMultiplier.Value = 1f; value10.StaggerMultiplier.Value = 1f; value10.StaminaRateMultiplier.Value = 1.5f; value10.AnimationSpeedMultiplier.Value = 1f; value10.RecoveryAnimationSpeedMultiplier.Value = 1f; value10.BlockCancelMode.Value = BlockCancelMode.AfterHitbox; value10.BlockCancelAnimationSpeedMultiplier.Value = 1f; value10.HyperArmorMode.Value = HyperArmorMode.Balanced; value10.DamageTakenMultiplier.Value = 0.7f; value10.StaggerTakenMultiplier.Value = 0f; value10.KnockbackTakenMultiplier.Value = 0f; value10.PvpDamageTakenMultiplier.Value = 1f; value10.PvpStaggerTakenMultiplier.Value = 1f; value10.PvpKnockbackTakenMultiplier.Value = 1f; value10.CounterDamageEnabled.Value = false; value10.CounterDamageMultiplier.Value = 1.3f; value10.PvpCounterDamageMultiplier.Value = 1f; value10.HitStopDurationMultiplier.Value = 1f; value10.PvpHitStopDurationMultiplier.Value = 0.3f; value10.ChopDamageMultiplier.Value = 0.5f; value10.PickaxeDamageMultiplier.Value = 0f; value10.BackstabDamageMultiplier.Value = 1f; value10.MovementSpeedMultiplier.Value = 0.9f; value10.MovementApplicationMode.Value = AttackMovementApplicationMode.BeforeHitbox; value10.RotationFactor.Value = 2f; value10.LockRotationAfterAttackTrigger.Value = true; value10.PvpDamageMultiplier.Value = 1f; value10.PvpStaggerMultiplier.Value = 20f; value10.PvpStaggerDurationSeconds.Value = 1.17f; value10.PvpKnockbackForceMultiplier.Value = 0.1f; value10.KnockbackForceMultiplier.Value = 1f; value10.AdrenalineMultiplier.Value = 2f; value10.LungeDistanceMultiplier.Value = 0f; value10.LungeApplicationMode.Value = LungeApplicationMode.BeforeHitbox; value10.StartNoiseMultiplier.Value = 1f; value10.HitNoiseMultiplier.Value = 1f; value10.RangeMultiplier.Value = 1f; value10.RayWidthMultiplier.Value = 1.25f; value10.HitboxHeightMultiplier.Value = 1.3f; value10.OffsetMultiplier.Value = 1f; value10.HitboxType.Value = AttackHitboxType.Horizontal; value10.AngleMultiplier.Value = 1.5f; value10.MultiTargetDamagePenalty.Value = AttackMultiTargetDamagePenaltyMode.Disabled; } if (RunningAttackBuildingDamageMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out ConfigEntry value11)) { value11.Value = 1f; } if (RunningAttackStaggeredEnemyDamageMultipliers.TryGetValue(WeaponCategory.SledgeAlt, out ConfigEntry value12)) { value12.Value = 1f; } if (RunningAttackMultiHitEnabled.TryGetValue(WeaponCategory.SledgeAlt, out ConfigEntry value13)) { value13.Value = true; } if (RunningAttackHitThroughTargetsEnabled.TryGetValue(WeaponCategory.SledgeAlt, out ConfigEntry value14)) { value14.Value = true; } if (RunningAttackHitThroughWallsEnabled.TryGetValue(WeaponCategory.SledgeAlt, out ConfigEntry value15)) { value15.Value = true; } } private static float DefaultRunningAttackStaminaRateMultiplier(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword && category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.SledgeAlt) { return 1f; } return 1.5f; } private static float DefaultJumpAttackStaminaRateMultiplier(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword && category != WeaponCategory.TwoHandedAxe) { return 1f; } return 1.5f; } private static void SetRunningAttackPreset(bool enabled, float damageMultiplier, float defaultLungeMultiplier) { foreach (KeyValuePair runningAttackConfig in RunningAttackConfigs) { RunningAttackConfig value = runningAttackConfig.Value; value.Enabled.Value = enabled; value.MinimumVelocity.Value = (enabled ? GooRunningAttackMinimumVelocity(runningAttackConfig.Key) : 0f); value.UsedAttack.Value = DefaultRunningAttackUsed(runningAttackConfig.Key); value.DamageMultiplier.Value = (enabled ? DefaultRunningAttackDamageMultiplier(runningAttackConfig.Key) : damageMultiplier); value.StaggerMultiplier.Value = 1f; value.StaminaRateMultiplier.Value = (enabled ? DefaultRunningAttackStaminaRateMultiplier(runningAttackConfig.Key) : 1f); value.AnimationSpeedMultiplier.Value = 1f; value.RecoveryAnimationSpeedMultiplier.Value = 1f; value.BlockCancelMode.Value = (enabled ? BlockCancelMode.AfterHitbox : BlockCancelMode.Disabled); value.BlockCancelAnimationSpeedMultiplier.Value = (enabled ? DefaultBlockCancelAnimationSpeedMultiplier(runningAttackConfig.Key) : 1f); value.HyperArmorMode.Value = (enabled ? DefaultRunningAttackHyperArmorMode(runningAttackConfig.Key) : HyperArmorMode.Off); value.DamageTakenMultiplier.Value = (enabled ? DefaultRunningAttackHyperArmorDamageTakenMultiplier(runningAttackConfig.Key) : 1f); value.StaggerTakenMultiplier.Value = (enabled ? DefaultRunningAttackHyperArmorStaggerTakenMultiplier(runningAttackConfig.Key) : 1f); value.KnockbackTakenMultiplier.Value = (enabled ? DefaultRunningAttackHyperArmorKnockbackTakenMultiplier(runningAttackConfig.Key) : 1f); value.PvpDamageTakenMultiplier.Value = 1f; value.PvpStaggerTakenMultiplier.Value = 1f; value.PvpKnockbackTakenMultiplier.Value = 1f; value.CounterDamageEnabled.Value = enabled && DefaultRunningAttackCounterDamageEnabled(runningAttackConfig.Key); value.CounterDamageMultiplier.Value = DefaultRunningAttackCounterDamageMultiplier(runningAttackConfig.Key); value.PvpCounterDamageMultiplier.Value = 1f; value.HitStopDurationMultiplier.Value = (enabled ? DefaultRunningAttackHitStopDurationMultiplier(runningAttackConfig.Key) : 1f); value.PvpHitStopDurationMultiplier.Value = (enabled ? DefaultRunningAttackPvpHitStopDurationMultiplier(runningAttackConfig.Key) : 1f); value.ChopDamageMultiplier.Value = (enabled ? 0.5f : 1f); value.PickaxeDamageMultiplier.Value = (enabled ? 0f : 1f); if (RunningAttackBuildingDamageMultipliers.TryGetValue(runningAttackConfig.Key, out ConfigEntry value2)) { value2.Value = 1f; } if (RunningAttackStaggeredEnemyDamageMultipliers.TryGetValue(runningAttackConfig.Key, out ConfigEntry value3)) { value3.Value = 1f; } value.BackstabDamageMultiplier.Value = (enabled ? DefaultGooBackstabDamageMultiplier(runningAttackConfig.Key) : DefaultVanillaBackstabDamageMultiplier(runningAttackConfig.Key)); value.MovementSpeedMultiplier.Value = 1f; value.MovementApplicationMode.Value = ((!enabled) ? AttackMovementApplicationMode.FullAnimation : AttackMovementApplicationMode.BeforeHitbox); value.RotationFactor.Value = (enabled ? 2f : 1f); value.LockRotationAfterAttackTrigger.Value = enabled; value.PvpDamageMultiplier.Value = 1f; value.PvpStaggerMultiplier.Value = 1f; value.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(runningAttackConfig.Key, RoleFromRunningAttackUsed(value.UsedAttack.Value)); value.PvpKnockbackForceMultiplier.Value = 1f; value.KnockbackForceMultiplier.Value = 1f; value.AdrenalineMultiplier.Value = 1f; value.LungeDistanceMultiplier.Value = defaultLungeMultiplier; value.LungeApplicationMode.Value = LungeApplicationMode.BeforeHitbox; value.StartNoiseMultiplier.Value = 1f; value.HitNoiseMultiplier.Value = 1f; value.RangeMultiplier.Value = 1f; value.RayWidthMultiplier.Value = 1f; value.HitboxHeightMultiplier.Value = 1f; value.OffsetMultiplier.Value = 1f; value.HitboxType.Value = DefaultVanillaRunningHitboxType(runningAttackConfig.Key); value.AngleMultiplier.Value = 1f; value.MultiTargetDamagePenalty.Value = DefaultVanillaMultiTargetDamagePenaltyMode(runningAttackConfig.Key, RoleFromRunningAttackUsed(value.UsedAttack.Value)); } } private static float DefaultBlockCancelAnimationSpeedMultiplier(WeaponCategory category) { return 1f; } private static float DefaultRunningAttackDamageMultiplier(WeaponCategory category) { if (category == WeaponCategory.TwoHandedSword || (uint)(category - 7) <= 4u) { return 0.7f; } return 1f; } private static float DefaultGooChopDamageMultiplier(WeaponCategory category, AttackRole role) { return DefaultChopDamageMultiplier(category); } private static float DefaultChopDamageMultiplier(WeaponCategory category) { if (category != WeaponCategory.OneHandedAxe && category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.DualAxes) { return 0f; } return 1f; } private static float DefaultPickaxeDamageMultiplier(WeaponCategory category) { return category switch { WeaponCategory.Other => 1f, WeaponCategory.Pickaxe => 1.5f, _ => 0f, }; } private static float VanillaPickaxeDamageMultiplier(WeaponCategory category) { if (category != WeaponCategory.Pickaxe && category != WeaponCategory.Other) { return 0f; } return 1f; } private static float DefaultGooHitStopDurationMultiplier(WeaponCategory category) { switch (category) { case WeaponCategory.TwoHandedSword: case WeaponCategory.OneHandedSword: return 0.6f; case WeaponCategory.TwoHandedAxe: return 0.9f; case WeaponCategory.DualAxes: return 0.7f; case WeaponCategory.Atgeir: case WeaponCategory.OneHandedAxe: case WeaponCategory.Spear: case WeaponCategory.Unarmed: return 0.8f; default: return 1f; } } private static AttackRole DefaultRunningAttackRole(WeaponCategory category) { return RoleFromRunningAttackUsed(DefaultRunningAttackUsed(category)); } private static HyperArmorMode DefaultRunningAttackHyperArmorMode(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return HyperArmorMode.Off; } return value.HyperArmorMode.Get(role).Value; } private static float DefaultRunningAttackHyperArmorDamageTakenMultiplier(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return 1f; } return value.DamageTakenMultiplier.Get(role).Value; } private static float DefaultRunningAttackHyperArmorStaggerTakenMultiplier(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return 1f; } return value.StaggerTakenMultiplier.Get(role).Value; } private static float DefaultRunningAttackHyperArmorKnockbackTakenMultiplier(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return 1f; } return value.KnockbackTakenMultiplier.Get(role).Value; } private static bool DefaultRunningAttackCounterDamageEnabled(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return value.CounterDamageEnabled.Get(role).Value; } return false; } private static float DefaultRunningAttackCounterDamageMultiplier(WeaponCategory category) { AttackRole role = DefaultRunningAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return 1.3f; } return value.CounterDamageMultiplier.Get(role).Value; } private static float DefaultRunningAttackHitStopDurationMultiplier(WeaponCategory category) { int category2; switch (category) { case WeaponCategory.SledgeAlt: return 1f; default: category2 = (int)category; break; case WeaponCategory.Sledge: category2 = 1; break; } return DefaultGooHitStopDurationMultiplier((WeaponCategory)category2); } private static float DefaultGooPvpHitStopDurationMultiplier(WeaponCategory category) { switch (category) { case WeaponCategory.DualAxes: case WeaponCategory.Knife: case WeaponCategory.DualKnives: return 1f; case WeaponCategory.Atgeir: case WeaponCategory.OneHandedSword: case WeaponCategory.Club: case WeaponCategory.Spear: case WeaponCategory.Unarmed: return 0.8f; case WeaponCategory.TwoHandedSword: case WeaponCategory.TwoHandedAxe: case WeaponCategory.Pickaxe: case WeaponCategory.OneHandedAxe: return 0.3f; default: return 1f; } } private static float DefaultRunningAttackPvpHitStopDurationMultiplier(WeaponCategory category) { return DefaultGooPvpHitStopDurationMultiplier((category == WeaponCategory.Sledge) ? WeaponCategory.TwoHandedAxe : category); } private static bool DefaultJumpAttackCounterDamageEnabled(WeaponCategory category) { AttackRole role = DefaultJumpAttackRole(category); if (CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return value.CounterDamageEnabled.Get(role).Value; } return false; } private static float DefaultJumpAttackCounterDamageMultiplier(WeaponCategory category) { AttackRole role = DefaultJumpAttackRole(category); if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return 1.3f; } return value.CounterDamageMultiplier.Get(role).Value; } private static float DefaultJumpAttackHitStopDurationMultiplier(WeaponCategory category) { if (category != WeaponCategory.TwoHandedAxe) { return DefaultGooHitStopDurationMultiplier(category); } return 5f; } private static float DefaultJumpAttackPvpHitStopDurationMultiplier(WeaponCategory category) { if (category != WeaponCategory.TwoHandedAxe) { return DefaultGooPvpHitStopDurationMultiplier(category); } return 5f; } private static float GooRunningAttackMinimumVelocity(WeaponCategory category) { return 5f; } private static float DefaultGooAirborneDamageMultiplier(WeaponCategory category) { if (!IsAirborneBonusWeaponCategory(category) || HasEnabledGooDefaultSpecialJumpAttack(category)) { return 1f; } return 1.3f; } private static bool HasEnabledGooDefaultSpecialJumpAttack(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword && category != WeaponCategory.TwoHandedAxe && category != WeaponCategory.Atgeir && category != WeaponCategory.OneHandedSword && category != WeaponCategory.OneHandedAxe && category != WeaponCategory.DualAxes) { return category == WeaponCategory.Club; } return true; } private static void SetJumpAttackPreset(bool enabled) { foreach (KeyValuePair jumpAttackConfig in JumpAttackConfigs) { JumpAttackConfig value = jumpAttackConfig.Value; bool flag = enabled; float value2 = 1f; float value3 = 1f; float value4 = 1f; if (jumpAttackConfig.Key == WeaponCategory.OneHandedAxe) { value2 = 1f; value3 = 1.7f; } else if (jumpAttackConfig.Key == WeaponCategory.TwoHandedSword) { value4 = 0.6f; } else if (jumpAttackConfig.Key == WeaponCategory.TwoHandedAxe) { value4 = 0.6f; } else if (jumpAttackConfig.Key == WeaponCategory.DualAxes) { value2 = 0.45f; value3 = 1f; value4 = 1f; } value.Enabled.Value = flag; value.UsedAttack.Value = DefaultJumpAttackUsed(jumpAttackConfig.Key); value.DamageMultiplier.Value = value2; value.StaggerMultiplier.Value = 1f; value.StaminaRateMultiplier.Value = (flag ? DefaultJumpAttackStaminaRateMultiplier(jumpAttackConfig.Key) : 1f); value.AnimationSpeedMultiplier.Value = value3; value.RecoveryAnimationSpeedMultiplier.Value = ((jumpAttackConfig.Key == WeaponCategory.OneHandedAxe) ? 0.8f : 1f); value.BlockCancelMode.Value = (enabled ? BlockCancelMode.AfterHitbox : BlockCancelMode.Disabled); value.BlockCancelAnimationSpeedMultiplier.Value = (enabled ? DefaultBlockCancelAnimationSpeedMultiplier(jumpAttackConfig.Key) : 1f); value.HyperArmorMode.Value = HyperArmorMode.Off; value.DamageTakenMultiplier.Value = 1f; value.StaggerTakenMultiplier.Value = 1f; value.KnockbackTakenMultiplier.Value = 1f; value.PvpDamageTakenMultiplier.Value = 1f; value.PvpStaggerTakenMultiplier.Value = 1f; value.PvpKnockbackTakenMultiplier.Value = 1f; value.CounterDamageEnabled.Value = enabled && DefaultJumpAttackCounterDamageEnabled(jumpAttackConfig.Key); value.CounterDamageMultiplier.Value = DefaultJumpAttackCounterDamageMultiplier(jumpAttackConfig.Key); value.PvpCounterDamageMultiplier.Value = 1f; value.HitStopDurationMultiplier.Value = (enabled ? DefaultJumpAttackHitStopDurationMultiplier(jumpAttackConfig.Key) : 1f); value.PvpHitStopDurationMultiplier.Value = (enabled ? DefaultJumpAttackPvpHitStopDurationMultiplier(jumpAttackConfig.Key) : 1f); value.ChopDamageMultiplier.Value = (enabled ? 0.5f : 1f); value.PickaxeDamageMultiplier.Value = (enabled ? 0f : 1f); if (JumpAttackBuildingDamageMultipliers.TryGetValue(jumpAttackConfig.Key, out ConfigEntry value5)) { value5.Value = 1f; } if (JumpAttackStaggeredEnemyDamageMultipliers.TryGetValue(jumpAttackConfig.Key, out ConfigEntry value6)) { value6.Value = 1f; } value.BackstabDamageMultiplier.Value = (enabled ? DefaultGooBackstabDamageMultiplier(jumpAttackConfig.Key) : DefaultVanillaBackstabDamageMultiplier(jumpAttackConfig.Key)); value.MovementSpeedMultiplier.Value = ((jumpAttackConfig.Key == WeaponCategory.OneHandedAxe) ? 1f : (flag ? 0.5f : 1f)); value.MovementApplicationMode.Value = (flag ? AttackMovementApplicationMode.FullAnimation : AttackMovementApplicationMode.FullAnimation); value.RotationFactor.Value = (flag ? 2f : 1f); value.LockRotationAfterAttackTrigger.Value = flag; value.PvpDamageMultiplier.Value = value4; value.PvpStaggerMultiplier.Value = 1f; value.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(jumpAttackConfig.Key, RoleFromJumpAttackUsed(NormalizeJumpAttackUsed(jumpAttackConfig.Key, value.UsedAttack.Value))); value.PvpKnockbackForceMultiplier.Value = 1f; value.KnockbackForceMultiplier.Value = 1f; value.AdrenalineMultiplier.Value = 1f; value.LungeDistanceMultiplier.Value = 0f; value.LungeApplicationMode.Value = LungeApplicationMode.BeforeHitbox; value.StartNoiseMultiplier.Value = 1f; value.HitNoiseMultiplier.Value = 1f; value.RangeMultiplier.Value = 1f; value.RayWidthMultiplier.Value = 1f; value.HitboxHeightMultiplier.Value = 1f; value.OffsetMultiplier.Value = 1f; value.HitboxType.Value = DefaultVanillaJumpHitboxType(jumpAttackConfig.Key); value.AngleMultiplier.Value = 1f; value.MultiTargetDamagePenalty.Value = DefaultVanillaMultiTargetDamagePenaltyMode(jumpAttackConfig.Key, RoleFromJumpAttackUsed(NormalizeJumpAttackUsed(jumpAttackConfig.Key, value.UsedAttack.Value))); } } private static void SetBlockedHitDamageTakenConfig(BlockedHitDamageTakenConfig? cfg, float physical, float elemental, float generic = 1f) { if (cfg != null) { cfg.Generic.Value = generic; cfg.Blunt.Value = physical; cfg.Slash.Value = physical; cfg.Pierce.Value = physical; cfg.Chop.Value = physical; cfg.Pickaxe.Value = physical; cfg.Fire.Value = elemental; cfg.Frost.Value = elemental; cfg.Lightning.Value = elemental; cfg.Poison.Value = elemental; cfg.Spirit.Value = elemental; } } private static void ResetBlockedHitDamageTakenConfigsToVanilla() { SetBlockedHitDamageTakenConfig(GlobalDamageTakenOnBlockedHit, 1f, 1f); SetBlockedHitDamageTakenConfig(ShieldBucklerDamageTakenOnBlockedHit, 1f, 1f); SetBlockedHitDamageTakenConfig(ShieldMediumDamageTakenOnBlockedHit, 1f, 1f); SetBlockedHitDamageTakenConfig(ShieldTowerDamageTakenOnBlockedHit, 1f, 1f); foreach (BlockedHitDamageTakenConfig value in WeaponDamageTakenOnBlockedHitConfigs.Values) { SetBlockedHitDamageTakenConfig(value, 1f, 1f); } foreach (BlockedHitDamageTakenConfig value2 in StaffDamageTakenOnBlockedHitConfigs.Values) { SetBlockedHitDamageTakenConfig(value2, 1f, 1f); } } private static void ApplyGooBlockedHitDamageTakenDefaults() { ResetBlockedHitDamageTakenConfigsToVanilla(); SetBlockedHitDamageTakenConfig(ShieldTowerDamageTakenOnBlockedHit, 0f, 1f); } private static void SetGooRunningWhileBlockingDefaults() { foreach (WeaponBlockExtraConfig value in WeaponBlockExtras.Values) { value.BlockingMovementSpeedMultiplier.Value = 1f; value.EnableRunningWhileBlocking.Value = true; value.RunningWhileBlockingMovementSpeedMultiplier.Value = 0.7f; } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.EnableRunningWhileBlocking.Value = true; ShieldBucklerBlockConfig.RunningWhileBlockingMovementSpeedMultiplier.Value = 0.7f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.EnableRunningWhileBlocking.Value = true; ShieldMediumBlockConfig.RunningWhileBlockingMovementSpeedMultiplier.Value = 0.7f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.EnableRunningWhileBlocking.Value = false; ShieldTowerBlockConfig.RunningWhileBlockingMovementSpeedMultiplier.Value = 1f; } foreach (StaffBlockConfig value2 in StaffBlockConfigs.Values) { value2.EnableRunningWhileBlocking.Value = true; value2.RunningWhileBlockingMovementSpeedMultiplier.Value = 0.7f; } } private static void SetGooBlockForceDefaults() { foreach (CategoryConfig value in CategoryConfigs.Values) { value.BlockForceMultiplier.Value = 0f; } if (GlobalBlockBreakThresholdMultiplier != null) { GlobalBlockBreakThresholdMultiplier.Value = 1.1f; } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.BlockForceMultiplier.Value = 0f; ShieldBucklerBlockConfig.BlockBreakThresholdMultiplier.Value = 1f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.BlockForceMultiplier.Value = 0f; ShieldMediumBlockConfig.BlockBreakThresholdMultiplier.Value = 1f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.BlockArmorMultiplier.Value = 1f; ShieldTowerBlockConfig.BlockForceMultiplier.Value = 0f; ShieldTowerBlockConfig.BlockBreakThresholdMultiplier.Value = 1.5f; } foreach (StaffBlockConfig value2 in StaffBlockConfigs.Values) { value2.BlockForceMultiplier.Value = 0f; } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.MovementModifierPercent.Value = -3f; ShieldBucklerBlockConfig.SoulsLikeBlockBreak.Value = false; ShieldBucklerBlockConfig.SoulsLikeBlockStaminaMode.Value = false; ShieldBucklerBlockConfig.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; ShieldBucklerBlockConfig.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.MovementModifierPercent.Value = -3f; ShieldMediumBlockConfig.SoulsLikeBlockBreak.Value = false; ShieldMediumBlockConfig.SoulsLikeBlockStaminaMode.Value = false; ShieldMediumBlockConfig.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; ShieldMediumBlockConfig.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.MovementModifierPercent.Value = -8f; ShieldTowerBlockConfig.SoulsLikeBlockBreak.Value = true; ShieldTowerBlockConfig.BlockStaminaConsumptionRate.Value = 1f; ShieldTowerBlockConfig.SoulsLikeBlockStaminaMode.Value = true; ShieldTowerBlockConfig.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; ShieldTowerBlockConfig.BlockArmorMultiplier.Value = 1f; ShieldTowerBlockConfig.BlockBreakThresholdMultiplier.Value = 1.5f; ShieldTowerBlockConfig.KnockbackTakenWhileBlockingMultiplier.Value = 0f; } ApplyGooBlockedHitDamageTakenDefaults(); } private static void SetGooPvpBlockBreakDefaults() { foreach (KeyValuePair weaponBlockExtra in WeaponBlockExtras) { weaponBlockExtra.Value.PvpBlockBreakThresholdMultiplier.Value = (WeaponPrimaryTierIsThree(weaponBlockExtra.Key) ? 10f : 5f); } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.PvpBlockBreakThresholdMultiplier.Value = 10f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.PvpBlockBreakThresholdMultiplier.Value = 10f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.PvpBlockBreakThresholdMultiplier.Value = 20f; } foreach (StaffBlockConfig value in StaffBlockConfigs.Values) { value.PvpBlockBreakThresholdMultiplier.Value = 1f; } } private static bool WeaponPrimaryTierIsThree(WeaponCategory category) { if (category != WeaponCategory.TwoHandedSword) { return category == WeaponCategory.TwoHandedAxe; } return true; } private static void SetCategoryPvpKnockback(CategoryConfig cfg, float value) { cfg.PvpKnockbackForceMultiplier.SetAll(value, value); } private static void SetGooDualAxesJumpAttackDefaults() { if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value)) { CategoryConfig config = GetConfig(WeaponCategory.DualAxes); value.Enabled.Value = true; value.UsedAttack.Value = JumpAttackUsed.Primary4; value.DamageMultiplier.Value = 0.35f; value.StaggerMultiplier.Value = config.StaggerPowerValue.Primary4.Value; value.StaminaRateMultiplier.Value = config.StaminaRateMultiplier.Primary4.Value; value.AnimationSpeedMultiplier.Value = 1f; value.RecoveryAnimationSpeedMultiplier.Value = 1f; value.RotationFactor.Value = config.AttackRotationFactor.Primary4.Value; value.LockRotationAfterAttackTrigger.Value = config.LockRotationAfterAttackTrigger.Primary4.Value; value.PvpDamageMultiplier.Value = 1f; value.PvpStaggerMultiplier.Value = config.PvpStaggerMultiplier.Primary4.Value; value.PvpStaggerDurationSeconds.Value = config.PvpStaggerDurationSeconds.Primary4.Value; value.PvpKnockbackForceMultiplier.Value = config.PvpKnockbackForceMultiplier.Primary4.Value; value.KnockbackForceMultiplier.Value = config.KnockbackForceMultiplier.Primary4.Value; value.AdrenalineMultiplier.Value = 2f; value.LungeDistanceMultiplier.Value = 0f; value.LungeApplicationMode.Value = LungeApplicationMode.BeforeHitbox; if (AttackStartNoiseMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value2)) { value.StartNoiseMultiplier.Value = value2.Primary4.Value; } if (AttackHitNoiseMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value3)) { value.HitNoiseMultiplier.Value = value3.Primary4.Value; } if (AttackRangeMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value4)) { value.RangeMultiplier.Value = value4.Primary4.Value; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value5)) { value.RayWidthMultiplier.Value = value5.Primary4.Value; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value6)) { value.HitboxHeightMultiplier.Value = value6.Primary4.Value; } if (AttackOffsetMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value7)) { value.OffsetMultiplier.Value = value7.Primary4.Value; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value8)) { value.HitboxType.Value = value8.Primary4.Value; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value9)) { value.AngleMultiplier.Value = value9.Primary4.Value; } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value10)) { value.MultiTargetDamagePenalty.Value = value10.Primary4.Value; } } } private static void SetGooPvpDefaults() { foreach (KeyValuePair categoryConfig in CategoryConfigs) { CategoryConfig value = categoryConfig.Value; value.PvpDamageMultiplier.SetAll(1f, 1f); value.PvpStaggerMultiplier.Primary1.Value = DefaultPvpStaggerPowerMultiplier(categoryConfig.Key, AttackRole.Primary1); value.PvpStaggerMultiplier.Primary2.Value = DefaultPvpStaggerPowerMultiplier(categoryConfig.Key, AttackRole.Primary2); value.PvpStaggerMultiplier.Primary3.Value = DefaultPvpStaggerPowerMultiplier(categoryConfig.Key, AttackRole.Primary3); value.PvpStaggerMultiplier.Primary4.Value = DefaultPvpStaggerPowerMultiplier(categoryConfig.Key, AttackRole.Primary4); value.PvpStaggerMultiplier.Secondary.Value = DefaultPvpStaggerPowerMultiplier(categoryConfig.Key, AttackRole.Secondary); value.PvpStaggerDurationSeconds.Primary1.Value = DefaultPvpStaggerDuration(categoryConfig.Key, AttackRole.Primary1); value.PvpStaggerDurationSeconds.Primary2.Value = DefaultPvpStaggerDuration(categoryConfig.Key, AttackRole.Primary2); value.PvpStaggerDurationSeconds.Primary3.Value = DefaultPvpStaggerDuration(categoryConfig.Key, AttackRole.Primary3); value.PvpStaggerDurationSeconds.Primary4.Value = DefaultPvpStaggerDuration(categoryConfig.Key, AttackRole.Primary4); value.PvpStaggerDurationSeconds.Secondary.Value = DefaultPvpStaggerDuration(categoryConfig.Key, AttackRole.Secondary); value.PvpKnockbackForceMultiplier.SetAll(1f, 1f); } GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Secondary.Value = 1.3f; GetConfig(WeaponCategory.Atgeir).PvpDamageMultiplier.Secondary.Value = 1.5f; GetConfig(WeaponCategory.Bow).PvpDamageMultiplier.SetAll(0.65f, 0.65f); GetConfig(WeaponCategory.Crossbow).PvpDamageMultiplier.SetAll(0.6f, 0.6f); GetConfig(WeaponCategory.Unarmed).PvpKnockbackForceMultiplier.Secondary.Value = 1.5f; GetConfig(WeaponCategory.Unarmed).PvpStaggerMultiplier.Secondary.Value = 999f; SetCategoryPvpKnockback(GetConfig(WeaponCategory.OneHandedSword), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.OneHandedAxe), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Club), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Knife), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.DualKnives), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Spear), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Atgeir), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.TwoHandedSword), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.TwoHandedAxe), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Sledge), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Pickaxe), 0.1f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.DualAxes), 0.1f); GetConfig(WeaponCategory.Unarmed).PvpKnockbackForceMultiplier.SetAll(0.7f, 0.7f); GetConfig(WeaponCategory.Unarmed).PvpKnockbackForceMultiplier.Secondary.Value = 1.5f; SetCategoryPvpKnockback(GetConfig(WeaponCategory.Bow), 0f); SetCategoryPvpKnockback(GetConfig(WeaponCategory.Crossbow), 0f); SetGooPvpBlockBreakDefaults(); foreach (KeyValuePair runningAttackConfig in RunningAttackConfigs) { RunningAttackConfig value2 = runningAttackConfig.Value; AttackRole role = RoleFromRunningAttackUsed(value2.UsedAttack.Value); value2.PvpDamageMultiplier.Value = 1f; value2.PvpStaggerMultiplier.Value = DefaultPvpStaggerPowerMultiplier(runningAttackConfig.Key, role); value2.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(runningAttackConfig.Key, role); value2.PvpKnockbackForceMultiplier.Value = 0.1f; } foreach (KeyValuePair jumpAttackConfig in JumpAttackConfigs) { JumpAttackConfig value3 = jumpAttackConfig.Value; AttackRole role2 = RoleFromJumpAttackUsed(NormalizeJumpAttackUsed(jumpAttackConfig.Key, value3.UsedAttack.Value)); value3.PvpDamageMultiplier.Value = 1f; value3.PvpStaggerMultiplier.Value = DefaultPvpStaggerPowerMultiplier(jumpAttackConfig.Key, role2); value3.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(jumpAttackConfig.Key, role2); value3.PvpKnockbackForceMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value4)) { value4.RecoveryAnimationSpeedMultiplier.Value = 1f; } } private static IEnumerable> GetUseCustomMovementModifierEntries() { yield return UseCustomLightArmorMovementModifier; yield return UseCustomMediumArmorMovementModifier; yield return UseCustomHeavyArmorMovementModifier; yield return UseCustomOneHandedSwordMovementModifier; yield return UseCustomOneHandedAxeMovementModifier; yield return UseCustomClubMovementModifier; yield return UseCustomKnifeMovementModifier; yield return UseCustomDualKnivesMovementModifier; yield return UseCustomSpearMovementModifier; yield return UseCustomAtgeirMovementModifier; yield return UseCustomTwoHandedSwordMovementModifier; yield return UseCustomTwoHandedAxeMovementModifier; yield return UseCustomSledgeMovementModifier; yield return UseCustomDualAxesMovementModifier; yield return UseCustomBowMovementModifier; yield return UseCustomCrossbowMovementModifier; yield return UseCustomToolMovementModifier; yield return UseCustomOtherEquipmentMovementModifier; } private static bool TrySetEquipmentModifierValue(Player player, int index, float value) { float[] fieldValue = GetFieldValue(player, "m_equipmentModifierValues"); if (fieldValue == null || index < 0 || index >= fieldValue.Length) { return false; } fieldValue[index] = value; return true; } private static float GetEquipmentModifierPlusSE(Player player, int index, float fallback) { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "GetEquipmentModifierPlusSE", new Type[1] { typeof(int) }, (Type[])null); if (methodInfo == null) { return fallback; } try { return (methodInfo.Invoke(player, new object[1] { index }) is float num) ? num : fallback; } catch { return fallback; } } private static T? GetStaticFieldValue(Type type, string fieldName) where T : class { FieldInfo cachedField = GetCachedField(type, fieldName); if (cachedField == null) { return null; } try { return cachedField.GetValue(null) as T; } catch { return null; } } private static float GetNegativeStaminaFloor() { if (NegativeStaminaFloor == null) { return 0f; } return Mathf.Min(0f, NegativeStaminaFloor.Value); } private static bool IsNegativeStaminaActionAllowed(float currentStamina, float cost, float floor, NegativeStaminaActionGateMode mode) { if (cost <= 0f) { return currentStamina > 0f; } if (currentStamina <= 0f) { return false; } return mode switch { NegativeStaminaActionGateMode.PositiveOnlyLegacy => true, _ => currentStamina - cost >= floor, }; } private static float GetPlayerStamina(Player player) { try { ZNetView fieldValue = GetFieldValue(player, "m_nview"); if ((Object)(object)fieldValue != (Object)null && fieldValue.IsValid() && !fieldValue.IsOwner()) { return fieldValue.GetZDO().GetFloat(ZDOVars.s_stamina, GetPrivateFloat(player, "m_maxStamina", 0f)); } } catch { } return GetPrivateFloat(player, "m_stamina", 0f); } private static void SetPlayerStamina(Player player, float value) { SetPrivateFloat(player, "m_stamina", value); try { ZNetView fieldValue = GetFieldValue(player, "m_nview"); if ((Object)(object)fieldValue != (Object)null && fieldValue.IsValid() && fieldValue.IsOwner()) { fieldValue.GetZDO().Set(ZDOVars.s_stamina, value); } } catch { } } private static int GetPrivateInt(object instance, string fieldName, int fallback) { FieldInfo cachedField = GetCachedField(instance.GetType(), fieldName); if (cachedField == null) { return fallback; } try { object value = cachedField.GetValue(instance); if (value is int result) { return result; } if (value != null && int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2; } } catch { } return fallback; } private static void SetFieldValueRaw(object instance, string fieldName, object? value) { if (instance == null) { return; } FieldInfo cachedField = GetCachedField(instance.GetType(), fieldName); if (cachedField == null || cachedField.IsInitOnly || cachedField.IsLiteral) { return; } try { cachedField.SetValue(instance, value); } catch { } } private static void TryInvokePrivateVoid(object instance, string methodName) { if (instance == null || string.IsNullOrWhiteSpace(methodName)) { return; } try { MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null && method.GetParameters().Length == 0) { method.Invoke(instance, null); } } catch { } } private static bool GetPrivateBool(object instance, string fieldName, bool fallback) { FieldInfo cachedField = GetCachedField(instance.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(bool)) { return fallback; } try { return (bool)cachedField.GetValue(instance); } catch { return fallback; } } private static float GetPrivateFloat(object instance, string fieldName, float fallback) { FieldInfo cachedField = GetCachedField(instance.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(float)) { return fallback; } try { return (float)cachedField.GetValue(instance); } catch { return fallback; } } private static void SetPrivateFloat(object instance, string fieldName, float value) { FieldInfo cachedField = GetCachedField(instance.GetType(), fieldName); if (cachedField == null || cachedField.FieldType != typeof(float) || cachedField.IsInitOnly || cachedField.IsLiteral) { return; } try { cachedField.SetValue(instance, value); } catch { } } private static bool TryGetSEManCharacter(object seMan, out Character? character) { character = GetFieldValue(seMan, "m_character"); return (Object)(object)character != (Object)null; } private static void DebugLogThrottled(string key, float intervalSeconds, string message) { if (string.IsNullOrEmpty(key)) { key = message; } float time = Time.time; if (!DebugLogThrottle.TryGetValue(key, out var value) || !(time < value)) { DebugLogThrottle[key] = time + Mathf.Max(0.5f, intervalSeconds); Log.LogInfo((object)message); } } private static FieldInfo? FindFieldSilent(Type type, string fieldName) { Type type2 = type; while (type2 != null) { try { FieldInfo field = type2.GetField(fieldName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } } catch { } type2 = type2.BaseType; } return null; } private static object? GetFieldValueRaw(object instance, string fieldName) { if (instance == null) { return null; } string key = instance.GetType().FullName + ":" + fieldName; if (!FieldCache.TryGetValue(key, out FieldInfo value)) { value = FindFieldSilent(instance.GetType(), fieldName); FieldCache[key] = value; } try { return value?.GetValue(instance); } catch { return null; } } private static T? GetFieldValue(object instance, string fieldName) where T : class { if (instance == null) { return null; } string key = instance.GetType().FullName + ":" + fieldName; if (!FieldCache.TryGetValue(key, out FieldInfo value)) { value = FindFieldSilent(instance.GetType(), fieldName); FieldCache[key] = value; } return value?.GetValue(instance) as T; } private static T? FindFirstFieldValue(object instance) where T : class { if (instance == null) { return null; } try { FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!typeof(T).IsAssignableFrom(fieldInfo.FieldType)) { continue; } try { if (fieldInfo.GetValue(instance) is T result) { return result; } } catch { } } } catch { } return null; } private static ItemData? GetCurrentWeapon(Character character) { if ((Object)(object)character == (Object)null) { return null; } Type type = ((object)character).GetType(); MethodInfo methodInfo = GetCachedMethod(type, "GetCurrentWeapon") ?? GetCachedMethod(type, "GetRightItem") ?? GetCachedMethod(type, "GetWeapon"); try { object? obj = methodInfo?.Invoke(character, Array.Empty()); return (ItemData?)((obj is ItemData) ? obj : null); } catch { return null; } } private static FieldInfo? GetCachedField(Type type, string fieldName) { string key = type.FullName + ":" + fieldName; if (!FieldCache.TryGetValue(key, out FieldInfo value)) { value = FindFieldSilent(type, fieldName); FieldCache[key] = value; } return value; } private static MethodInfo? GetCachedMethod(Type type, string name) { return GetCachedMethod(type, name, Type.EmptyTypes); } private static MethodInfo? GetCachedMethod(Type type, string name, Type[] argumentTypes) { string text = ((argumentTypes == null || argumentTypes.Length == 0) ? string.Empty : string.Join(",", argumentTypes.Select((Type t) => t.FullName))); string key = type.FullName + ":" + name + ":" + text; if (!MethodCache.TryGetValue(key, out MethodInfo value)) { value = AccessTools.Method(type, name, argumentTypes ?? Type.EmptyTypes, (Type[])null); MethodCache[key] = value; } return value; } private static Character? GetAttacker(HitData hit) { if (hit == null) { return null; } if (_hitDataGetAttacker != null) { try { object? obj = _hitDataGetAttacker.Invoke(hit, Array.Empty()); Character val = (Character)((obj is Character) ? obj : null); if (val != null) { return val; } } catch { } } try { foreach (FieldInfo allInstanceField in GetAllInstanceFields(((object)hit).GetType())) { if (!typeof(Character).IsAssignableFrom(allInstanceField.FieldType)) { continue; } string text = SafeLower(allInstanceField.Name); if (!text.Contains("attacker") && !text.Contains("owner") && !text.Contains("source")) { continue; } try { object? value = allInstanceField.GetValue(hit); Character val2 = (Character)((value is Character) ? value : null); if (val2 != null) { return val2; } } catch { } } } catch { } return null; } internal static WeaponCategory ClassifyWeapon(ItemData? item) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Invalid comparison between Unknown and I4 //IL_00e4: Unknown result type (might be due to invalid IL or missing references) if (item == null || item.m_shared == null) { return WeaponCategory.Unarmed; } string text = SafeLower(((object)Unsafe.As(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString()); string text2 = SafeLower(((object)Unsafe.As(ref item.m_shared.m_skillType)/*cast due to .constrained prefix*/).ToString()); string text3 = SafeLower(((object)Unsafe.As(ref item.m_shared.m_animationState)/*cast due to .constrained prefix*/).ToString()); string text4 = SafeLower(item.m_shared.m_name) + " " + SafeLower(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty); if (IsAbyssalHarpoon(item)) { return WeaponCategory.Spear; } bool flag = (int)item.m_shared.m_itemType == 19 || text.Contains("tool"); bool flag2 = item.m_shared.m_damages.m_pickaxe > 0f || item.GetDamage().m_pickaxe > 0f; if (text2.Contains("pickaxe") || text4.Contains("pickaxe") || flag2) { return WeaponCategory.Pickaxe; } if (flag && text3.Contains("twohandedclub")) { return WeaponCategory.Pickaxe; } bool flag3 = text.Contains("twohanded") || text.Contains("two handed") || text4.Contains("twohand") || text4.Contains("2h"); bool flag4 = text4.Contains("sledge") || text4.Contains("demolisher") || text4.Contains("stagbreaker") || (text2.Contains("club") && (flag3 || text4.Contains("hammer") || text3.Contains("twohandedclub") || text3.Contains("twohandedaxe"))); bool flag5 = text2.Contains("axe") || text4.Contains("battleaxe") || text4.Contains("battle axe") || text4.Contains("battle_axe") || text4.Contains("greataxe") || text4.Contains("great axe") || text4.Contains("great_axe") || text4.Contains("twohandedaxe") || text4.Contains("two handed axe") || text4.Contains("2h axe") || text4.Contains("axe"); bool flag6 = text2.Contains("spear") || text4.Contains("spear") || text4.Contains("splitnir") || text4.Contains("splitner"); bool flag7 = text4.Contains("dualaxe") || text4.Contains("dual axe") || text4.Contains("dual_axe") || text4.Contains("dual-axe") || text4.Contains("dualaxes") || text4.Contains("dual axes") || text4.Contains("twinaxe") || text4.Contains("twin axe") || text4.Contains("pairedaxe") || text4.Contains("paired axe") || text4.Contains("berserkir"); if (text3.Contains("dualaxes") || flag7) { return WeaponCategory.DualAxes; } if (text3.Contains("greatsword")) { return WeaponCategory.TwoHandedSword; } if (text3.Contains("twohandedaxe")) { if (flag4 && !flag5) { return WeaponCategory.Sledge; } if (!flag5) { return WeaponCategory.Sledge; } return WeaponCategory.TwoHandedAxe; } if (text3.Contains("atgeir")) { if (!flag6) { return WeaponCategory.Atgeir; } return WeaponCategory.Spear; } if (text3.Contains("twohandedclub")) { return WeaponCategory.Sledge; } if (text3.Contains("knives") && (text4.Contains("skoll") || text4.Contains("hati") || text4.Contains("dual") || text4.Contains("paired") || text4.Contains("twin"))) { return WeaponCategory.DualKnives; } if (text3.Contains("knives")) { return WeaponCategory.Knife; } if (text3.Contains("crossbow")) { return WeaponCategory.Crossbow; } if (text3.Contains("bow")) { return WeaponCategory.Bow; } if (text3.Contains("staves") || text3.Contains("magicitem")) { return WeaponCategory.Magic; } if (flag4) { return WeaponCategory.Sledge; } if (text4.Contains("atgeir") || text2.Contains("polearm")) { return WeaponCategory.Atgeir; } if (text2.Contains("sword")) { if (!flag3 && !text4.Contains("krom") && !text4.Contains("greatsword") && !text4.Contains("slayer")) { return WeaponCategory.OneHandedSword; } return WeaponCategory.TwoHandedSword; } if (text2.Contains("axe")) { if (!flag3 && !text4.Contains("battleaxe") && !text4.Contains("battle_axe") && !text4.Contains("greataxe")) { return WeaponCategory.OneHandedAxe; } return WeaponCategory.TwoHandedAxe; } if (text2.Contains("club")) { if (flag3 || text4.Contains("sledge") || text4.Contains("hammer")) { return WeaponCategory.Sledge; } return WeaponCategory.Club; } if ((text2.Contains("knife") || text4.Contains("knife") || text4.Contains("dagger")) && (text4.Contains("skoll") || text4.Contains("hati") || text4.Contains("dual") || text4.Contains("paired") || text4.Contains("twin"))) { return WeaponCategory.DualKnives; } if (text2.Contains("knife") || text4.Contains("knife") || text4.Contains("dagger")) { return WeaponCategory.Knife; } if (text2.Contains("spear")) { return WeaponCategory.Spear; } if (text2.Contains("crossbow")) { return WeaponCategory.Crossbow; } if (text2.Contains("bow")) { return WeaponCategory.Bow; } if (text2.Contains("magic") || text4.Contains("staff") || text4.Contains("wand")) { return WeaponCategory.Magic; } if (text2.Contains("unarmed")) { return WeaponCategory.Unarmed; } return WeaponCategory.Other; } private static CategoryConfig GetConfig(WeaponCategory category) { if (!CategoryConfigs.TryGetValue(category, out CategoryConfig value)) { return CategoryConfigs[WeaponCategory.Other]; } return value; } private static bool Debug(ConfigEntry? category = null) { if (DebugLogging == null || !DebugLogging.Value) { return false; } if (VerboseDebugLogging != null && VerboseDebugLogging.Value) { return true; } return category?.Value ?? true; } private static string SafeLower(string? value) { if (!string.IsNullOrEmpty(value)) { return value.ToLowerInvariant(); } return string.Empty; } private static string SafeName(Character character) { try { return ((Object)character).name; } catch { return "unknown"; } } } public enum WeaponCategory { TwoHandedSword, TwoHandedAxe, Sledge, SledgeAlt, Pickaxe, DualAxes, Atgeir, OneHandedSword, OneHandedAxe, Club, Knife, DualKnives, Spear, Bow, Crossbow, Magic, Unarmed, Other } public enum AttackRole { Primary1, Primary2, Primary3, Primary4, Secondary } public enum RunningAttackUsed { Primary1, Primary2, Primary3, Secondary } public enum JumpAttackUsed { Primary1, Primary2, Primary3, Primary4, Secondary } public enum JumpAttackUsedBasic { Primary1, Primary2, Primary3, Secondary } public enum StaggerApplicationMode { MultiplyVanilla, SetFinalMultiplier } public enum HyperArmorMode { Off, Balanced, FullAttackAnimation, WhileHoldingWeaponType } public enum StaminaRegenCurveMode { Vanilla, Constant, Reverted } public enum NegativeStaminaActionGateMode { PositiveOnlyLegacy, RequireCostWithinFloor } public enum AttackHitboxType { Horizontal, Vertical } public enum AttackMultiTargetDamagePenaltyMode { Enabled, Disabled } public enum AttackMovementApplicationMode { BeforeHitbox, FullAnimation } public enum AttackMovementGroundingMode { Always, GroundOnly } public enum LungeApplicationMode { BeforeHitbox, FullAnimation } public enum BlockCancelMode { Disabled, BeforeHitbox, AfterHitbox, FullAnimation } public enum HyperArmorDamageMitigationMode { FinalHealthRestore, RawHitData, RawHitDataAndFinalHealthRestore } public sealed class ConfigurationManagerAttributes { public int? Order { get; set; } public string? DispName { get; set; } public string? Category { get; set; } } }