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.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.3.3")] 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 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; } } 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 DurabilitySnapshotBox { public float Before { get; set; } public DurabilitySnapshotBox(float before) { Before = before; } } 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 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 parryBonusMultiplier, ConfigEntry parryWindowTimeMultiplier, ConfigEntry pvpParryBonusMultiplier, ConfigEntry pvpBlockForceMultiplier, ConfigEntry pvpBlockBreakThresholdMultiplier, ConfigEntry soulsLikeBlockBreak, ConfigEntry soulsLikeBlockStaminaMode, ConfigEntry soulsLikeBlockStaminaBaseRate, ConfigEntry knockbackTakenWhileBlockingMultiplier, ConfigEntry blockingMovementSpeedMultiplier, ConfigEntry enableRunningWhileBlocking, ConfigEntry runningWhileBlockingMovementSpeedMultiplier) { 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 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 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) { 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 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 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) { 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? 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? 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; 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 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 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); if (TryGetZdo(_character, out ZDO zdo)) { zdo.Set("GCO_TestDummyWasAttacked", false); zdo.Set("GCO_TestDummyNextHealTime", 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); if (_character.GetHealth() <= 0.5f) { _character.SetHealth(Mathf.Max(1f, pvpTestDummyMaxHealth)); } if (val != null && val.GetBool("GCO_TestDummyWasAttacked", false)) { float num = val.GetFloat("GCO_TestDummyNextHealTime", 0f); if (num <= 0f) { val.Set("GCO_TestDummyNextHealTime", Time.time + Mathf.Max(0.25f, PvpTestDummyFullHealIntervalSeconds.Value)); } else if (Time.time >= num) { _character.SetHealth(pvpTestDummyMaxHealth); SetPrivateFloat(_character, "m_staggerDamage", 0f); val.Set("GCO_TestDummyWasAttacked", false); val.Set("GCO_TestDummyNextHealTime", 0f); } } } } 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 int SkeletonItemQuality { get; } public int SkeletonResolvedSpawnCap { 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); SkeletonItemQuality = ((item == null) ? 1 : Mathf.Clamp(item.m_quality, 1, 4)); SkeletonResolvedSpawnCap = ((IsSkeleton && item != null) ? GetStaffSkeletonConfiguredSpawnCapForItem(item) : 0); 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 PvpDummyDebugState { public float HealthBefore { get; } public float StaggerBefore { get; } public PvpDummyDebugState(float healthBefore, float staggerBefore) { HealthBefore = healthBefore; StaggerBefore = staggerBefore; } } private sealed class ProcessedHitMarker { public static readonly ProcessedHitMarker Instance = new ProcessedHitMarker(); } 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__508_0; public static Func <>9__508_1; public static Func <>9__513_0; public static Func <>9__513_1; public static Func <>9__533_1; public static ConsoleOptionsFetcher <>9__533_0; public static Func, ConfigEntryBase> <>9__537_1; public static Func>, KeyValuePair> <>9__537_2; public static Func <>9__553_0; public static Func <>9__625_0; public static Func <>9__627_0; public static Func <>9__633_0; public static Func <>9__680_0; public static Func <>9__685_0; public static Func <>9__685_1; public static Func <>9__757_0; public static Func <>9__757_1; public static Func <>9__761_0; public static Func, object> <>9__817_1; public static ConditionalWeakTable.CreateValueCallback <>9__943_0; public static Func <>9__1237_0; internal bool b__508_0(string part) { return !string.IsNullOrWhiteSpace(part); } internal bool b__508_1(string part) { return !string.IsNullOrWhiteSpace(part); } internal bool b__513_0(string t) { return !string.IsNullOrWhiteSpace(t); } internal string b__513_1(string t) { return t.Trim(); } internal string b__533_1(string key) { if (!GcoFlatBranchDisplayNames.TryGetValue(key, out string value)) { return key; } return value; } internal List b__533_0() { return new List { "goo", "vanilla" }; } internal ConfigEntryBase b__537_1(KeyValuePair pair) { return pair.Value; } internal KeyValuePair b__537_2(IGrouping> group) { return group.First(); } internal bool b__553_0(string x) { return !string.IsNullOrWhiteSpace(x); } internal bool b__625_0(MethodInfo m) { return m.Name == "Stagger"; } internal bool b__627_0(Type t) { return t != null; } internal string b__633_0(Type p) { return p.Name; } internal bool b__680_0(Food food) { if (food != null) { return food.CanEatAgain(); } return false; } internal bool b__685_0(Food food) { if (food != null) { return food.CanEatAgain(); } return false; } internal float b__685_1(Food food) { return food.m_time; } internal bool b__757_0(MethodInfo m) { if (m.Name == "Start") { return m.ReturnType == typeof(bool); } return false; } internal bool b__757_1(MethodInfo m) { return m.Name == "Start"; } internal bool b__761_0(MethodInfo m) { if (m.Name == "StartAttack") { return m.ReturnType == typeof(bool); } return false; } internal object b__817_1(KeyValuePair kvp) { return kvp.Key; } internal CombatDingState b__943_0(HitData _) { return new CombatDingState(); } internal string b__1237_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.3.3"; 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 _startupLegacyConfigNeedsGooRewrite; private bool _startupConfigVersionNeedsBump; private static readonly Dictionary GcoCommandPrimaryPaths = new Dictionary(); 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 readonly ConditionalWeakTable DurabilitySnapshots = new ConditionalWeakTable(); private static bool _gcoFlatConsoleCommandsRegistered; 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 Roll = null; internal static ConfigEntry DebugLogging = null; internal static ConfigEntry VerboseDebugLogging = null; internal static ConfigEntry DebugAttackLifecycle = 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 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 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 GlobalFlankingDamageMultiplier = null; internal static ConfigEntry GlobalPvpFlankingDamageMultiplier = 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 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 GlobalBackstabDamageMultiplier = 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 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 PvpTestDummyFullHealIntervalSeconds = 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 VanillaJumpForce = 10f; private const float VanillaJumpStaminaUsage = 10f; private const float VanillaRunStaminaDrain = 10f; private const float VanillaDodgeStaminaUsage = 10f; private const float VanillaStaminaRegen = 5f; 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 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 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 SpearProjectileSpeedMultiplier = null; internal static ConfigEntry SpearLoyaltyMode = 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> AttackBuildingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> AttackStaggeredEnemyDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> BackstabDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> WeaponFlankingDamageMultipliers = new Dictionary>(); internal static readonly Dictionary> WeaponPvpFlankingDamageMultipliers = 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 StaffLightningProjectileSpeedMultiplier = null; internal static ConfigEntry StaffIceShardsProjectileSpeedMultiplier = 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 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 Dictionary AoeAttackContexts = new Dictionary(); [ThreadStatic] private static Stack? CurrentAttackHitContexts; [ThreadStatic] private static StaffSpawnOrigin? CurrentStaffSpawnDamageOrigin; [ThreadStatic] private static StaffSpawnOrigin? CurrentSpawnAbilityAoeSetupOrigin; [ThreadStatic] private static StaffSpawnOrigin? CurrentSpawnAbilityRuntimeOrigin; 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 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 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 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 PvpTestDummyZdoWasAttacked = "GCO_TestDummyWasAttacked"; private const string PvpTestDummyZdoNextHealTime = "GCO_TestDummyNextHealTime"; private const string StaffSkeletonResolvedMaxInstancesZdo = "GCO_StaffSkeletonResolvedMaxInstances"; private const string StaffSkeletonResolvedItemQualityZdo = "GCO_StaffSkeletonResolvedItemQuality"; 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[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[] MeleeFeatureWeaponCategories = new WeaponCategory[13] { WeaponCategory.TwoHandedSword, WeaponCategory.TwoHandedAxe, 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[] 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[17] { "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", "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 VanillaStaggeredEnemyDamageMultiplier = 2f; 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(); _hitDataGetAttacker = AccessTools.Method(typeof(HitData), "GetAttacker", (Type[])null, (Type[])null); PatchCoreDamageAndStagger(); PatchAttackLifecycle(); PatchPlayerAndSystemTuning(); PatchStaffSpawnAndProjectileSystems(); RegisterGcoFlatConsoleCommands(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate(object _, SettingChangedEventArgs eventArgs) { HandleGcoConfigSettingChanged(eventArgs); 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(); AoeAttackContexts.Clear(); StopAllPvpStaggerSpeedCoroutines(); }; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Goo's Combat Overhaul 1.3.3 loaded."); } private void OnDestroy() { _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(); StaffSpawnProjectileOrigins.Clear(); StaffSpawnAoeOrigins.Clear(); ProjectileAttackContexts.Clear(); SpearLoyaltyConsumeItemOriginals.Clear(); AoeAttackContexts.Clear(); 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(); AoeAttackContexts.Clear(); 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 (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) { 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 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); if (ShouldRunConfigMigration()) { MigrateCompactConfigValue(section, key, text, val); string text2 = LegacyTitleConfigKey(key); if (!string.Equals(text2, key, StringComparison.Ordinal) && !string.Equals(text2, text, StringComparison.Ordinal)) { MigrateCompactConfigValue(section, text2, text, val); } } return val; } private ConfigEntry BindBootstrapConfig(string section, string key, T defaultValue, string description, AcceptableValueBase? acceptableValues = null) { string prettyKey = PrettyConfigKey(key); ConfigEntry val = BindConfigEntry(section, prettyKey, defaultValue, description, acceptableValues); RegisterCommandConfigEntry(section, prettyKey, (ConfigEntryBase)(object)val); return val; } private ConfigEntry BindConfigEntry(string section, string prettyKey, T defaultValue, string description, AcceptableValueBase? acceptableValues) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description, acceptableValues, new object[1] { new ConfigurationManagerAttributes { Order = GetConfigDisplayOrder(section, prettyKey) } }); return ((BaseUnityPlugin)this).Config.Bind(section, prettyKey, defaultValue, val); } 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("Flanking", StringComparison.OrdinalIgnoreCase)) { return 910000; } if (section.Equals("Stealth", StringComparison.OrdinalIgnoreCase)) { return 900000; } 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; } if (section.Equals("Bows - Ranged", StringComparison.OrdinalIgnoreCase)) { return 790000; } if (section.Equals("Crossbows - Ranged", StringComparison.OrdinalIgnoreCase)) { return 789000; } if (section.Equals("Spears - Ranged", StringComparison.OrdinalIgnoreCase)) { return 788000; } 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) { for (int i = 0; i < ConfigWeaponSectionOrder.Length; i++) { string text = ConfigWeaponSectionOrder[i]; if (section.Equals(text, StringComparison.OrdinalIgnoreCase) || section.StartsWith(text + " - ", StringComparison.OrdinalIgnoreCase)) { return i; } } return -1; } private static int GetConfigWeaponFeatureOrder(string section) { 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(" - Flanking", StringComparison.OrdinalIgnoreCase)) { return 7000; } if (section.EndsWith(" - Stealth", StringComparison.OrdinalIgnoreCase)) { return 6500; } 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, "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("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("Stealth", StringComparison.OrdinalIgnoreCase) || section.EndsWith(" - Stealth", StringComparison.OrdinalIgnoreCase)) { if (text.Contains("backstabdamagemultiplier")) { return 900; } if (text.Contains("backstabdamagestackable")) { return 880; } if (text.Contains("attackstartnoise")) { return 850; } if (text.Contains("attackhitnoise")) { return 840; } } 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/legacy configs at 1.0.4 or older are rewritten to Goo defaults once; newer configs preserve compatible user values while newly added entries for the current target version receive Goo defaults."); 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."); SetToVanillaRuleset = BindBootstrapConfig("General", "ResetToVanilla", defaultValue: false, "One-shot reset button. Set true to rewrite combat/balance config entries back to vanilla/no-op Valheim behavior, then this value automatically returns to false. Debug/general runtime toggles are preserved."); } private void EvaluateStartupConfigVersionDecision() { if (ConfigVersion != null) { string value = ConfigVersion.Value; _startupLegacyConfigNeedsGooRewrite = IsVersionOlderThanOrEqualTo(value, "1.0.4"); _startupConfigVersionNeedsBump = IsVersionOlderThan(value, "1.3.3"); _startupConfigVersionDecisionInitialized = true; } } private bool ShouldRunConfigMigration() { if (_startupConfigVersionDecisionInitialized) { if (!_startupLegacyConfigNeedsGooRewrite) { return _startupConfigVersionNeedsBump; } return true; } 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); } dictionary.Remove(key); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Migrated config entry [" + section + "] " + compactKey + " -> " + prettyKey + ".")); } } catch (Exception ex) { ((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("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", "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."); 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, MeleeFeatureWeaponCategories); BindFeatureApplicationToggles("Counter Damage", "counter damage", "CounterDamage", ModFeature.CounterDamage, MeleeFeatureWeaponCategories); BindFeatureApplicationToggles("Hit-Stop / Hitlag", "hit-stop", "HitStop", ModFeature.HitStop, MeleeFeatureWeaponCategories); BindFeatureApplicationToggles("Attack movement and lunge", "attack movement and lunge", "AttackMovementAndLunge", ModFeature.AttackMovement, MeleeFeatureWeaponCategories); BindFeatureApplicationToggles("Attack rotation", "attack rotation", "AttackRotation", ModFeature.AttackRotation, MeleeFeatureWeaponCategories); BindFeatureApplicationToggles("Hit Ray", "hit ray", "HitRay", ModFeature.HitRay, DirectHitRayWeaponCategories); BindFeatureApplicationToggles("Stealth", "stealth", "Stealth", ModFeature.StealthAttributes, StealthAttributeWeaponCategories); BindFeatureApplicationToggles("Multi target penalty", "multi-target damage penalty", "MultiTargetPenalty", ModFeature.MultiTargetPenalty, DirectHitRayWeaponCategories); 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 void BindConfig() { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { 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."); 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 backstab, counter damage, and flanking. If false, it only applies when no other tracked conditional bonus is active."); BackstabDamageStackableWithOtherBonuses = BindConfig("Stealth", "BackstabDamageStackableWithOtherBonuses", defaultValue: true, "If true, backstab/sneak damage can stack with other conditional damage bonuses such as staggered-enemy, counter damage, and flanking 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 backstab, staggered-enemy, and flanking 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("Flanking", "EnableFlankingDamage", defaultValue: true, "If true, player attacks can receive configurable bonus damage when they hit the horizontal rear/flank sector of a PvE enemy or PvP target."); FlankingDamageStackableWithOtherBonuses = BindConfig("Flanking", "FlankingDamageStackableWithOtherBonuses", defaultValue: true, "If true, flanking damage can stack with other conditional damage bonuses such as backstab, counter damage, and staggered-enemy bonuses. If false, it only applies when no other tracked conditional bonus is active."); SuccessfulFlankDing = BindConfig("Flanking", "SuccessfulFlankDing", 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("Flanking", "FlankingPveRearAngleDegrees", 120f, "Total horizontal rear-sector angle used for PvE flanking checks. 120 means a 120-degree slice centered on the target's back; 0 disables PvE flanking by angle; 360 makes every horizontal direction count."); FlankingPvpRearAngleDegrees = BindConfig("Flanking", "FlankingPvpRearAngleDegrees", 90f, "Total horizontal rear-sector angle used for PvP flanking checks. 90 means a 90-degree slice centered on the target's back; 0 disables PvP flanking by angle; 360 makes every horizontal direction count."); GlobalFlankingDamageMultiplier = BindConfig("Flanking", "GlobalFlankingDamageMultiplier", 1f, "Global PvE flanking damage multiplier. Stacks with per-weapon flanking damage. 1 leaves per-weapon values unchanged."); GlobalPvpFlankingDamageMultiplier = BindConfig("Flanking", "GlobalPvpFlankingDamageMultiplier", 1f, "Global PvP flanking damage multiplier. Stacks with per-weapon PvP flanking 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."); 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."); GlobalBackstabDamageMultiplier = BindConfig("Stealth", "GlobalBackstabDamageMultiplier", 2f, "Global backstab/sneak damage multiplier. Final tooltip/runtime backstab equals this global value multiplied by the weapon/attack local BackstabDamageMultiplier. Goo preset default is 2."); 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."); 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."); 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. They restore to full health on the configured heal interval and are clamped above death for repeat testing."); 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."); PvpTestDummyFullHealIntervalSeconds = BindConfig("Debug", "PvpTestDummyFullHealIntervalSeconds", 3f, "Seconds between full-health restores for GCO PvP test dummies. Goo default is 3 seconds."); EnemyStaggerDamageDealtMultiplier = BindConfig("Staggering", "EnemyStaggerDamageDealtMultiplier", 1f, "Multiplier for stagger/poise damage dealt by enemies. 1 is vanilla."); EnemyKnockbackDealtMultiplier = BindConfig("Knockback", "EnemyKnockbackDealtMultiplier", 1f, "Multiplier for knockback/push force dealt by enemies to players. 1 is vanilla; 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: true, "If true, starting a melee attack or starting to block while holding movement input faces the player toward movement-input direction instead of forcing crosshair/look direction. The camera/crosshair look direction is not changed."); 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."); 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."); 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."); 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, 1f, 1f, 0f, 0f, 2.4f, 2f, primary2LungeDistanceMultiplier, primary3LungeDistanceMultiplier, 1f, 1f, 1f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 6f, 1f, null, null, 0.4f); float? primary1DamageRateMultiplier = 2f; primary3LungeDistanceMultiplier = 0.85f; primary2LungeDistanceMultiplier = 0.6f; 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, null, null, 1f, 1f, 0f, 0f, 2f, 2f, null, null, 1f, 1f, 1f, 1f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 0.6f, primary3LungeDistanceMultiplier, primary2LungeDistanceMultiplier, 1.35f); 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); 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); 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); 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); 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); 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); 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); 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); 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); 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); AddCategory(WeaponCategory.Bow, "Bows", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f); AddCategory(WeaponCategory.Crossbow, "Crossbows", 1f, 1f, HyperArmorMode.Off, HyperArmorMode.Off, 1.15f, 1.15f); primary2LungeDistanceMultiplier = 1.5f; primary3LungeDistanceMultiplier = 0.7f; 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, primary2LungeDistanceMultiplier, null, 1.2f, 1.3f, 2f, 2f, primaryLockRotationAfterAttackTrigger: true, secondaryLockRotationAfterAttackTrigger: true, 1f, 1f, 1f, primary3LungeDistanceMultiplier, null, 1.3f); 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); AddRunningAttackConfigs(); AddJumpAttackConfigs(); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); 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."); BowDrawSpeedMultiplier = BindConfig("Bows - Ranged", "BowDrawSpeedMultiplier", 1f, "Multiplier for bow draw percentage gain. 1 is vanilla; 2 reaches full draw twice as fast."); 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."); 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."); 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 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, 0.5f); 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, "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 - (base rate + effective block armor / 1000)) / 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, "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 - (base rate + effective block armor / 1000)) / 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, "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 - (base rate + effective block armor / 1000)) / 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."); StaffLightningProjectileSpeedMultiplier = BindConfig("Magic - Dundr", "ProjectileSpeedMultiplier", 1f, "Projectile speed multiplier for Dundr / StaffLightning. 1 is vanilla/no-op."); StaffIceShardsProjectileSpeedMultiplier = BindConfig("Magic - Staff of Frost", "ProjectileSpeedMultiplier", 1f, "Projectile speed multiplier for Staff of Frost / StaffIceShards. 1 is vanilla/no-op."); StaffLightningProjectileCountMultiplier = BindConfig("Magic - Dundr", "ProjectileCountMultiplier", 1f, "Projectile count multiplier for Dundr / StaffLightning. 1 is vanilla/no-op."); 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 spawned Staff of the Wild vines/roots using Valheim's native SpawnAbility m_maxSpawned cap. Vanilla/default 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, _ => 0.7f, }; 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, "BackstabDamageMultiplier", 1f, "Local backstab/sneak damage multiplier for this staff. Final tooltip/runtime backstab equals GlobalBackstabDamageMultiplier 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."), 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, "BackstabDamageMultiplier", 1f, "Local backstab/sneak damage multiplier for this staff. Final tooltip/runtime backstab equals GlobalBackstabDamageMultiplier 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, 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 AddRunningAttackConfigs() { (WeaponCategory, string)[] array = new(WeaponCategory, string)[11] { (WeaponCategory.TwoHandedSword, "Two-Handed Swords"), (WeaponCategory.TwoHandedAxe, "Two-Handed 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.Unarmed, "Unarmed"), (WeaponCategory.Other, "Other") }; for (int i = 0; i < array.Length; i++) { (WeaponCategory, string) tuple = array[i]; WeaponCategory item = tuple.Item1; string item2 = tuple.Item2; string section = WeaponFeatureSection(item2, "Running Attack"); float defaultValue = GooRunningAttackMinimumVelocity(item); float defaultValue2 = item switch { WeaponCategory.TwoHandedAxe => 0f, WeaponCategory.OneHandedSword => 2f, WeaponCategory.Club => 2f, _ => 2.5f, }; float defaultValue3 = DefaultRunningAttackDamageMultiplier(item); RegisterRunningAttackBuildingDamageMultiplier(item, item2); RegisterRunningAttackStaggeredEnemyDamageMultiplier(item, item2); RunningAttackConfigs[item] = new RunningAttackConfig(BindConfig(section, "EnableRunningAttack", item != WeaponCategory.Knife && item != WeaponCategory.DualKnives, "If true, a primary melee attack started while the player is actually running attempts to use that weapon's configured running-attack chain animation. The moveset is controlled by RunningAttackUsed. Sledges, Dual Axes, knives, and dual knives are disabled by Goo default."), 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(item), "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", 1f, "Attack stamina-cost multiplier used only by this weapon category's running attack. This does not inherit the selected vanilla attack slot's stamina config. 1 leaves running attack stamina cost unchanged."), BindConfig(section, "RunningAttackAnimationSpeedMultiplier", (item == 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", (item == 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(item), "Hyperarmor mode used only by this weapon category's running attack. Goo default mirrors the selected running moveset's hyperarmor."), BindConfig(section, "RunningAttackHyperArmorDamageTakenMultiplier", DefaultRunningAttackHyperArmorDamageTakenMultiplier(item), "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(item), "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(item), "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(item), "If true, this running attack can deal counter damage. Goo default mirrors the selected running moveset's counter-damage setting."), BindConfig(section, "RunningAttackCounterDamageMultiplier", DefaultRunningAttackCounterDamageMultiplier(item), "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(item), "Hit-stop duration multiplier for this running attack. Goo default follows this weapon type's hit-stop duration multiplier."), BindConfig(section, "RunningAttackPvpHitStopDurationMultiplier", DefaultRunningAttackPvpHitStopDurationMultiplier(item), "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, "RunningAttackBackstabDamageMultiplier", DefaultGooBackstabDamageMultiplier(item), "Local backstab/sneak damage multiplier used only by this running attack. Final runtime backstab equals GlobalBackstabDamageMultiplier x this local value."), BindConfig(section, "RunningAttackMovementModifier", 1f, "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."), 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(item, RoleFromRunningAttackUsed(DefaultRunningAttackUsed(item))), "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", 1f, "Melee hit-ray thickness multiplier used only during running attacks. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra. 1 is vanilla/no-op."), BindConfig(section, "Running Attack Hit Ray Start Height", (item == 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", AttackMultiTargetDamagePenaltyMode.Enabled, "Controls Attack.m_lowerDamagePerHit for this running attack. Enabled allows Valheim's multi-target damage falloff; Disabled prevents damage loss across multiple targets.")); } } private void AddJumpAttackConfigs() { AddJumpAttackConfig(WeaponCategory.TwoHandedSword, "Two-Handed Swords", enabledByDefault: true, 1f, 1f); AddJumpAttackConfig(WeaponCategory.TwoHandedAxe, "Two-Handed Axes", enabledByDefault: true, 1f, 1f, 0.6f); AddJumpAttackConfig(WeaponCategory.DualAxes, "Dual Axes", enabledByDefault: true, 0.45f, 1f); AddJumpAttackConfig(WeaponCategory.Atgeir, "Atgeirs", enabledByDefault: true, 1f, 1f); AddJumpAttackConfig(WeaponCategory.OneHandedSword, "One-Handed Swords", enabledByDefault: true, 1f, 1f); AddJumpAttackConfig(WeaponCategory.OneHandedAxe, "One-Handed Axes", enabledByDefault: true, 1f, 1.7f); AddJumpAttackConfig(WeaponCategory.Club, "Clubs and Maces", enabledByDefault: true, 1f, 1f); } 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", 1f, "Attack stamina-cost multiplier used only by this weapon category's jump attack. This overrides normal attack-slot stamina multipliers; 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, "JumpAttackBackstabDamageMultiplier", DefaultGooBackstabDamageMultiplier(category), "Local backstab/sneak damage multiplier used only by this jump attack. Final runtime backstab equals GlobalBackstabDamageMultiplier 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", 1f, "Melee hit-ray thickness multiplier used only during jump attacks. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra. 1 is vanilla/no-op."), 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", AttackMultiTargetDamagePenaltyMode.Enabled, "Controls Attack.m_lowerDamagePerHit for this jump attack. Enabled allows Valheim's multi-target damage falloff; Disabled prevents damage loss across multiple targets.")); } 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) { bool flag = IsMinimalRangedConfigCategory(category); bool flag2 = category == WeaponCategory.Pickaxe; bool flag3 = !flag && !flag2; bool flag4 = !flag; bool exposeSecondary2 = !flag; bool expose = category != WeaponCategory.Magic; bool expose2 = category == WeaponCategory.Magic; bool expose3 = category != WeaponCategory.Magic; bool expose4 = flag3 || flag2; bool flag5 = flag3 && category != WeaponCategory.Sledge; bool expose5 = (flag3 || flag2) && category != WeaponCategory.Sledge; _bindCategoryForCurrentCategory = category; _bindPrimary3ForCurrentCategory = flag4; _bindPrimary4ForCurrentCategory = flag4; 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, "Stealth"); string section16 = WeaponFeatureSection(section, "Multi Target Penalty"); string section17 = WeaponFeatureSection(section, "Destruction"); float num = DefaultGooAirborneDamageMultiplier(category); 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(!flag, flag4, 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, flag4, 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, flag4, section3, "DamageToStaggeredEnemyMultiplier", DefaultGooDamageToStaggeredEnemyMultiplier(category, AttackRole.Primary1), DefaultGooDamageToStaggeredEnemyMultiplier(category, AttackRole.Primary2), DefaultGooDamageToStaggeredEnemyMultiplier(category, AttackRole.Primary3), DefaultGooDamageToStaggeredEnemyMultiplier(category, 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(flag3, flag4, 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(flag3, flag4, 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(flag3, flag4, section4, "StaggerTakenMultiplierDuringHyperArmor", primaryStaggerTakenMultiplierDuringHyperArmor, secondaryStaggerTakenMultiplierDuringHyperArmor, "Incoming stagger multiplier during this attack slot's hyperarmor. 0 means stagger immunity; 1 means vanilla.", exposeSecondary2), BindRoleSettings(flag3, flag4, section13, "KnockbackTakenMultiplierDuringHyperArmor", primaryKnockbackTakenMultiplierDuringHyperArmor, secondaryKnockbackTakenMultiplierDuringHyperArmor, "Incoming push/knockback multiplier during this attack slot's hyperarmor. 0 means knockback immunity; 1 means vanilla.", exposeSecondary2), BindRoleSettings(flag3, flag4, 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(flag3, flag4, 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(flag3, flag4, 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, flag4, section5, "HitStopDurationMultiplier", DefaultGooHitStopDurationMultiplier(category), DefaultGooHitStopDurationMultiplier(category), "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, flag4, section5, "PvpHitStopDurationMultiplier", DefaultGooPvpHitStopDurationMultiplier(category), DefaultGooPvpHitStopDurationMultiplier(category), "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(flag3, flag4, 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(flag3, flag4, section6, "CounterDamageMultiplier", 1.3f, 1.3f, "Counter-damage multiplier for eligible attacks. 1.30 means 30% additional damage.", exposeSecondary2), BindRoleSettings(flag3, flag4, 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, flag4, section2, "DamageRateMultiplier", primary1DamageRateMultiplier ?? primaryDamageRateMultiplier, primary2DamageRateMultiplier ?? primaryDamageRateMultiplier, primary3DamageRateMultiplier ?? primaryDamageRateMultiplier, secondaryDamageRateMultiplier, "Outgoing attack damage multiplier. 1 leaves vanilla damage unchanged.", exposeSecondary2), BindRoleSettings(expose: true, flag4, section2, "ChopDamageMultiplier", DefaultGooChopDamageMultiplier(category, AttackRole.Primary1), DefaultGooChopDamageMultiplier(category, AttackRole.Primary2), DefaultGooChopDamageMultiplier(category, AttackRole.Primary3), DefaultGooChopDamageMultiplier(category, 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, flag4, section2, "PickaxeDamageMultiplier", DefaultPickaxeDamageMultiplier(category), DefaultPickaxeDamageMultiplier(category), "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(flag3, flag4, 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(flag3, flag4, 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(flag3, flag4, 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, flag4, 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(flag3, flag4, section8, "AttackMovementModifier", primaryAttackMovementModifier, secondaryAttackMovementModifier, "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.", exposeSecondary2), BindRoleSettings(flag3, flag4, 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(flag3, flag4, 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(flag3, flag4, 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, flag4, section9, "AttackAnimationSpeedMultiplier", primaryAttackAnimationSpeedMultiplier, secondaryAttackAnimationSpeedMultiplier, "Actual swing animation-speed multiplier using Animator.speed during the attack. 1 leaves vanilla animation timing unchanged.", exposeSecondary2), BindRoleSettings(expose4, flag4, 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(flag3, flag4, 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(flag3, flag4, 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, flag4, 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, flag4, 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, flag4, 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, flag4, section12, "PvpStaggerDurationSeconds", (primaryPvpStaggerDurationSeconds >= 0f) ? primaryPvpStaggerDurationSeconds : DefaultPvpStaggerDuration(category, AttackRole.Primary1), (secondaryPvpStaggerDurationSeconds >= 0f) ? secondaryPvpStaggerDurationSeconds : DefaultPvpStaggerDuration(category, 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, flag4, 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, flag4, 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(expose: true, section12, "PvpBlockArmorMultiplier", DefaultGooPvpBlockArmorMultiplier(category), "PvP-only Block Armor multiplier when this weapon category is used to block a player attack. 1 leaves vanilla PvP Block Armor unchanged."), BindCategorySetting(flag3, 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(flag3, 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(flag3, section14, "BlockStaminaConsumptionRate", 1f, "Multiplier for final block stamina usage when blocking with this weapon category. 1 is vanilla."), BindCategorySetting(!flag, section14, "BlockingStaminaRegenMultiplier", 1f, "Stamina regeneration multiplier while actively blocking with this weapon category. Stacks with GlobalBlockingStaminaRegenMultiplier. 1 is vanilla."), BindCategorySetting(flag3, 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(flag3, flag4, 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(flag3, flag4, 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(flag3, flag4, 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; bool flag6 = IsBackstabConfigCategory(category); bool exposePrimaryChain2 = flag6 && (flag4 || category == WeaponCategory.Bow || category == WeaponCategory.Crossbow); AttackStartNoiseMultipliers[category] = BindRoleSettings(flag6, 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(flag6, 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(flag6, exposePrimaryChain2, section15, "BackstabDamageMultiplier", DefaultGooBackstabDamageMultiplier(category), DefaultGooBackstabDamageMultiplier(category), DefaultGooBackstabDamageMultiplier(category), DefaultGooBackstabDamageMultiplier(category), "Local backstab/sneak damage multiplier for this attack slot. Final tooltip/runtime backstab equals GlobalBackstabDamageMultiplier x this local value. Goo preset default is 3 for knives/dual knives/unarmed and 1 for other weapon types; Reset to Vanilla sets this to the vanilla final backstab value while global becomes 1.", exposeSecondary2); AttackRangeMultipliers[category] = BindRoleSettings(flag5, flag4, 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); AttackRayWidthMultipliers[category] = BindRoleSettings(flag5, flag4, text, "HitRayThickness", 1f, 1f, "Multiplier for melee hit-ray thickness. Scales Attack.m_attackRayWidth and m_attackRayWidthCharExtra on the cloned Attack instance. 1 is vanilla/no-op; 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(flag5, flag4, 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(flag5, flag4, 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(flag5, flag4, 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(flag5, flag4, text, "HitRayAngle", 1f, 1f, "Multiplier for melee hit-ray fan angle, Attack.m_attackAngle. 1 is vanilla/no-op.", exposeSecondary2); BindVanillaWeaponPrefabHitRayRangeConfigs(category, text, flag5); bool expose6 = flag3 && category != WeaponCategory.Sledge; AttackMultiTargetDamagePenaltyModes[category] = BindRoleSettings(expose6, flag4, section16, "MultiTargetDamagePenalty", AttackMultiTargetDamagePenaltyMode.Enabled, AttackMultiTargetDamagePenaltyMode.Enabled, "Controls Attack.m_lowerDamagePerHit for this cloned melee attack. Enabled allows Valheim's multi-target damage falloff; Disabled prevents damage loss across multiple targets. Sledges are excluded because their AOE-style identity keeps vanilla handling.", exposeSecondary2); AttackBuildingDamageMultipliers[category] = BindRoleSettings(expose: true, flag4, 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, "Flanking"); WeaponFlankingDamageMultipliers[category] = BindConfig(section18, "FlankingDamageMultiplier", DefaultGooFlankingDamageMultiplier(category, pvp: false), "PvE damage multiplier when this weapon/tool hits the rear flanking arc of an enemy. Stacks with GlobalFlankingDamageMultiplier. 1 is vanilla/no-op."); WeaponPvpFlankingDamageMultipliers[category] = BindConfig(section18, "PvpFlankingDamageMultiplier", DefaultGooFlankingDamageMultiplier(category, pvp: true), "PvP damage multiplier when this weapon/tool hits the rear flanking arc of a player/PvP target. Stacks with GlobalPvpFlankingDamageMultiplier. 1 is vanilla/no-op."); 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.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; default: if (IsBackstabConfigCategory(category)) { return 3f; } return 1f; } } private static float DefaultGooBackstabDamageMultiplier(WeaponCategory category) { if (category != WeaponCategory.Knife && category != WeaponCategory.DualKnives && category != WeaponCategory.Unarmed) { return 1f; } return 3f; } private static string WeaponFeatureSection(string weaponSection, string 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) { string text = BuildCompactCommandPath(section, prettyKey); if (!string.IsNullOrWhiteSpace(text)) { GcoCommandPrimaryPaths[entry] = text; RegisterFlatGcoCommandEntry(text, entry); } } } private static void RegisterFlatGcoCommandEntry(string primaryPath, ConfigEntryBase entry) { if (entry == null || string.IsNullOrWhiteSpace(primaryPath) || !TryBuildFlatGcoBranchAndFeature(primaryPath, out string branchDisplay, out string featureDisplay)) { return; } string text = NormalizeCommandKey(branchDisplay); string text2 = NormalizeCommandKey(featureDisplay); if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(text2)) { if (!GcoFlatCommandEntries.TryGetValue(text, out Dictionary value)) { value = new Dictionary(StringComparer.OrdinalIgnoreCase); GcoFlatCommandEntries[text] = value; } value[text2] = entry; if (!GcoFlatCommandOptions.TryGetValue(text, out SortedSet value2)) { value2 = new SortedSet(StringComparer.OrdinalIgnoreCase); GcoFlatCommandOptions[text] = value2; } value2.Add(featureDisplay); GcoFlatBranchDisplayNames[text] = branchDisplay; } } 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.AddRange(CompactKeyTokens(prettyKey)); return string.Join(" ", from t in list where !string.IsNullOrWhiteSpace(t) select t.Trim()); } 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 { "player", "resources" }; 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 "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", "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", ["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", ["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", ["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", ["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 CompactFeatureToken(string value) { return SectionFeatureToken(value); } private static string CompactToken(string value) { return NormalizeCommandKey(value); } 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); if (!text.StartsWith("spawn", StringComparison.Ordinal) && !text.StartsWith("remove", StringComparison.Ordinal) && !text.StartsWith("toggle", StringComparison.Ordinal) && !text.Contains("heal") && !text.Contains("dummy") && !(text == "settogoosruleset")) { return text == "settovanillaruleset"; } return true; } 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__533_0; if (obj == null) { ConsoleOptionsFetcher val4 = () => new List { "goo", "vanilla" }; <>c.<>9__533_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.Take(240).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)).Take(240).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:"); int num = 0; { foreach (string gcoFlatFeatureOption in GetGcoFlatFeatureOptions(branchKey)) { args.Context.AddString(" " + gcoFlatFeatureOption); num++; if (num >= 120) { args.Context.AddString(" ... list truncated; type a prefix after " + FlatGcoUsagePrefix(branchKey, "list") + " to narrow it."); break; } } return; } } string value2 = NormalizeCommandKey(args[1]); int num2 = 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 + "]"); num2++; if (num2 >= 120) { args.Context.AddString(" ... list truncated; type a longer prefix to narrow it."); break; } } } if (num2 == 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") + " "); 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") + " "); return; } 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."); return; } 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; try { if (flag) { SetToGooRulesetValues(); } else { ApplyVanillaPresetValues(); } ((BaseUnityPlugin)Instance).Config.Save(); } finally { _suppressGcoChatBroadcast = false; } 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 void HandleGcoConfigSettingChanged(SettingChangedEventArgs eventArgs) { if (!ProcessGeneralPresetToggles() && !_suppressGcoChatBroadcast && eventArgs != null) { ConfigEntryBase changedSetting = eventArgs.ChangedSetting; if (changedSetting != null && !ShouldSkipGcoConfigChangeBroadcast(changedSetting)) { BroadcastGcoConfigChange(changedSetting); } } } 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 { } } } 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("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; 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; } 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(); } string value = ConfigVersion.Value; bool startupLegacyConfigNeedsGooRewrite = _startupLegacyConfigNeedsGooRewrite; bool startupConfigVersionNeedsBump = _startupConfigVersionNeedsBump; if (!startupLegacyConfigNeedsGooRewrite && !startupConfigVersionNeedsBump) { return; } int num = RemoveRenamedBlockConfigEntries(); num += RemoveCompactConfigEntriesReplacedBySpacedNames(); num += RemoveGeneralFeatureScopedPresetEntries(); num += RemoveRemovedHitStopEnableConfigEntries(); num += RemoveDeprecatedGlobalPresetEntries(); num += RemoveUnsupportedPrimary4ConfigEntries(); num += RemoveOrphanedConfigEntry("Debug", "DebugModeSetSpawnPoint"); num += RemoveOrphanedConfigEntry("Debug", "DebugModeSetSpawnPointKeybind"); if (startupLegacyConfigNeedsGooRewrite) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Config version '" + value + "' is 1.0.4 or older; archiving the old config file outside the active BepInEx config and generating a clean Goo ruleset config.")); ArchiveLegacyConfigFile(value); SetToGooRulesetValues(); SetToGoosRuleset.Value = false; SetToVanillaRuleset.Value = false; ClearOrphanedConfigEntries(); } else if (startupConfigVersionNeedsBump) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Config version '" + value + "' is older than 1.3.3; preserving existing compatible values and setting Goo defaults only for newly added config entries.")); if (IsVersionOlderThan(value, "1.1.0")) { SetGoo110NewDefaultValuesOnly(); } if (IsVersionOlderThan(value, "1.2.0")) { SetGoo120NewDefaultValuesOnly(); } if (IsVersionOlderThan(value, "1.2.1")) { SetGoo121NewDefaultValuesOnly(); } if (IsVersionOlderThan(value, "1.2.2")) { SetGoo122NewDefaultValuesOnly(); } } if (startupConfigVersionNeedsBump || startupLegacyConfigNeedsGooRewrite || num > 0) { if (startupConfigVersionNeedsBump || startupLegacyConfigNeedsGooRewrite) { ConfigVersion.Value = "1.3.3"; } ((BaseUnityPlugin)this).Config.Save(); } } 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 MigrateOrphanedFloatConfigEntry(string section, string key, ConfigEntry? target) { if (target == null) { return 0; } if (!TryGetOrphanedConfigValue(section, key, out string raw)) { return 0; } if (float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { target.Value = result; RemoveOrphanedConfigEntry(section, key); return 1; } return 0; } private int MigrateOrphanedIntConfigEntry(string section, string key, ConfigEntry? target) { if (target == null) { return 0; } if (!TryGetOrphanedConfigValue(section, key, out string raw)) { return 0; } if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { target.Value = result; RemoveOrphanedConfigEntry(section, key); return 1; } return 0; } private bool TryGetOrphanedConfigValue(string section, string key, out string raw) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown raw = string.Empty; 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[key2] != null) { raw = dictionary[key2].ToString() ?? string.Empty; return raw.Length > 0; } } } catch { } return false; } 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 int ClearOrphanedConfigEntries() { try { if (typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(((BaseUnityPlugin)this).Config) is IDictionary { Count: var count } dictionary) { dictionary.Clear(); if (count > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Removed {count} orphaned/stale config entries from the config file."); } return count; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not clear orphaned config entries: " + ex.Message)); } return 0; } private void ArchiveLegacyConfigFile(string previousVersion) { try { string configFilePath = GetConfigFilePath(); if (!string.IsNullOrWhiteSpace(configFilePath) && File.Exists(configFilePath)) { string? path = Path.GetDirectoryName(configFilePath) ?? Paths.ConfigPath; string text = MakeSafeFileName(string.IsNullOrWhiteSpace(previousVersion) ? "unknown" : previousVersion); string text2 = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture); string text3 = Path.Combine(path, Path.GetFileName(configFilePath) + ".legacy-pre-1.0.5-" + text + "-" + text2 + ".bak"); File.Copy(configFilePath, text3, overwrite: false); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Archived pre-1.0.5 config to '" + text3 + "'. The active config will be regenerated cleanly for the current Goo ruleset.")); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not archive legacy config file before migration: " + ex.Message)); } } 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 string MakeSafeFileName(string text) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text = text.Replace(oldChar, '_'); } return text; } 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 static bool IsVersionOlderThanOrEqualTo(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) { 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; GlobalPlayerKnockbackMultiplier.Value = 1f; GlobalPvpKnockbackMultiplier.Value = 1f; GlobalPlayerStaggerPowerMultiplier.Value = 1f; ClearEnemyStaggerMeterWhenStaggered.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; 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 (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 1f; } GlobalAttackMovementMultiplier.Value = 1f; GlobalLungeDistanceMultiplier.Value = 1f; GlobalRunningAttackLungeDistanceMultiplier.Value = 1f; GlobalJumpAttackLungeDistanceMultiplier.Value = 1f; GlobalAttackRotationMultiplier.Value = 1f; GlobalPvpHitStopDurationMultiplier.Value = 1f; GlobalAttackAnimationSpeedMultiplier.Value = 1f; GlobalRecoveryAnimationSpeedMultiplier.Value = 1f; 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 (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; AirborneAttackMinimumAirTimeSeconds.Value = 0f; JumpStaminaConsumptionRate.Value = 1f; RunStaminaConsumptionRate.Value = 1f; DrainRunStaminaDuringAttacksAndAirborne.Value = true; RollStaminaConsumptionRate.Value = 1f; CrouchStaminaConsumptionRate.Value = 1f; RollAnimationSpeed.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; BowSpreadMultiplier.Value = 1f; BowProjectileSpeedMultiplier.Value = 1f; BowBaseSpreadMultiplier.Value = 1f; CrossbowReloadTimeMultiplier.Value = 1f; CrossbowProjectileSpeedMultiplier.Value = 1f; SpearProjectileSpeedMultiplier.Value = 1f; if (SpearLoyaltyMode != null) { SpearLoyaltyMode.Value = false; } if (HarpoonRopeStrengthMultiplier != null) { HarpoonRopeStrengthMultiplier.Value = 1f; } CrossbowBaseSpreadMultiplier.Value = 1f; StaffLightningReloadSpeedMultiplier.Value = 1f; StaffLightningProjectileSpeedMultiplier.Value = 1f; StaffIceShardsProjectileSpeedMultiplier.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 value2 in StaffBlockConfigs.Values) { value2.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) { SetCategoryToVanilla(categoryConfig.Value); SetStealthAndHitboxPreset(categoryConfig.Key, 1f, 1f, 1f, 1f, 1f); if (BackstabDamageMultipliers.TryGetValue(categoryConfig.Key, out RoleSettings value)) { value.SetAll(DefaultVanillaBackstabDamageMultiplier(categoryConfig.Key), DefaultVanillaBackstabDamageMultiplier(categoryConfig.Key)); } categoryConfig.Value.ChopDamageMultiplier.SetAll(DefaultChopDamageMultiplier(categoryConfig.Key), DefaultChopDamageMultiplier(categoryConfig.Key)); categoryConfig.Value.PickaxeDamageMultiplier.SetAll(VanillaPickaxeDamageMultiplier(categoryConfig.Key), VanillaPickaxeDamageMultiplier(categoryConfig.Key)); } foreach (WeaponBlockExtraConfig value3 in WeaponBlockExtras.Values) { value3.ParryBonusMultiplier.Value = 1f; value3.ParryWindowTimeMultiplier.Value = 1f; value3.PvpParryBonusMultiplier.Value = 1f; value3.PvpBlockForceMultiplier.Value = 1f; value3.PvpBlockBreakThresholdMultiplier.Value = 1f; value3.SoulsLikeBlockBreak.Value = false; value3.SoulsLikeBlockStaminaMode.Value = false; value3.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value3.KnockbackTakenWhileBlockingMultiplier.Value = 1f; value3.BlockingMovementSpeedMultiplier.Value = 1f; value3.EnableRunningWhileBlocking.Value = false; value3.RunningWhileBlockingMovementSpeedMultiplier.Value = 1f; } ResetShieldTypeBlockConfig(ShieldBucklerBlockConfig); ResetShieldTypeBlockConfig(ShieldMediumBlockConfig); ResetShieldTypeBlockConfig(ShieldTowerBlockConfig); ResetBackstabDamageMultipliersToVanilla(); SetStaffConfigsToVanilla(); } private static void SetCategoryToVanilla(CategoryConfig cfg) { cfg.BaseDamageMultiplier.Value = 1f; cfg.StaggerMode.SetAll(StaggerApplicationMode.MultiplyVanilla, StaggerApplicationMode.MultiplyVanilla); cfg.StaggerPowerValue.SetAll(1f, 1f); cfg.DamageToStaggeredEnemyMultiplier.SetAll(1f, 1f); cfg.HyperArmorMode.SetAll(HyperArmorMode.Off, HyperArmorMode.Off); cfg.DamageTakenMultiplier.SetAll(1f, 1f); cfg.StaggerTakenMultiplier.SetAll(1f, 1f); cfg.KnockbackTakenMultiplier.SetAll(1f, 1f); cfg.PvpDamageTakenMultiplierDuringHyperArmor.SetAll(1f, 1f); cfg.PvpStaggerTakenMultiplierDuringHyperArmor.SetAll(1f, 1f); cfg.PvpKnockbackTakenMultiplierDuringHyperArmor.SetAll(1f, 1f); cfg.HitStopDurationMultiplier.SetAll(1f, 1f); cfg.PvpHitStopDurationMultiplier.SetAll(1f, 1f); cfg.CounterDamageEnabled.SetAll(primary: false, secondary: false); cfg.CounterDamageMultiplier.SetAll(1.3f, 1.3f); cfg.PvpCounterDamageMultiplier.SetAll(1f, 1f); cfg.DamageRateMultiplier.SetAll(1f, 1f); cfg.ChopDamageMultiplier.SetAll(0f, 0f); cfg.PickaxeDamageMultiplier.SetAll(0f, 0f); cfg.AirborneDamageMultiplier.SetAll(1f, 1f); cfg.AirborneStaggerMultiplier.SetAll(1f, 1f); cfg.AirborneRotationFactor.SetAll(1f, 1f); cfg.StaminaRateMultiplier.SetAll(1f, 1f); cfg.EitrRateMultiplier.SetAll(1f, 1f); cfg.AttackMovementModifier.SetAll(1f, 1f); cfg.AttackMovementApplicationMode.SetAll(AttackMovementApplicationMode.FullAnimation, AttackMovementApplicationMode.FullAnimation); cfg.LungeDistanceMultiplier.SetAll(1f, 1f); cfg.LungeApplicationMode.SetAll(LungeApplicationMode.BeforeHitbox, LungeApplicationMode.BeforeHitbox); cfg.AttackAnimationSpeedMultiplier.SetAll(1f, 1f); cfg.RecoveryAnimationSpeedMultiplier.SetAll(1f, 1f); cfg.AttackRotationFactor.SetAll(-1f, -1f); cfg.LockRotationAfterAttackTrigger.SetAll(primary: false, secondary: false); cfg.AdrenalineMultiplier.SetAll(1f, 1f); cfg.PvpDamageMultiplier.SetAll(1f, 1f); cfg.PvpStaggerMultiplier.SetAll(1f, 1f); cfg.PvpStaggerDurationSeconds.SetAll(0f, 0f); cfg.PvpKnockbackForceMultiplier.SetAll(1f, 1f); cfg.KnockbackForceMultiplier.SetAll(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; cfg.BlockCancelMode.SetAll(BlockCancelMode.Disabled, BlockCancelMode.Disabled); cfg.BlockCancelAnimationSpeedMultiplier.SetAll(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); SetRoleHitRayThicknessToVanillaDefaults(category, value4); } 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)) { SetRoleHitboxTypesToVanillaDefaults(category, value8); } if (AttackMultiTargetDamagePenaltyModes.TryGetValue(category, out RoleSettings value9)) { SetRoleMultiTargetDamagePenaltyToVanillaDefaults(category, value9); } } private static void SetRoleHitRayThicknessToVanillaDefaults(WeaponCategory category, RoleSettings settings) { settings.Primary1.Value = DefaultVanillaHitRayThicknessMultiplier(category, AttackRole.Primary1); settings.Primary2.Value = DefaultVanillaHitRayThicknessMultiplier(category, AttackRole.Primary2); settings.Primary3.Value = DefaultVanillaHitRayThicknessMultiplier(category, AttackRole.Primary3); settings.Primary4.Value = DefaultVanillaHitRayThicknessMultiplier(category, AttackRole.Primary4); settings.Secondary.Value = DefaultVanillaHitRayThicknessMultiplier(category, AttackRole.Secondary); } 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) { settings.Primary1.Value = DefaultVanillaMultiTargetDamagePenaltyMode(category, AttackRole.Primary1); settings.Primary2.Value = DefaultVanillaMultiTargetDamagePenaltyMode(category, AttackRole.Primary2); settings.Primary3.Value = DefaultVanillaMultiTargetDamagePenaltyMode(category, AttackRole.Primary3); settings.Primary4.Value = DefaultVanillaMultiTargetDamagePenaltyMode(category, AttackRole.Primary4); settings.Secondary.Value = DefaultVanillaMultiTargetDamagePenaltyMode(category, AttackRole.Secondary); } 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 SetRoleHitboxTypesToVanillaDefaults(WeaponCategory category, RoleSettings settings) { settings.Primary1.Value = DefaultVanillaHitboxType(category, AttackRole.Primary1); settings.Primary2.Value = DefaultVanillaHitboxType(category, AttackRole.Primary2); settings.Primary3.Value = DefaultVanillaHitboxType(category, AttackRole.Primary3); settings.Primary4.Value = DefaultVanillaHitboxType(category, AttackRole.Primary4); settings.Secondary.Value = DefaultVanillaHitboxType(category, AttackRole.Secondary); } 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.DualAxes, out RoleSettings value3)) { value3.SetAll(2.2f, 2.2f); value3.Secondary.Value = 0.55f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value4)) { value4.SetPrimary(1.67f); if (OneHandedAxeSecondaryHitRayAddedThicknessMeters != null) { OneHandedAxeSecondaryHitRayAddedThicknessMeters.Value = 0.4f; } if (OneHandedAxeJumpAttackHitRayAddedThicknessMeters != null) { OneHandedAxeJumpAttackHitRayAddedThicknessMeters.Value = 0.4f; } } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Spear, out RoleSettings value5)) { value5.SetPrimary(1.2f); } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value6)) { value6.Primary1.Value = 1.25f; value6.Primary2.Value = 1.25f; value6.Primary3.Value = 1.4f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value7)) { value7.Primary3.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value8)) { value8.Primary3.Value = 1f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value9)) { value9.Primary1.Value = 1.4f; value9.Primary2.Value = 1.4f; value9.Primary3.Value = 1.67f; value9.Secondary.Value = 1.67f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Knife, out RoleSettings value10)) { value10.Primary1.Value = 2f; value10.Primary2.Value = 2f; value10.Primary3.Value = 2f; value10.Secondary.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.DualKnives, out RoleSettings value11)) { value11.Primary1.Value = 2f; value11.Primary2.Value = 2f; value11.Primary3.Value = 2f; value11.Secondary.Value = 2f; } if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.Unarmed, out RoleSettings value12)) { value12.Primary1.Value = 2.5f; value12.Primary2.Value = 2.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value13)) { value13.SetPrimary(1.3f); value13.Secondary.Value = 1.8f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value14)) { value14.Primary1.Value = 1.3f; value14.Primary2.Value = 1.3f; value14.Primary3.Value = 1.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value15)) { value15.Primary3.Value = 1.6f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value16)) { value16.Primary1.Value = 1.5f; value16.Primary2.Value = 1.5f; value16.Primary3.Value = 1.5f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value17)) { value17.Primary3.Value = 1.6f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value18)) { value18.Primary1.Value = 1.25f; value18.Primary2.Value = 1.25f; value18.Primary3.Value = 2f; value18.Secondary.Value = 2f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.Knife, out RoleSettings value19)) { value19.Primary1.Value = 2f; value19.Primary2.Value = 2f; value19.Primary3.Value = 2f; value19.Secondary.Value = 2f; } if (AttackHitboxHeightMultipliers.TryGetValue(WeaponCategory.DualKnives, out RoleSettings value20)) { value20.Primary1.Value = 2f; value20.Primary2.Value = 2f; value20.Primary3.Value = 2f; value20.Secondary.Value = 2f; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value21)) { value21.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value22)) { value22.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value23)) { value23.Primary3.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.Spear, out RoleSettings value24)) { value24.Primary1.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.Club, out RoleSettings value25)) { value25.Primary3.Value = AttackHitboxType.Vertical; value25.Secondary.Value = AttackHitboxType.Vertical; } if (AttackHitboxTypes.TryGetValue(WeaponCategory.OneHandedAxe, out RoleSettings value26)) { value26.Secondary.Value = DefaultVanillaHitboxType(WeaponCategory.OneHandedAxe, AttackRole.Secondary); } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.TwoHandedAxe, out RoleSettings value27)) { value27.Primary1.Value = 1.5f; value27.Primary2.Value = 1.5f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.Club, out RoleSettings value28)) { value28.Primary3.Value = 0.7f; value28.Secondary.Value = 2f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.TwoHandedSword, out RoleSettings value29)) { value29.Primary1.Value = 1.4f; value29.Primary2.Value = 1.4f; value29.Primary3.Value = 0.5f; } if (AttackAngleMultipliers.TryGetValue(WeaponCategory.OneHandedSword, out RoleSettings value30)) { value30.Primary1.Value = 1.2f; value30.Primary2.Value = 1.2f; value30.Primary3.Value = 0.7f; } SetGooMultiTargetPenaltyDefaults(); SetGooDualAxesJumpAttackDefaults(); if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value31)) { value31.HitboxType.Value = AttackHitboxType.Vertical; value31.HitboxHeightMultiplier.Value = 1f; value31.RayWidthMultiplier.Value = 1.4f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.OneHandedSword, out JumpAttackConfig value32)) { value32.HitboxType.Value = AttackHitboxType.Vertical; value32.HitboxHeightMultiplier.Value = 1f; value32.RayWidthMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value33)) { value33.HitboxType.Value = AttackHitboxType.Vertical; value33.HitboxHeightMultiplier.Value = 1f; value33.RayWidthMultiplier.Value = 1.67f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out JumpAttackConfig value34)) { value34.HitboxType.Value = AttackHitboxType.Vertical; value34.HitboxHeightMultiplier.Value = 1f; value34.RayWidthMultiplier.Value = 2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value35)) { value35.RayWidthMultiplier.Value = 2.2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Knife, out JumpAttackConfig value36)) { value36.HitboxHeightMultiplier.Value = 2f; value36.RayWidthMultiplier.Value = 2f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualKnives, out JumpAttackConfig value37)) { value37.HitboxHeightMultiplier.Value = 2f; value37.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 SetGoo110NewDefaultValuesOnly() { SetGooMeleeHitboxDefaults(); SetGooHitboxShapeDefaults(); if (StaffGreenRootsVineLevel != null) { StaffGreenRootsVineLevel.Value = 0; } if (StaffSkeletonSizeMultiplier != null) { StaffSkeletonSizeMultiplier.Value = 1f; } if (StaffSkeletonAttackSpeedMultiplier != null) { StaffSkeletonAttackSpeedMultiplier.Value = 1f; } if (StaffSkeletonLevel != null) { StaffSkeletonLevel.Value = 0; } } private static void SetGoo120NewDefaultValuesOnly() { SetGooDualAxesJumpAttackDefaults(); if (AttackRayWidthMultipliers.TryGetValue(WeaponCategory.DualAxes, out RoleSettings value)) { value.SetAll(2.2f, 2.2f); value.Secondary.Value = 0.55f; } SetGoo120HitStopDefaults(); if (GlobalHitStopEnabled != null) { GlobalHitStopEnabled.Value = true; } if (GlobalBlockingMovementSpeedMultiplier != null) { GlobalBlockingMovementSpeedMultiplier.Value = 1f; } SetGooRunningWhileBlockingDefaults(); if (AlwaysDoMaxDamage != null) { AlwaysDoMaxDamage.Value = false; } if (AlwaysDoMaxDamageInPvp != null) { AlwaysDoMaxDamageInPvp.Value = true; } if (MustEatUniqueFood != null) { MustEatUniqueFood.Value = true; } if (FoodHealthMultiplier != null) { FoodHealthMultiplier.Value = 1f; } if (FoodStaminaMultiplier != null) { FoodStaminaMultiplier.Value = 1f; } if (FoodEitrMultiplier != null) { FoodEitrMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value2)) { value2.PvpDamageMultiplier.Value = 0.6f; value2.RecoveryAnimationSpeedMultiplier.Value = 1f; value2.PvpHitStopDurationMultiplier.Value = 5f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value3)) { value3.PvpDamageMultiplier.Value = 1f; } if (CategoryConfigs.TryGetValue(WeaponCategory.Pickaxe, out CategoryConfig value4)) { value4.PickaxeDamageMultiplier.SetAll(1.5f, 1.5f); value4.PvpDamageMultiplier.SetAll(1f, 1f); } if (StaffConfigs.TryGetValue("StaffLightning", out StaffConfig value5) && value5.PvpDamageMultiplier != null) { value5.PvpDamageMultiplier.Value = 1.25f; } if (StaffConfigs.TryGetValue("StaffIceShards", out StaffConfig value6) && value6.PvpDamageMultiplier != null) { value6.PvpDamageMultiplier.Value = 1.25f; } if (StaffConfigs.TryGetValue("StaffClusterbomb", out StaffConfig value7) && value7.PvpDamageMultiplier != null) { value7.PvpDamageMultiplier.Value = 1f; } } 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 void SetGoo121NewDefaultValuesOnly() { 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 (VerboseDebugLogging != null) { VerboseDebugLogging.Value = false; } if (EnableDebugFlyToggle != null) { EnableDebugFlyToggle.Value = true; } if (EnableNoPlacementCostToggle != null) { EnableNoPlacementCostToggle.Value = true; } if (EnableGhostModeToggle != null) { EnableGhostModeToggle.Value = true; } if (BroadcastConfigChanges != null) { BroadcastConfigChanges.Value = false; } if (GlobalParryWindowTimeMultiplier != null) { GlobalParryWindowTimeMultiplier.Value = 1f; } ResetParryWindowMultipliersToVanilla(); if (PlayerDamageToBuildingsMultiplier != null) { PlayerDamageToBuildingsMultiplier.Value = 1f; } if (EnemyDamageToBuildingsMultiplier != null) { EnemyDamageToBuildingsMultiplier.Value = 0.2f; } ResetBuildingDamageMultipliersToGoo(); 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; } if (DebugModeHeal != null) { DebugModeHeal.Value = false; } if (DebugModeAddRested != null) { DebugModeAddRested.Value = false; } ResetGearDurabilityConfigsToVanilla(); } private static void SetGoo122NewDefaultValuesOnly() { if (OneHandedAxeSecondaryHitRayAddedThicknessMeters != null) { OneHandedAxeSecondaryHitRayAddedThicknessMeters.Value = 0.4f; } if (OneHandedAxeJumpAttackHitRayAddedThicknessMeters != null) { OneHandedAxeJumpAttackHitRayAddedThicknessMeters.Value = 0.4f; } if (NegativeStaminaActionGate != null) { NegativeStaminaActionGate.Value = NegativeStaminaActionGateMode.RequireCostWithinFloor; } if (EnemySpeedMultiplier != null) { EnemySpeedMultiplier.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; } ResetStaggeredEnemyDamageMultipliersToGoo(); ResetBackstabDamageMultipliersToGoo(); ResetFlankingDamageMultipliersToGoo(); ResetEffectiveBlockAngleMultipliersToGoo(); } 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; } } 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); 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; 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 (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 2f; } GlobalAttackMovementMultiplier.Value = 1f; GlobalLungeDistanceMultiplier.Value = 1f; GlobalRunningAttackLungeDistanceMultiplier.Value = 1f; GlobalJumpAttackLungeDistanceMultiplier.Value = 1f; GlobalAttackRotationMultiplier.Value = 1f; GlobalPvpHitStopDurationMultiplier.Value = 1f; GlobalAttackAnimationSpeedMultiplier.Value = 1f; GlobalRecoveryAnimationSpeedMultiplier.Value = 1f; ClearEnemyStaggerMeterWhenStaggered.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; } ResetParryWindowMultipliersToVanilla(); if (PlayerDamageToBuildingsMultiplier != null) { PlayerDamageToBuildingsMultiplier.Value = 1f; } if (EnemyDamageToBuildingsMultiplier != null) { EnemyDamageToBuildingsMultiplier.Value = 0.2f; } 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 value16 in WeaponBlockExtras.Values) { value16.SoulsLikeBlockBreak.Value = false; value16.SoulsLikeBlockStaminaMode.Value = false; value16.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value16.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } SetGooRunningWhileBlockingDefaults(); foreach (StaffBlockConfig value17 in StaffBlockConfigs.Values) { value17.SoulsLikeBlockBreak.Value = false; value17.SoulsLikeBlockStaminaMode.Value = false; value17.SoulsLikeBlockStaminaBaseRate.Value = 0.7f; value17.KnockbackTakenWhileBlockingMultiplier.Value = 1f; } HitStopDurationMultiplier.Value = 1f; HitStopDurationOverrideSeconds.Value = -1f; AttackMovementGroundingModeConfig.Value = AttackMovementGroundingMode.GroundOnly; BetterFreeAim.Value = true; 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 = 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 = 0.75f; 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.LungeDistanceMultiplier.Value = 3f; } 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 = 1f; } SetJumpAttackPreset(enabled: true); if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out JumpAttackConfig value11)) { value11.PvpDamageMultiplier.Value = 0.6f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value12)) { value12.PvpDamageMultiplier.Value = 0.6f; value12.RecoveryAnimationSpeedMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value13)) { value13.PvpDamageMultiplier.Value = 1f; } 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.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.DamageRateMultiplier.Primary1.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 = 0.65f; GetConfig(WeaponCategory.Unarmed).HitStopDurationMultiplier.SetAll(0.8f, 0.8f); GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary1.Value = 1f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary2.Value = 0.7f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Secondary.Value = 1.3f; GetConfig(WeaponCategory.Knife).PvpDamageMultiplier.Primary3.Value = 0.65f; GetConfig(WeaponCategory.DualKnives).PvpDamageMultiplier.Primary3.Value = 0.65f; 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; 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 value14)) { value14.KnockbackForceMultiplier.Value = 0.7f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value15)) { value15.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 (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 = 1.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(); } private static void ResetShieldTypeBlockConfig(ShieldTypeBlockConfig cfg) { if (cfg != null) { 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.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.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 = (IsStaffSkeleton(value6) ? 1f : 0.7f); } if (value6.PvpStaggerMultiplier != null) { value6.PvpStaggerMultiplier.Value = 0f; } if (value6.PvpKnockbackForceMultiplier != null) { value6.PvpKnockbackForceMultiplier.Value = 0f; } 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 = 1.25f; } 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) && value4.PvpDamageMultiplier != null) { value4.PvpDamageMultiplier.Value = 1.25f; } if (StaffConfigs.TryGetValue("StaffLightning", out StaffConfig value5) && value5.SpreadMultiplier != null) { value5.SpreadMultiplier.Value = 0.3f; } if (StaffLightningReloadSpeedMultiplier != null) { StaffLightningReloadSpeedMultiplier.Value = 1f; } if (StaffLightningProjectileSpeedMultiplier != null) { StaffLightningProjectileSpeedMultiplier.Value = 1f; } if (StaffIceShardsProjectileSpeedMultiplier != null) { StaffIceShardsProjectileSpeedMultiplier.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_00d5: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Expected O, but got Unknown //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Expected O, but got Unknown //IL_0375: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Expected O, but got Unknown //IL_0397: 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, (HarmonyMethod)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."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Character), "RPC_Stagger", new Type[2] { typeof(long), typeof(Vector3) }, (Type[])null); if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)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), (HarmonyMethod)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 is 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 num = 0; foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(typeof(Character)) where m.Name == "Stagger" select m) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length != 0 && parameters[0].ParameterType == typeof(Vector3)) { _harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterStaggerPrefix", (Type[])null), new HarmonyMethod(typeof(GooCombatOverhaulPlugin), "CharacterStaggerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Patched {num} 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"); PatchAttackMethod(type, "DoMeleeAttack", "AttackDoMeleeAttackPostfix", postfix: true); PatchAttackMethodOptional(type, "DoAreaAttack", "AttackDoAreaAttackPrefix"); PatchAttackMethodOptional(type, "DoAreaAttack", "AttackDoAreaAttackPostfix", postfix: true); 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_07db: Unknown result type (might be due to invalid IL or missing references) //IL_07e9: 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, "DrainEquipedItemDurability", "HumanoidDrainEquippedItemDurabilityPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "BlockAttack", "DurabilitySnapshotPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "BlockAttack", "HumanoidBlockAttackPrefix", postfix: false, optional: true); PatchDeclaredMethods(type3, "BlockAttack", "HumanoidBlockAttackPostfix", postfix: true, optional: true); 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, "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."); } Type type5 = AccessTools.TypeByName("Tameable"); if (type5 != null) { PatchDeclaredMethods(type5, "Command", "TameableCommandPrefix", postfix: false, optional: true); PatchDeclaredMethods(type5, "UnsummonMaxInstances", "TameableUnsummonMaxInstancesPrefix", postfix: false, optional: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched Tameable.Command and UnsummonMaxInstances for StaffSkeleton item-quality native summon-cap control."); } } 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_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Invalid comparison between Unknown and I4 //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_010d: 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_0135: 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 category = ClassifyWeapon(val); float num5 = Mathf.Clamp(GetConfig(category).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, category, 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) ? ("\n$item_backstab: " + FormatMultiplierForTooltip(multiplier) + "x") : string.Empty); if (tooltip.Contains(text)) { tooltip = tooltip.Replace(text, text2); } else if (!string.IsNullOrEmpty(text2)) { 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); } float configuredParryBonusMultiplier = GetConfiguredParryBonusMultiplier(item, pvp: false); if (item.m_shared.m_timedBlockBonus > 0f) { string text = "\n$item_parrybonus: " + FormatMultiplierForTooltip(item.m_shared.m_timedBlockBonus) + "x"; float num3 = item.m_shared.m_timedBlockBonus * configuredParryBonusMultiplier; string newValue = ((num3 > 0f) ? ("\n$item_parrybonus: " + FormatMultiplierForTooltip(num3) + "x") : string.Empty); if (tooltip.Contains(text)) { tooltip = tooltip.Replace(text, newValue); } } float configuredParryAdrenalineMultiplier = GetConfiguredParryAdrenalineMultiplier(item); if (!Mathf.Approximately(configuredParryAdrenalineMultiplier, 1f) && item.m_shared.m_perfectBlockAdrenaline > 0f) { string text2 = "\n$item_parryadrenaline: " + item.m_shared.m_perfectBlockAdrenaline.ToString(CultureInfo.InvariantCulture) + ""; string newValue2 = "\n$item_parryadrenaline: " + FormatMultiplierForTooltip(item.m_shared.m_perfectBlockAdrenaline * configuredParryAdrenalineMultiplier) + ""; if (tooltip.Contains(text2)) { tooltip = tooltip.Replace(text2, newValue2); } } } 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); } } } public static void CharacterOnDestroyPostfix(Character __instance) { if (!((Object)(object)__instance == (Object)null)) { StaffSpawnCharacterOrigins.Remove(__instance); StaffGreenRootsRuntimeTweakedCharacters.Remove(__instance); StaffSkeletonRuntimeTweakedCharacters.Remove(__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); if (!Mathf.Approximately(num, 1f) && !CharacterIsInAttackFast((Character)(object)val)) { turnSpeed *= num; } return; } if (IsEnemyRotationLockedAfterHitRay(__instance)) { turnSpeed = 0f; return; } float num2 = Mathf.Clamp(EnemyBaseRotationFactor.Value, 0f, 10f); float num3 = Mathf.Clamp(EnemyAttackRotationFactor.Value, 0f, 10f); if (!Mathf.Approximately(num2, 1f) || !Mathf.Approximately(num3, 1f)) { if (Mathf.Approximately(num2, num3)) { turnSpeed *= num2; return; } float num4 = (CharacterIsInAttackFast(__instance) ? num3 : num2); turnSpeed *= num4; } } 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 weaponCategory = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); AttackRole characterAnimEventAttackRole = GetCharacterAnimEventAttackRole(val, weaponCategory); if (!UseCharacterAnimEventSpeedMultiplier(weaponCategory, characterAnimEventAttackRole)) { return; } float num = GetResolvedAttackAnimationSpeedMultiplier(val, weaponCategory, characterAnimEventAttackRole); object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); if (fieldValueRaw != null && RecoveryAnimationSpeedAppliedAttacks.Contains(fieldValueRaw)) { num *= GetResolvedRecoveryAnimationSpeedMultiplier(val, weaponCategory, 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)} {weaponCategory} {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 || !AttackStates.TryGetValue((Character)(object)val, out var value)) { return; } if (Time.time > value.EndTime || IsCharacterDead((Character)(object)val) || !CharacterReportsInAttack((Character)(object)val)) { AttackStates.Remove((Character)(object)val); } else { if (((value.AttackInstance == null || !AttackInstanceWeapons.TryGetValue(value.AttackInstance, out ItemData value2) || !TryGetStaffBlockConfig(value2, out StaffBlockConfig _)) && !IsMeleeWeaponCategory(value.Category)) || (AttackMovementGroundingModeConfig.Value == AttackMovementGroundingMode.GroundOnly && !((Character)val).IsOnGround()) || !ShouldApplyAttackMovementOverride(value.AttackInstance, value.Category, value.Role)) { return; } if (GetEffectiveAttackMovementApplicationMode(value.AttackInstance, value.Category, value.Role) == AttackMovementApplicationMode.BeforeHitbox && value.AttackInstance != null && LungeHitboxTriggeredAttacks.Contains(value.AttackInstance)) { __result = 0f; return; } float effectiveAttackMovementModifier = GetEffectiveAttackMovementModifier(value.AttackInstance, value.Category, value.Role); if (UsesDirectAttackMovementFactor(value.AttackInstance, value.Category, value.Role)) { __result = Mathf.Clamp(effectiveAttackMovementModifier, 0f, 5f); } else { __result = Mathf.Clamp(__result * effectiveAttackMovementModifier, 0f, 5f); } } } 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) { 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) { ApplyEffectiveBlockAngleMultiplier(val, activeBlocker, hit); ApplyParryWindowTimeMultiplier(val, activeBlocker); 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 num = (UseSoulsLikeBlockStaminaMode(activeBlocker) ? ComputeSoulsLikeBlockStaminaCostForIncomingHit(val, activeBlocker, hit, attacker) : ComputeBlockStaminaCostForIncomingHit(val, activeBlocker, hit)); if (UseSoulsLikeBlockStaminaMode(activeBlocker)) { SoulsLikeBlockStaminaStates[val] = new SoulsLikeBlockStaminaState(activeBlocker, num, Time.time + 0.25f); } if (UseSoulsLikeBlockBreak(activeBlocker)) { bool staminaWillBeCleared = num >= playerStamina && num > 0f; SoulsLikeBlockBreakStates[val] = new SoulsLikeBlockBreakState(activeBlocker, playerStamina, num, staminaWillBeCleared, Time.time + 0.25f); } } } public static void HumanoidBlockAttackPostfix(object __instance, HitData hit, Character attacker, bool __result) { 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; } } } 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; } float configuredParryWindowTimeMultiplier = GetConfiguredParryWindowTimeMultiplier(blocker); if (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 (configuredParryWindowTimeMultiplier <= 0f) { SetPrivateFloat(player, "m_blockTimer", 9999f); } else { SetPrivateFloat(player, "m_blockTimer", privateFloat / configuredParryWindowTimeMultiplier); } } } 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.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldBucklerBlockConfig != null) { ShieldBucklerBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldMediumBlockConfig != null) { ShieldMediumBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } if (ShieldTowerBlockConfig != null) { ShieldTowerBlockConfig.ParryWindowTimeMultiplier.Value = 1f; } foreach (StaffBlockConfig value2 in StaffBlockConfigs.Values) { value2.ParryWindowTimeMultiplier.Value = 1f; } } 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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; } float num = InferPvpStaggerDurationFromFinalHit(hit); if (num <= 0f) { return false; } PendingPvpStaggerDurations[victim] = num; return true; } 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)) { TrySetCharacterAnimationSpeed(character, speed); float end = Time.time + duration; while ((Object)(object)character != (Object)null && Time.time < end && character.IsStaggering()) { 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 - (num2 + num3 / 1000f)); 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 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 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 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_008f: Expected O, but got Unknown //IL_008f: 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, (HarmonyMethod)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) { 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; } AttackRole role = AttackRole.Primary1; bool flag = default(bool); foreach (object obj in __args) { int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { role = AttackRole.Secondary; break; } } PendingAttackRoles[val] = new PendingAttackRoleState(role, Time.time + 0.75f); return true; } } return true; } public static void HumanoidStartAttackPostfix(object __instance, object[] __args, bool __result) { if (!ModActive) { return; } Character val = (Character)((__instance is Character) ? __instance : null); if (val == null) { return; } if (!__result) { PendingAttackRoles.Remove(val); return; } object fieldValueRaw = GetFieldValueRaw(val, "m_currentAttack"); if (fieldValueRaw == null) { PendingAttackRoles.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)); if ((RunningAttackInstances.Contains(fieldValueRaw) || JumpAttackInstances.Contains(fieldValueRaw)) && AttackInstanceRoles.TryGetValue(fieldValueRaw, out var value)) { attackRole = value; } AttackRole value2; bool flag2 = AttackInstanceRoles.TryGetValue(fieldValueRaw, out value2); AttackInstanceOwners[fieldValueRaw] = val; if (currentWeapon != null) { AttackInstanceWeapons[fieldValueRaw] = currentWeapon; } AttackInstanceRoles[fieldValueRaw] = attackRole; SetAttackState(val, weaponCategory, attackRole, fieldValueRaw); ReapplyAuthoritativeAttackTweaksAfterRoleCorrection(fieldValueRaw, val, currentWeapon, weaponCategory, attackRole, flag2, value2); PendingAttackRoles.Remove(val); if (Debug(DebugAttackLifecycle)) { string text = ((flag2 && 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}."); } } 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 void TryApplyBetterFreeAimSnap(Player player) { //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) if ((Object)(object)player == (Object)null) { 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 ((Object)(object)player == (Object)null || !BetterFreeAim.Value || !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) { if (ModActive && BetterFreeAim.Value) { Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { 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_005a; } } if (HasMeaningfulMovementInput(val)) { __result = false; return false; } goto IL_005a; } } return true; IL_005a: 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)}."); } } } private static Animator? GetAnimator(Character character) { if ((Object)(object)character == (Object)null) { return null; } object? fieldValueRaw = GetFieldValueRaw(character, "m_animator"); Animator val = (Animator)((fieldValueRaw is Animator) ? fieldValueRaw : null); if (val != null) { return val; } object fieldValueRaw2 = GetFieldValueRaw(character, "m_zanim"); if (fieldValueRaw2 != null) { object? fieldValueRaw3 = GetFieldValueRaw(fieldValueRaw2, "m_animator"); Animator val2 = (Animator)((fieldValueRaw3 is Animator) ? fieldValueRaw3 : null); if (val2 != null) { return val2; } } try { return ((Component)character).GetComponentInChildren(); } catch { return null; } } private static void InvalidateAnimationHashCache(Character character) { SetFieldValueRaw(character, "m_cachedAnimHashFrame", -1); SetFieldValueRaw(character, "m_cachedCurrentAnimHash", 0); SetFieldValueRaw(character, "m_cachedNextAnimHash", 0); SetFieldValueRaw(character, "m_cachedNextOrCurrentAnimHash", 0); } private static string GetAnimatorSnapshot(Animator animator) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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) if ((Object)(object)animator == (Object)null) { return "animator=null"; } try { AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0); bool flag = animator.IsInTransition(0); string text = string.Empty; if (flag) { AnimatorStateInfo nextAnimatorStateInfo = animator.GetNextAnimatorStateInfo(0); text = string.Format(", nextFull={0}, nextShort={1}, nextTag={2}, nextTime={3}", ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).fullPathHash, ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).shortNameHash, ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).tagHash, ((AnimatorStateInfo)(ref nextAnimatorStateInfo)).normalizedTime.ToString("0.000", CultureInfo.InvariantCulture)); } return string.Format("currentFull={0}, currentShort={1}, currentTag={2}, currentTime={3}, inTransition={4}{5}", ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).tagHash, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime.ToString("0.000", CultureInfo.InvariantCulture), flag, text); } catch (Exception ex) { return "animator snapshot failed: " + ex.Message; } } private static void TrySetZanimTrigger(Character character, string trigger) { object fieldValueRaw = GetFieldValueRaw(character, "m_zanim"); if (fieldValueRaw == null) { return; } try { fieldValueRaw.GetType().GetMethod("SetTrigger", new Type[1] { typeof(string) })?.Invoke(fieldValueRaw, new object[1] { trigger }); } catch { } } private static void TrySetZanimBool(Character character, string name, bool value) { object fieldValueRaw = GetFieldValueRaw(character, "m_zanim"); if (fieldValueRaw == null) { return; } try { fieldValueRaw.GetType().GetMethod("SetBool", new Type[2] { typeof(string), typeof(bool) })?.Invoke(fieldValueRaw, new object[2] { name, value }); } catch { } } public static void HumanoidUpdateBlockPostfix(object __instance) { if (!ModActive) { return; } Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { if (!((Character)val).IsBlocking()) { BetterFreeAimActiveBlocks.Remove(val); } else if (BetterFreeAim.Value && BetterFreeAimActiveBlocks.Add(val)) { TryApplyBetterFreeAimBlockSnap(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) { WeaponCategory category = ClassifyWeapon(weapon); AttackRole role = DetermineAttackRole(character, __instance, weapon); 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 (!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; } } public static void AttackStartPrefix(object __instance, object[] __args) { if (!ModActive || __instance == null || !TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return; } ResetAttackInstanceForFreshStart(__instance); Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return; } WeaponCategory weaponCategory = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); if (!IsMeleeWeaponCategory(weaponCategory)) { return; } bool flag = CharacterReportsInAttack((Character)(object)val) || IsInAttackState((Character)(object)val); AttackInstanceStartedWhileAlreadyInAttack[__instance] = flag; AttackInstanceStartedAirborne[__instance] = PlayerStartedAttackAirborne(val); if ((!PendingAttackRoles.TryGetValue((Character)(object)val, out var value) || value.Role != AttackRole.Secondary || !(Time.time <= value.EndTime)) && !TryStartJumpAttack(__instance, __args, val, weaponCategory, weapon) && !flag && weaponCategory != WeaponCategory.Sledge && weaponCategory != WeaponCategory.DualAxes && TryGetRunningAttackConfig(weaponCategory, out RunningAttackConfig config) && config.Enabled.Value && PlayerIsActuallyRunning(val) && PlayerMeetsRunningAttackVelocity(val, config.MinimumVelocity.Value) && TryApplyRunningAttackSelection(__instance, __args, weaponCategory, weapon, config, out string forcedText)) { 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}; {forcedText}."); } } } 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 category = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); bool value; bool flag = AttackInstanceStartedWhileAlreadyInAttack.TryGetValue(__instance, out value) && value; if (!BetterFreeAimPreAppliedAttacks.Remove(__instance) && BetterFreeAim.Value && !flag) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && IsMeleeWeaponCategory(category)) { TryApplyBetterFreeAimSnap(val); } } ApplyForcedChainPostStart(__instance); AttackRole role = DetermineAttackRole(character, __instance, weapon); SetAttackState(character, category, role, __instance); PendingAttackRoles.Remove(character); ClearBalancedHyperArmorCompleted(__instance); if (character is Player) { if (weapon != null) { ApplyRangedAttackTweaksIfConfigured(__instance, character, weapon, category, role); } ApplyMeleeStealthAndHitboxIfConfigured(__instance, character, category, role); ApplyPlayerSizeAttackRangeIfConfigured(__instance, character, category); ApplyAttackMovementSpeedIfConfigured(__instance, weapon, category, role); ApplyAttackAnimationSpeedIfConfigured(__instance, character, weapon, category, role); ApplyAttackRotationOverrideIfConfigured(__instance, weapon, category, role); } else { ApplyEnemyAttackHitRayIfConfigured(__instance, character); ApplyEnemyAttackAnimationSpeedIfConfigured(__instance, character); } if (character is Player && weapon != null) { CategoryConfig config = GetConfig(category); if (config.GetHyperArmorMode(role) == HyperArmorMode.FullAttackAnimation) { float num = Mathf.Max(0.01f, config.GetFullAttackDuration(role)); SetHyperArmor(character, category, role, 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 = flag2 || flag3 || flag4 || flag5; if (!flag && !flag6) { return; } RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(attackInstance); RecoveryAnimationSpeedAppliedAttacks.Remove(attackInstance); AttackInstanceRoles[attackInstance] = authoritativeRole; AttackInstanceOwners[attackInstance] = character; if (weapon != null) { AttackInstanceWeapons[attackInstance] = weapon; } if (character is Player) { EndHyperArmorForAttack(character, attackInstance, null); if (weapon != null) { ApplyRangedAttackTweaksIfConfigured(attackInstance, character, weapon, category, authoritativeRole); } ApplyMeleeStealthAndHitboxIfConfigured(attackInstance, character, category, authoritativeRole); ApplyPlayerSizeAttackRangeIfConfigured(attackInstance, character, category); ApplyAttackMovementSpeedIfConfigured(attackInstance, weapon, category, authoritativeRole); ApplyAttackAnimationSpeedIfConfigured(attackInstance, character, weapon, category, authoritativeRole); ApplyAttackRotationOverrideIfConfigured(attackInstance, weapon, category, authoritativeRole); if (GetConfig(category).GetHyperArmorMode(authoritativeRole) == HyperArmorMode.FullAttackAnimation) { float effectiveFullAttackDurationSeconds = GetEffectiveFullAttackDurationSeconds(attackInstance, character, weapon, category, authoritativeRole); SetHyperArmor(character, category, 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); SetAttackStateIfChanged(character, category, role, __instance); Player val = (Player)(object)((character is Player) ? character : null); if (val != null && weapon != null) { TryHandleBlockCancelDuringAttack(val, __instance, weapon, category, role); } MarkCounterVulnerableDuringAttackAnimation(character, category, role, __instance); if ((character is Player && weapon != null) || !(character is Player)) { ReapplyAttackAnimationSpeedTweaksIfConfigured(__instance); } if (character is Player && weapon != null && GetEffectiveHyperArmorMode(__instance, category, role) == HyperArmorMode.Balanced && !IsBalancedHyperArmorCompleted(__instance)) { float effectiveFullAttackDurationSeconds = GetEffectiveFullAttackDurationSeconds(__instance, character, weapon, category, role); SetHyperArmorIfChanged(character, category, role, HyperArmorMode.Balanced, Time.time + effectiveFullAttackDurationSeconds, __instance); } } public static bool AttackDoMeleeAttackPrefix(object __instance, object[] __args) { if (!ModActive) { return true; } if (!TryGetAttackContext(__instance, __args, out Character character, out ItemData weapon)) { return true; } WeaponCategory category = ((weapon != null) ? ClassifyWeapon(weapon) : WeaponCategory.Unarmed); AttackRole role = DetermineAttackRole(character, __instance, weapon); if (character is Player) { if (ShouldSuppressHitboxForBlockCancel(__instance)) { MarkLungeHitboxTriggered(__instance); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, role); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, category, role); return false; } MarkLungeHitboxTriggered(__instance); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, role); if (weapon != null) { ApplyRecoveryAnimationSpeedIfConfigured(__instance, character, weapon, category, role); } } SetAttackState(character, category, role, __instance); PushAttackHitContext(__instance, __args); if (character is Player && weapon != null) { BeginMeleeHitStopContext(character, category, role, __instance); } return true; } 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 category = ClassifyWeapon(weapon); AttackRole role = DetermineAttackRole(character, __instance, weapon); ApplySpearLoyaltyConsumeSuppressionIfNeeded(__instance, character, weapon, category, role); if (ShouldSuppressHitboxForBlockCancel(__instance)) { MarkLungeHitboxTriggered(__instance); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, category, role); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, role); return false; } MarkLungeHitboxTriggered(__instance); ApplyAttackRotationLockAfterTriggerIfConfigured(__instance, weapon, category, role); RestoreAttackMovementSpeedAtHitboxIfConfigured(__instance, category, role); ApplyRecoveryAnimationSpeedIfConfigured(__instance, character, weapon, category, role); 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 { } } public static void AttackDoMeleeAttackPostfix(object __instance, object[] __args) { if (TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { LockEnemyRotationAfterHitRayIfConfigured(__instance, character, "DoMeleeAttack"); } PopAttackHitContext(); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "DoMeleeAttack"); EndMeleeHitStopContext(__instance, __args); } public static void AttackDoAreaAttackPrefix(object __instance, object[] __args) { PushAttackHitContext(__instance, __args); } public static void AttackDoAreaAttackPostfix(object __instance, object[] __args) { if (TryGetAttackContext(__instance, __args, out Character character, out ItemData _)) { LockEnemyRotationAfterHitRayIfConfigured(__instance, character, "DoAreaAttack"); } PopAttackHitContext(); EndBalancedHyperArmorAfterAttackEvent(__instance, __args, "DoAreaAttack"); } 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); 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 bool TryGetCurrentHitAttackContext(Character? attacker, WeaponCategory category, out AttackRuntimeState context) { context = default(AttackRuntimeState); if (CurrentAttackHitContexts == null || CurrentAttackHitContexts.Count == 0) { return false; } context = CurrentAttackHitContexts.Peek(); if (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); } } 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); AttackInstanceStartedAirborne.Remove(attackInstance); AttackInstanceStartedWhileAlreadyInAttack.Remove(attackInstance); ClearBalancedHyperArmorCompleted(attackInstance); RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(attackInstance); } private static void ResetAttackInstanceForFreshStart(object? attackInstance) { if (attackInstance != null) { RestoreAttackMovementSpeedTweaks(attackInstance); RestoreAttackRotationTweaks(attackInstance); RestoreAttackRangedFieldTweaks(attackInstance); RestoreAttackAnimationSpeedTweaks(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); 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 (!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() { bool flag = IsGooCombatDebugModeActive(); Player localPlayer = Player.m_localPlayer; 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 && 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 float global, out float local) || !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 (SuccessfulFlankDing != null && SuccessfulFlankDing.Value) { PlayConditionalDamageDing(victim, attacker, hit, "flank"); } if (Debug(DebugAttackLifecycle)) { DebugLogThrottled($"flanking-damage:{((Object)victim).GetInstanceID()}:{RuntimeHelpers.GetHashCode(hit)}", 0.25f, $"Flanking damage for {SafeName((Character)(object)player)} -> {SafeName(victim)} using {category}: pvp={pvp}, global={global.ToString(CultureInfo.InvariantCulture)}, local={local.ToString(CultureInfo.InvariantCulture)}, final={num.ToString(CultureInfo.InvariantCulture)}."); } } } private static bool TryGetFlankingDamageContext(Character victim, Character? attacker, HitData hit, out Player player, out bool pvp, out WeaponCategory category, out float global, out float local) { player = null; pvp = false; category = WeaponCategory.Unarmed; global = 1f; local = 1f; 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 { } } if (!IsFlankingHit(victim, attacker, hit, pvp)) { return false; } player = val; ItemData currentWeapon = GetCurrentWeapon((Character)(object)player); category = ResolveActiveAttackCategory((Character)(object)player, currentWeapon); if (category == WeaponCategory.Sledge) { return false; } global = ((!pvp) ? ((GlobalFlankingDamageMultiplier != null) ? Mathf.Clamp(GlobalFlankingDamageMultiplier.Value, 0f, 20f) : 1f) : ((GlobalPvpFlankingDamageMultiplier != null) ? Mathf.Clamp(GlobalPvpFlankingDamageMultiplier.Value, 0f, 20f) : 1f)); local = GetConfiguredFlankingDamageMultiplier(category, pvp); 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; float global; float local; bool flanking = TryGetFlankingDamageContext(victim, attacker, hit, out player, out pvp, out category, out global, out local); 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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 float GetConfiguredFlankingDamageMultiplier(WeaponCategory category, bool pvp) { if ((pvp ? WeaponPvpFlankingDamageMultipliers : WeaponFlankingDamageMultipliers).TryGetValue(category, out ConfigEntry value)) { return Mathf.Clamp(value.Value, 0f, 20f); } return 1f; } 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); ApplyKnockbackForceModifiers(__instance, attacker, hit); ApplyStaffShieldActiveMitigationIfNeeded(__instance, attacker, hit); ApplyOutgoingDamageAndPvpCalibration(__instance, attacker, hit); ApplyFlankingDamageIfEligible(__instance, attacker, hit); ApplyPvpTestDummyArmorIfNeeded(__instance, attacker, hit); ApplyCounterDamageIfEligible(__instance, attacker, hit); ApplyOutgoingStaggerCalibration(attacker, hit); ApplyPvpStaggerCalibration(__instance, attacker, hit); ApplyAdrenalineMultiplierIfEligible(attacker, hit); ApplyGlobalPlayerDamageTakenIfNeeded(__instance, hit); __state = ApplyHyperArmorIfActive(__instance, attacker, hit); 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); } LogPvpTestDummyDamageIfNeeded(__instance, hit); } 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(victim.GetHealth(), 0f, num3); float num5 = Mathf.Max(0f, num - num4); float privateFloat = GetPrivateFloat(victim, "m_staggerDamage", 0f); float num6 = num3 * Mathf.Max(0.001f, GetPrivateFloat(victim, "m_staggerDamageFactor", 0.4f)); MarkPvpTestDummyAttacked(victim); 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) + " -> " + privateFloat.ToString("0.###", CultureInfo.InvariantCulture) + " / " + num6.ToString("0.###", CultureInfo.InvariantCulture) + "; next full heal in " + Mathf.Max(0.25f, PvpTestDummyFullHealIntervalSeconds.Value).ToString("0.###", CultureInfo.InvariantCulture) + "s.")); } } private static void MarkPvpTestDummyAttacked(Character victim) { if (!((Object)(object)victim == (Object)null) && TryGetZdo(victim, out ZDO zdo)) { zdo.Set("GCO_TestDummyWasAttacked", true); zdo.Set("GCO_TestDummyNextHealTime", Time.time + Mathf.Max(0.25f, PvpTestDummyFullHealIntervalSeconds.Value)); } } 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 CharacterRpcDamagePrefix(Character __instance, long sender, HitData hit) { if (ModActive && !((Object)(object)__instance == (Object)null) && hit != null) { CapturePendingPvpStaggerDurationFromIncomingHit(__instance, hit); } } 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: 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_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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; 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); Vector3 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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; } 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 (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 && (RunningAttackInstances.Contains(attackInstance) || JumpAttackInstances.Contains(attackInstance)) && AttackInstanceRoles.TryGetValue(attackInstance, out var value)) { return value; } 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 value2)) { if (!(Time.time > value2.EndTime)) { if (value2.Role != AttackRole.Secondary) { return ResolvePrimaryChainRole(attackInstance); } return AttackRole.Secondary; } PendingAttackRoles.Remove(character); } if (attackInstance != null && AttackInstanceRoles.TryGetValue(attackInstance, out var value3)) { return value3; } if ((Object)(object)character != (Object)null && AttackStates.TryGetValue(character, out var value4) && value4.AttackInstance != null && attackInstance != null && value4.AttackInstance == attackInstance) { return value4.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 = ClassifyWeapon(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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01af: 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_00c1: 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_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: 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_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: 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) if ((Object)(object)victim == (Object)null || hit == null || string.IsNullOrEmpty(dingKey) || (DamageBonusStackStates.TryGetValue(hit, out DamageBonusStackState value) && (value.Backstab || value.StaggeredEnemy))) { return; } CombatDingState value2 = CombatDingStates.GetValue(hit, (HitData _) => new CombatDingState()); if (value2.CounterDamageDingPlayed || value2.FlankingDingPlayed) { return; } if (string.Equals(dingKey, "counter", StringComparison.OrdinalIgnoreCase)) { value2.CounterDamageDingPlayed = true; } else { value2.FlankingDingPlayed = true; } 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 }); } 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 = ClassifyWeapon(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; } bool value2; bool flag = AttackInstanceStartedWhileAlreadyInAttack.TryGetValue(attackInstance, out value2) && value2; if (BetterFreeAim.Value && !flag) { 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; } Type type = attackInstance.GetType(); Type type2 = secondaryAttack.GetType(); 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(attackInstance, cachedField.GetValue(secondaryAttack)); } catch { } } } return true; } 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(category, out cfg); } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.AttackInstance != null && fieldValueRaw != null && value.AttackInstance == fieldValueRaw && RunningAttackInstances.Contains(value.AttackInstance)) { return TryGetRunningAttackConfig(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(category, out cfg); } object fieldValueRaw = GetFieldValueRaw(player, "m_currentAttack"); if (AttackStates.TryGetValue((Character)(object)player, out var value) && value.AttackInstance != null && fieldValueRaw != null && value.AttackInstance == fieldValueRaw && JumpAttackInstances.Contains(value.AttackInstance)) { return TryGetJumpAttackConfig(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 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 IsStaffIceShards(StaffConfig cfg) { if (cfg != null) { return IsStaffIceShardsPrefabName(cfg.PrefabName); } return false; } 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); return true; } if (!IsBackstabConfigCategory(category)) { return false; } multiplier = GetConfiguredBackstabDamageMultiplier(null, item, category, AttackRole.Primary1); return true; } private static float GetConfiguredStaffBackstabDamageMultiplier(StaffConfig cfg) { if (cfg == null) { return 1f; } float num = ((GlobalBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalBackstabDamageMultiplier.Value, 0f, 20f) : 1f); float num2 = Mathf.Clamp(cfg.BackstabDamageMultiplier.Value, 0f, 20f); return Mathf.Clamp(num * num2, 1f, 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); } 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; } return Mathf.Clamp(((GlobalBackstabDamageMultiplier != null) ? Mathf.Clamp(GlobalBackstabDamageMultiplier.Value, 0f, 20f) : 1f) * Mathf.Clamp(num, 0f, 20f), 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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); } } if (!EnablePvpDamageModifiers.Value) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || !IsPvpLikeTarget(victim) || !((Object)(object)attacker != (Object)(object)victim)) { return; } 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 num2 = ((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))); num2 *= Mathf.Clamp(GlobalPvpDamageMultiplier.Value, 0f, 10f); num2 *= Mathf.Clamp(GlobalPvpDamageTakenMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num2, 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(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); } 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 (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(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 || 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(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 || 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(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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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; } } } public static void TameableCommandPrefix(object __instance) { if (!ModActive) { return; } Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { Character val2 = val.GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = val.GetComponentInParent(); } if (!((Object)(object)val2 == (Object)null) && IsLikelyStaffSkeletonCharacter(val2)) { ApplyStaffSkeletonResolvedMaxInstances(val2, force: false); } } } public static void TameableUnsummonMaxInstancesPrefix(object __instance, ref int __0) { if (!ModActive) { return; } Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { Character val2 = val.GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = val.GetComponentInParent(); } if (!((Object)(object)val2 == (Object)null) && IsLikelyStaffSkeletonCharacter(val2) && TryGetStaffSkeletonResolvedMaxInstances(val2, out var resolved) && resolved > 0 && resolved != __0) { __0 = resolved; ApplyStaffSkeletonResolvedMaxInstances(val2, force: true); } } } 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))); } private static int GetStaffSkeletonMaxConfiguredSpawnCap() { int num = 0; for (int i = 1; i <= 4; i++) { num = Mathf.Max(num, GetStaffSkeletonConfiguredSpawnCap(i)); } return num; } private static bool IsLikelyStaffSkeletonCharacter(Character character) { if ((Object)(object)character == (Object)null) { return false; } string text = SafeLower(GetPrefabLikeName(((Component)character).gameObject) + " " + ((Object)character).name + " " + character.m_name); if (!text.Contains("skeleton") && !text.Contains("deadraiser") && !text.Contains("dead_raiser")) { return text.Contains("dead raiser"); } return true; } private static bool TryGetStaffSkeletonResolvedMaxInstances(Character character, out int resolved) { resolved = 0; if ((Object)(object)character == (Object)null) { return false; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } ZDO zDO = component.GetZDO(); if (zDO == null) { return false; } resolved = zDO.GetInt("GCO_StaffSkeletonResolvedMaxInstances", 0); if (resolved <= 0) { resolved = zDO.GetInt(ZDOVars.s_maxInstances, 0); } resolved = Mathf.Clamp(resolved, 0, 200); return resolved > 0; } private static void ApplyStaffSkeletonResolvedMaxInstances(Character character, bool force) { if ((Object)(object)character == (Object)null || (!force && !IsLikelyStaffSkeletonCharacter(character))) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (zDO != null) { int num = zDO.GetInt("GCO_StaffSkeletonResolvedMaxInstances", 0); if (num <= 0) { num = zDO.GetInt(ZDOVars.s_maxInstances, 0); } num = Mathf.Clamp(num, 0, 200); if (num > 0) { zDO.Set("GCO_StaffSkeletonResolvedMaxInstances", num); zDO.Set(ZDOVars.s_maxInstances, num, false); } } } private static void ApplyStaffSkeletonResolvedMaxInstances(Character character, int resolvedCap, int itemQuality, bool force) { if ((Object)(object)character == (Object)null || (!force && !IsLikelyStaffSkeletonCharacter(character))) { return; } ZNetView component = ((Component)character).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (zDO != null) { resolvedCap = Mathf.Clamp(resolvedCap, 0, 200); if (resolvedCap > 0) { zDO.Set("GCO_StaffSkeletonResolvedMaxInstances", resolvedCap); zDO.Set("GCO_StaffSkeletonResolvedItemQuality", Mathf.Clamp(itemQuality, 1, 4)); zDO.Set(ZDOVars.s_maxInstances, resolvedCap, false); } } } 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)) { int num = Mathf.Clamp((StaffGreenRootsMaxVinesSpawned != null) ? StaffGreenRootsMaxVinesSpawned.Value : 10, 0, 200); SetFieldValueRaw(spawnAbility, "m_maxSpawned", num); } else { if (!IsStaffSkeleton(staffCfg)) { return; } int staffSkeletonConfiguredSpawnCapForItem = GetStaffSkeletonConfiguredSpawnCapForItem(item); int staffSkeletonMaxConfiguredSpawnCap = GetStaffSkeletonMaxConfiguredSpawnCap(); SetFieldValueRaw(spawnAbility, "m_maxSpawned", staffSkeletonMaxConfiguredSpawnCap); 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; } 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) && (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 (__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) { if (!ModActive || __instance == null) { return; } if (StaffSpawnProjectileOrigins.TryGetValue(__instance, out StaffSpawnOrigin value)) { CurrentStaffSpawnDamageOrigin = value; } if (ProjectileAttackContexts.TryGetValue(__instance, out var value2)) { if (CurrentAttackHitContexts == null) { CurrentAttackHitContexts = new Stack(); } CurrentAttackHitContexts.Push(value2); } } public static void ProjectileOnHitPostfix(object __instance, object[] __args) { if (__instance != null && ProjectileAttackContexts.ContainsKey(__instance)) { PopAttackHitContext(); } CurrentStaffSpawnDamageOrigin = null; } public static void AoeSetupPostfix(object __instance, object[] __args) { if (ModActive && __instance != null) { if (CurrentSpawnAbilityAoeSetupOrigin != null) { StaffSpawnAoeOrigins[__instance] = CurrentSpawnAbilityAoeSetupOrigin; } if (CurrentAttackHitContexts != null && CurrentAttackHitContexts.Count > 0) { AoeAttackContexts[__instance] = CurrentAttackHitContexts.Peek(); } } } public static void AoeOnHitPrefix(object __instance, object[] __args) { if (!ModActive || __instance == null) { return; } if (StaffSpawnAoeOrigins.TryGetValue(__instance, out StaffSpawnOrigin value)) { CurrentStaffSpawnDamageOrigin = value; } if (AoeAttackContexts.TryGetValue(__instance, out var value2)) { if (CurrentAttackHitContexts == null) { CurrentAttackHitContexts = new Stack(); } CurrentAttackHitContexts.Push(value2); } } public static void AoeOnHitPostfix(object __instance, object[] __args) { if (__instance != null && AoeAttackContexts.ContainsKey(__instance)) { PopAttackHitContext(); } 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); } if (origin.IsSkeleton) { ApplyStaffSkeletonResolvedMaxInstances(character, origin.SkeletonResolvedSpawnCap, origin.SkeletonItemQuality, force: true); ApplyStaffSkeletonRuntimeTweaks(character); } if (IsPvpStaffSpawnAllowed(origin)) { EnableStaffSpawnPvpTargeting(character); } } } 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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) { 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 GetEffectiveAttackMovementModifier(object? attackInstance, WeaponCategory category, AttackRole 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; } num *= ((GlobalAttackMovementMultiplier != null) ? Mathf.Clamp(GlobalAttackMovementMultiplier.Value, 0f, 10f) : 1f); return Mathf.Clamp(num, 0f, 5f); } private static AttackMovementApplicationMode GetEffectiveAttackMovementApplicationMode(object? attackInstance, WeaponCategory category, AttackRole 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 = UsesDirectAttackMovementFactor(attackInstance, category, role); if ((!flag && Mathf.Approximately(effectiveAttackMovementModifier, 1f)) || AttackMovementGroundingModeConfig.Value == AttackMovementGroundingMode.GroundOnly || AttackMovementSpeedContexts.ContainsKey(attackInstance)) { return; } List list = new List(); SetKnownAttackMovementSpeedFields(attackInstance, effectiveAttackMovementModifier, flag, list); if (list.Count > 0) { AttackMovementSpeedContexts[attackInstance] = list; 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, float movementFactor, bool directMovementFactor, List originals) { if (target != null) { SetAttackMovementSpeedField(target, "m_speedFactor", movementFactor, directMovementFactor, originals); } } private static bool UsesDirectAttackMovementFactor(object? attackInstance, WeaponCategory category, AttackRole role) { if (category != WeaponCategory.TwoHandedAxe) { return false; } if (attackInstance != null && RunningAttackInstances.Contains(attackInstance)) { return true; } if (attackInstance != null && JumpAttackInstances.Contains(attackInstance)) { return false; } if (role != AttackRole.Primary1 && role != AttackRole.Primary2 && role != AttackRole.Primary3 && role != AttackRole.Primary4) { return role == AttackRole.Secondary; } return true; } private static void SetAttackMovementSpeedField(object target, string fieldName, float movementFactor, bool directMovementFactor, 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 num) { float num2 = (directMovementFactor ? Mathf.Clamp(movementFactor, 0f, 5f) : Mathf.Clamp(num * movementFactor, 0f, 5f)); originals.Add(new SuppressedFieldValue(target, cachedField, value)); cachedField.SetValue(target, num2); } } catch { } } private static void SetFloatField(object target, string fieldName, float value, List originals) { FieldInfo cachedField = GetCachedField(target.GetType(), fieldName); if (cachedField == null || cachedField.IsInitOnly || cachedField.IsLiteral || cachedField.FieldType != typeof(float)) { return; } try { object value2 = cachedField.GetValue(target); if (value2 is float) { cachedField.SetValue(target, value); originals.Add(new SuppressedFieldValue(target, cachedField, value2)); } } 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; } 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 || !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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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 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 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) { 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 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 (TryGetStaffConfig(weapon, out StaffConfig cfg) && cfg.SpreadMultiplier != null) { float num = Mathf.Clamp(cfg.SpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num) ? 1u : 0u)) != 0 && Debug(DebugRanged)) { DebugLogThrottled("staff-spread:" + ((Object)character).GetInstanceID(), 1f, "Staff spread fields multiplied by " + num.ToString(CultureInfo.InvariantCulture) + " for " + cfg.PrefabName + "."); } if (IsStaffLightning(cfg)) { float num2 = Mathf.Clamp(StaffLightningProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num2, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num2); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num2); } float num3 = Mathf.Clamp(StaffLightningProjectileCountMultiplier.Value, 0.01f, 10f); if (!Mathf.Approximately(num3, 1f)) { TryMultiplyIntField(attackInstance, "m_projectiles", num3); } } else if (IsStaffIceShards(cfg)) { float num4 = Mathf.Clamp(StaffIceShardsProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num4, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num4); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num4); } } } switch (category) { case WeaponCategory.Bow: { float num8 = Mathf.Clamp(BowSpreadMultiplier.Value * BowBaseSpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num8, 1f) && (0u | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num8) ? 1u : 0u) | (TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num8) ? 1u : 0u)) != 0 && Debug(DebugRanged)) { DebugLogThrottled("bow-spread:" + ((Object)character).GetInstanceID(), 1f, "Bow accuracy/spread fields multiplied by " + num8.ToString(CultureInfo.InvariantCulture) + " on cloned Attack for " + SafeName(character) + "."); } float num9 = Mathf.Clamp(BowProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num9, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num9); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num9); } break; } case WeaponCategory.Crossbow: { float num6 = Mathf.Clamp(CrossbowBaseSpreadMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num6, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileAccuracy", num6); TryMultiplyFloatField(attackInstance, "m_projectileAccuracyMin", num6); } float num7 = Mathf.Clamp(CrossbowProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num7, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num7); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num7); } break; } case WeaponCategory.Spear: if (role == AttackRole.Secondary) { float num5 = Mathf.Clamp(SpearProjectileSpeedMultiplier.Value, 0f, 10f); if (!Mathf.Approximately(num5, 1f)) { TryMultiplyFloatField(attackInstance, "m_projectileVel", num5); TryMultiplyFloatField(attackInstance, "m_projectileVelMin", num5); } } 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) { 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); } 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) { 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) { 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) { 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); 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 = ((currentWeapon != null) ? ClassifyWeapon(currentWeapon) : WeaponCategory.Unarmed); AttackRole currentAttackRole = GetCurrentAttackRole(attacker, category); if (TryGetAttackerStaffConfig(attacker, out StaffConfig cfg) && cfg.DirectAttack && cfg.PvpStaggerMultiplier != null) { hit.m_staggerMultiplier *= Mathf.Clamp(cfg.PvpStaggerMultiplier.Value, 0f, 9999f); return; } if (GetConfiguredPvpStaggerDuration(val, category, currentAttackRole) <= 0f) { hit.m_staggerMultiplier = 0f; return; } 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 || 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 (RoleSettings value in AttackBuildingDamageMultipliers.Values) { value.SetAll(1f, 1f); } foreach (ConfigEntry value2 in RunningAttackBuildingDamageMultipliers.Values) { value2.Value = 1f; } foreach (ConfigEntry value3 in JumpAttackBuildingDamageMultipliers.Values) { value3.Value = 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 (GlobalFlankingDamageMultiplier != null) { GlobalFlankingDamageMultiplier.Value = 1f; } if (GlobalPvpFlankingDamageMultiplier != null) { GlobalPvpFlankingDamageMultiplier.Value = 1f; } foreach (ConfigEntry value in WeaponFlankingDamageMultipliers.Values) { value.Value = 1f; } foreach (ConfigEntry value2 in WeaponPvpFlankingDamageMultipliers.Values) { value2.Value = 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 (GlobalFlankingDamageMultiplier != null) { GlobalFlankingDamageMultiplier.Value = 1f; } if (GlobalPvpFlankingDamageMultiplier != null) { GlobalPvpFlankingDamageMultiplier.Value = 1f; } foreach (KeyValuePair> weaponFlankingDamageMultiplier in WeaponFlankingDamageMultipliers) { weaponFlankingDamageMultiplier.Value.Value = DefaultGooFlankingDamageMultiplier(weaponFlankingDamageMultiplier.Key, pvp: false); } foreach (KeyValuePair> weaponPvpFlankingDamageMultiplier in WeaponPvpFlankingDamageMultipliers) { weaponPvpFlankingDamageMultiplier.Value.Value = DefaultGooFlankingDamageMultiplier(weaponPvpFlankingDamageMultiplier.Key, pvp: true); } } 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 (RoleSettings value in AttackStaggeredEnemyDamageMultipliers.Values) { value.SetAll(1f, 1f); } foreach (ConfigEntry value2 in RunningAttackStaggeredEnemyDamageMultipliers.Values) { value2.Value = 1f; } foreach (ConfigEntry value3 in JumpAttackStaggeredEnemyDamageMultipliers.Values) { value3.Value = 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 (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 1f; } foreach (KeyValuePair> backstabDamageMultiplier in BackstabDamageMultipliers) { backstabDamageMultiplier.Value.SetAll(DefaultVanillaBackstabDamageMultiplier(backstabDamageMultiplier.Key), DefaultVanillaBackstabDamageMultiplier(backstabDamageMultiplier.Key)); } foreach (KeyValuePair runningAttackConfig in RunningAttackConfigs) { runningAttackConfig.Value.BackstabDamageMultiplier.Value = DefaultVanillaBackstabDamageMultiplier(runningAttackConfig.Key); } foreach (KeyValuePair jumpAttackConfig in JumpAttackConfigs) { jumpAttackConfig.Value.BackstabDamageMultiplier.Value = DefaultVanillaBackstabDamageMultiplier(jumpAttackConfig.Key); } foreach (StaffConfig value in StaffConfigs.Values) { value.BackstabDamageMultiplier.Value = 3f; } } private static void ResetBackstabDamageMultipliersToGoo() { if (GlobalBackstabDamageMultiplier != null) { GlobalBackstabDamageMultiplier.Value = 2f; } 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) { if (role != AttackRole.Primary1) { return 1f; } return 1.5f; } if (role != AttackRole.Secondary) { 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 = 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 - 6) <= 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) { return DefaultGooHitStopDurationMultiplier(category); } 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); } 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 = 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, 0.5f); } 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(0.95f, 0.95f); 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); } CategoryConfig config = GetConfig(WeaponCategory.OneHandedSword); config.PvpDamageMultiplier.Primary3.Value = 0.65f; config.PvpDamageMultiplier.Secondary.Value = 0.45f; CategoryConfig config2 = GetConfig(WeaponCategory.Club); config2.PvpDamageMultiplier.SetAll(1f, 1f); config2.PvpDamageMultiplier.Primary3.Value = 0.7f; config2.PvpDamageMultiplier.Secondary.Value = 0.55f; CategoryConfig config3 = GetConfig(WeaponCategory.TwoHandedSword); config3.PvpDamageMultiplier.Primary1.Value = 0.85f; config3.PvpDamageMultiplier.Primary2.Value = 0.85f; config3.PvpDamageMultiplier.Primary3.Value = 0.5f; config3.PvpDamageMultiplier.Secondary.Value = 0.4f; CategoryConfig config4 = GetConfig(WeaponCategory.TwoHandedAxe); config4.PvpDamageMultiplier.Primary1.Value = 0.6f; config4.PvpDamageMultiplier.Primary2.Value = 0.85f; config4.PvpDamageMultiplier.Primary3.Value = 0.6f; config4.PvpDamageMultiplier.Secondary.Value = 1.35f; GetConfig(WeaponCategory.Knife).PvpDamageMultiplier.Secondary.Value = 0.65f; GetConfig(WeaponCategory.DualKnives).PvpDamageMultiplier.Secondary.Value = 0.65f; CategoryConfig config5 = GetConfig(WeaponCategory.DualAxes); config5.PvpDamageMultiplier.SetPrimary(0.8f); config5.PvpDamageMultiplier.Primary4.Value = 0.55f; config5.PvpDamageMultiplier.Secondary.Value = 0.85f; GetConfig(WeaponCategory.OneHandedAxe).PvpDamageMultiplier.Primary3.Value = 0.65f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary1.Value = 1f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Primary2.Value = 0.7f; GetConfig(WeaponCategory.Unarmed).PvpDamageMultiplier.Secondary.Value = 1.3f; GetConfig(WeaponCategory.Knife).PvpDamageMultiplier.Primary3.Value = 0.65f; GetConfig(WeaponCategory.DualKnives).PvpDamageMultiplier.Primary3.Value = 0.65f; CategoryConfig config6 = GetConfig(WeaponCategory.Atgeir); config6.PvpDamageMultiplier.Primary3.Value = 0.65f; config6.PvpDamageMultiplier.Secondary.Value = 1.5f; GetConfig(WeaponCategory.Sledge).PvpDamageMultiplier.SetPrimary(0.8f); GetConfig(WeaponCategory.Pickaxe).PvpDamageMultiplier.SetAll(1f, 1f); GetConfig(WeaponCategory.Bow).PvpDamageMultiplier.SetAll(0.65f, 0.65f); GetConfig(WeaponCategory.Crossbow).PvpDamageMultiplier.SetAll(0.45f, 0.45f); 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 = 0.95f; value2.PvpStaggerMultiplier.Value = DefaultPvpStaggerPowerMultiplier(runningAttackConfig.Key, role); value2.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(runningAttackConfig.Key, role); value2.PvpKnockbackForceMultiplier.Value = 0.1f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.OneHandedSword, out RunningAttackConfig value3)) { value3.PvpDamageMultiplier.Value = 0.75f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.Club, out RunningAttackConfig value4)) { value4.PvpDamageMultiplier.Value = 0.8f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out RunningAttackConfig value5)) { value5.PvpDamageMultiplier.Value = 0.75f; } if (RunningAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out RunningAttackConfig value6)) { value6.PvpDamageMultiplier.Value = 0.85f; } foreach (KeyValuePair jumpAttackConfig in JumpAttackConfigs) { JumpAttackConfig value7 = jumpAttackConfig.Value; AttackRole role2 = RoleFromJumpAttackUsed(NormalizeJumpAttackUsed(jumpAttackConfig.Key, value7.UsedAttack.Value)); value7.PvpDamageMultiplier.Value = 0.95f; value7.PvpStaggerMultiplier.Value = DefaultPvpStaggerPowerMultiplier(jumpAttackConfig.Key, role2); value7.PvpStaggerDurationSeconds.Value = DefaultPvpStaggerDuration(jumpAttackConfig.Key, role2); value7.PvpKnockbackForceMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.OneHandedSword, out JumpAttackConfig value8)) { value8.PvpDamageMultiplier.Value = 0.65f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Club, out JumpAttackConfig value9)) { value9.PvpDamageMultiplier.Value = 0.7f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedSword, out JumpAttackConfig value10)) { value10.PvpDamageMultiplier.Value = 0.6f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.TwoHandedAxe, out JumpAttackConfig value11)) { value11.PvpDamageMultiplier.Value = 0.6f; value11.RecoveryAnimationSpeedMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.DualAxes, out JumpAttackConfig value12)) { value12.PvpDamageMultiplier.Value = 1f; } if (JumpAttackConfigs.TryGetValue(WeaponCategory.Atgeir, out JumpAttackConfig value13)) { value13.PvpDamageMultiplier.Value = 0.65f; } } 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 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 = AccessTools.Field(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 = AccessTools.Field(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 = AccessTools.Field(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("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") || flag4) { return WeaponCategory.DualAxes; } if (text3.Contains("greatsword")) { return WeaponCategory.TwoHandedSword; } if (text3.Contains("twohandedaxe")) { return WeaponCategory.TwoHandedAxe; } if (text3.Contains("atgeir")) { return WeaponCategory.Atgeir; } 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 (text4.Contains("sledge") || text4.Contains("demolisher") || text4.Contains("stagbreaker")) { 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, 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; } } }