using System; 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.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JG224.BetterCombat.Patches; using JG224.ModCore.API; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("JG224.BetterCombat.CoreTests")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("JG224.BetterCombat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.0.0")] [assembly: AssemblyInformationalVersion("0.5.0")] [assembly: AssemblyProduct("JG224.BetterCombat")] [assembly: AssemblyTitle("JG224.BetterCombat")] [assembly: AssemblyVersion("0.5.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace JG224.BetterCombat { internal static class DefenseDefaultsMigration { internal const int CurrentRevision = 2; internal static float PerfectBlockWindow(int revision, float current) { if (revision < 1 && Math.Abs(current - 0.25f) < 0.0001f) { current = 0.15f; } if (revision >= 2 || !(Math.Abs(current - 0.15f) < 0.0001f)) { return current; } return 0.25f; } internal static BlockCancelMode BlockCancel(int revision, BlockCancelMode current) { if (revision < 1 && current == BlockCancelMode.Full) { current = BlockCancelMode.Off; } if (revision >= 2 || current != BlockCancelMode.Off) { return current; } return BlockCancelMode.RecoveryOnly; } internal static float BossKnockback(int revision, float current) { if (revision >= 2 || !(Math.Abs(current - 0.15f) < 0.0001f)) { return current; } return 0.35f; } internal static float CombatClearDelay(int revision, float current) { if (revision >= 2 || !(Math.Abs(current - 8f) < 0.0001f)) { return current; } return 4f; } internal static float BowZoomFov(int revision, float current) { if (revision >= 2 || !(Math.Abs(current - 52f) < 0.0001f)) { return current; } return 40f; } internal static float BowZoomDrawSpeed(int revision, float current) { if (revision >= 2 || !(Math.Abs(current - 8f) < 0.0001f)) { return current; } return 3f; } internal static float BowZoomReleaseSpeed(int revision, float current) { if (revision >= 2 || !(Math.Abs(current - 14f) < 0.0001f)) { return current; } return 2f; } } internal sealed class BetterCombatConfig { private readonly ConfigFile _file; private readonly List> _entries = new List>(); internal ConfigEntry Enabled { get; } internal ConfigEntry Preset { get; } internal ConfigEntry TrainingFeedback { get; } internal ConfigEntry PerfectBlockWindow { get; } internal ConfigEntry BlockStaminaMultiplier { get; } internal ConfigEntry PerfectBlockStaminaMultiplier { get; } internal ConfigEntry BlockCancel { get; } internal ConfigEntry InstantBlockFacing { get; } internal ConfigEntry PlayerKnockbackMultiplier { get; } internal ConfigEntry EnemyKnockbackMultiplier { get; } internal ConfigEntry BossKnockbackMultiplier { get; } internal ConfigEntry KnockbackSpeed { get; } internal ConfigEntry KnockbackDurationLimit { get; } internal ConfigEntry DodgeStaminaMultiplier { get; } internal ConfigEntry PerfectDodgeRefundMultiplier { get; } internal ConfigEntry MeleeStaminaMultiplier { get; } internal ConfigEntry BowStaminaMultiplier { get; } internal ConfigEntry SpearStaminaMultiplier { get; } internal ConfigEntry MeleeVerticalAngle { get; } internal ConfigEntry MeleeCharacterRadiusBonus { get; } internal ConfigEntry BowTrajectory { get; } internal ConfigEntry ProgressiveBowVelocityBonus { get; } internal ConfigEntry ProgressiveBowGravityMultiplier { get; } internal ConfigEntry ProgressiveBowAimElevation { get; } internal ConfigEntry BowVelocityMultiplier { get; } internal ConfigEntry BowGravityMultiplier { get; } internal ConfigEntry SpearVelocityMultiplier { get; } internal ConfigEntry SpearGravityMultiplier { get; } internal ConfigEntry ArrowRecoveryChance { get; } internal ConfigEntry CombatClearDelaySeconds { get; } internal ConfigEntry DodgeHotkey { get; } internal ConfigEntry BowDrawCancelHotkey { get; } internal ConfigEntry AutoEquipShield { get; } internal ConfigEntry RestoreWeaponsAfterSwimming { get; } internal ConfigEntry BowZoom { get; } internal ConfigEntry BowZoomFov { get; } internal ConfigEntry BowZoomDrawSpeed { get; } internal ConfigEntry BowZoomReleaseSpeed { get; } internal ConfigEntry DebugLogging { get; } internal BetterCombatConfig(ConfigFile file) { //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04f3: Unknown result type (might be due to invalid IL or missing references) _file = file; RemoveObsoletePoiseSettings(); Enabled = Server("General", "Enabled", value: true, "Master switch for BetterCombat gameplay changes."); Preset = Server("General", "PolicyPreset", CombatPreset.Custom, "Custom uses saved settings. Standard uses current defaults. Relaxed widens perfect blocks and reduces stamina costs; Precise narrows blocks and uses native bow trajectories. Switching presets preserves all custom values."); TrainingFeedback = Local("HUD", "TrainingFeedback", value: false, "Show local perfect-block timing and perfect-dodge feedback during gameplay."); PerfectBlockWindow = ServerRange("Defense", "PerfectBlockWindow", 0.25f, 0.05f, 1f, "Seconds after raising a block that count as a perfect block. The default 0.25 matches Valheim."); BlockStaminaMultiplier = ServerRange("Defense", "BlockStaminaMultiplier", 1f, 0f, 5f, "Normal block stamina cost multiplier."); PerfectBlockStaminaMultiplier = ServerRange("Defense", "PerfectBlockStaminaMultiplier", 1f, 0f, 5f, "Perfect block stamina cost multiplier."); BlockCancel = Server("Defense", "BlockCancelMode", BlockCancelMode.RecoveryOnly, "Off never interrupts attacks; RecoveryOnly requires a fresh block press after the hit commits and cancels only recovery; Full cancels wind-up or recovery immediately. Completed hits and projectiles are never reversed."); InstantBlockFacing = Server("Defense", "InstantBlockFacing", value: true, "Immediately face horizontal camera aim while blocking instead of using Valheim's slow combat turn."); PlayerKnockbackMultiplier = ServerRange("Knockback", "PlayerKnockbackMultiplier", 0.35f, 0f, 2f, "Multiplier for knockback received by players before the duration cap is applied."); EnemyKnockbackMultiplier = ServerRange("Knockback", "EnemyKnockbackMultiplier", 0.65f, 0f, 2f, "Multiplier for knockback received by ordinary enemies before the duration cap is applied."); BossKnockbackMultiplier = ServerRange("Knockback", "BossKnockbackMultiplier", 0.35f, 0f, 2f, "Multiplier for knockback received by bosses before the duration cap is applied."); KnockbackSpeed = ServerRange("Knockback", "KnockbackSpeed", 8f, 0f, 20f, "Horizontal push speed while knockback is active. Valheim normally forces 20."); KnockbackDurationLimit = ServerRange("Knockback", "KnockbackDurationLimit", 0.25f, 0f, 1f, "Maximum seconds a single combat push can remain active. At defaults, displacement is about two metres."); DodgeStaminaMultiplier = ServerRange("Defense", "DodgeStaminaMultiplier", 1f, 0f, 5f, "Dodge stamina cost multiplier."); PerfectDodgeRefundMultiplier = ServerRange("Defense", "PerfectDodgeRefundMultiplier", 1f, 0f, 5f, "Multiplier for Valheim's native perfect-dodge stamina refund."); MeleeStaminaMultiplier = ServerRange("Offense", "MeleeStaminaMultiplier", 1f, 0f, 5f, "Attack stamina multiplier for ordinary melee weapons."); BowStaminaMultiplier = ServerRange("Offense", "BowStaminaMultiplier", 1f, 0f, 5f, "Attack stamina multiplier for bows and crossbows."); SpearStaminaMultiplier = ServerRange("Offense", "SpearStaminaMultiplier", 1f, 0f, 5f, "Attack stamina multiplier for spear melee and thrown attacks."); MeleeVerticalAngle = ServerRange("Targeting", "MeleeVerticalAngle", 55f, 0f, 89f, "Maximum vertical angle in degrees for player melee aim correction."); MeleeCharacterRadiusBonus = ServerRange("Targeting", "MeleeCharacterRadiusBonus", 0.08f, 0f, 1f, "Extra character-only melee trace radius, improving hits on slopes without hitting through walls."); BowTrajectory = Server("Projectiles", "BowTrajectoryMode", BowTrajectoryMode.Progressive, "Native preserves Valheim bow ballistics. Progressive adds a tier-preserving velocity bonus, stronger gravity, and a small aim lift while retaining the native draw curve and bow progression."); ProgressiveBowVelocityBonus = ServerRange("Projectiles", "ProgressiveBowVelocityBonus", 10f, 0f, 50f, "Velocity added to a draw bow's native full-draw maximum in Progressive mode. Native bow differences are retained."); ProgressiveBowGravityMultiplier = ServerRange("Projectiles", "ProgressiveBowGravityMultiplier", 2.75f, 0f, 4f, "Additional arrow gravity multiplier in Progressive mode, applied before BowGravityMultiplier."); ProgressiveBowAimElevation = ServerRange("Projectiles", "ProgressiveBowAimElevation", 0.05f, -0.2f, 0.2f, "Small normalized world-up aim correction in Progressive mode. The default is 0.05."); BowVelocityMultiplier = ServerRange("Projectiles", "BowVelocityMultiplier", 1f, 0.25f, 4f, "Final bow and crossbow projectile velocity multiplier, applied after the selected trajectory mode."); BowGravityMultiplier = ServerRange("Projectiles", "BowGravityMultiplier", 1f, 0f, 4f, "Final bow and crossbow projectile gravity multiplier, applied after the selected trajectory mode."); SpearVelocityMultiplier = ServerRange("Projectiles", "SpearVelocityMultiplier", 1f, 0.25f, 4f, "Thrown spear velocity multiplier."); SpearGravityMultiplier = ServerRange("Projectiles", "SpearGravityMultiplier", 1f, 0f, 4f, "Thrown spear gravity multiplier."); ArrowRecoveryChance = ServerRange("Projectiles", "ArrowRecoveryChance", 0.25f, 0f, 1f, "Chance for a physical arrow or bolt to drop on impact. Recovery is decided by the projectile owner."); CombatClearDelaySeconds = ServerRange("Integration", "CombatClearDelaySeconds", 4f, 0f, 60f, "Seconds without hostile contact before Mod Core reports the player out of combat."); DodgeHotkey = Local("Dodge", "DodgeHotkey", new KeyboardShortcut((KeyCode)308, Array.Empty()), "Highest-priority local dodge shortcut. Short taps are latched; directional movement rolls that way, while no direction rolls backward from camera aim like default Valheim."); BowDrawCancelHotkey = Local("Archery", "BowDrawCancelHotkey", new KeyboardShortcut((KeyCode)101, Array.Empty()), "Local key that safely cancels an active draw-bow attack before the arrow fires. Set to None to disable."); AutoEquipShield = Local("Equipment", "AutoEquipShieldWithOneHandedWeapon", value: true, "Automatically equip the last used usable shield, or the first usable shield in inventory, after equipping a one-handed weapon into an empty off hand."); RestoreWeaponsAfterSwimming = Local("Equipment", "RestoreWeaponsAfterSwimming", value: true, "Remember native hand items hidden for swimming and retry equipping those exact inventory items after returning to ground."); BowZoom = Local("Camera", "BowZoom", BowZoomMode.WhileDrawing, "Off, WhileDrawing, or WhileEquipped. This is visual and never server-authoritative."); BowZoomFov = LocalRange("Camera", "BowZoomFov", 40f, 30f, 80f, "Camera field of view while bow zoom is active."); BowZoomDrawSpeed = LocalRange("Camera", "BowZoomDrawSpeed", 3f, 1f, 30f, "Zoom-in interpolation speed while an arrow is nocked/drawing."); BowZoomReleaseSpeed = LocalRange("Camera", "BowZoomReleaseSpeed", 2f, 1f, 30f, "Zoom-out interpolation speed after firing, cancelling, or putting the bow away."); DebugLogging = Developer("Diagnostics", "DebugLogging", value: false, "Enable verbose BetterCombat diagnostics."); ApplyDefaultMigrations(); } private void ApplyDefaultMigrations() { ConfigEntry val = _file.Bind("Migration", "DefenseDefaultsRevision", 0, "Internal one-time migration marker for Better Combat defaults."); if (val.Value < 2) { PerfectBlockWindow.Value = DefenseDefaultsMigration.PerfectBlockWindow(val.Value, PerfectBlockWindow.Value); BlockCancel.Value = DefenseDefaultsMigration.BlockCancel(val.Value, BlockCancel.Value); BossKnockbackMultiplier.Value = DefenseDefaultsMigration.BossKnockback(val.Value, BossKnockbackMultiplier.Value); CombatClearDelaySeconds.Value = DefenseDefaultsMigration.CombatClearDelay(val.Value, CombatClearDelaySeconds.Value); BowZoomFov.Value = DefenseDefaultsMigration.BowZoomFov(val.Value, BowZoomFov.Value); BowZoomDrawSpeed.Value = DefenseDefaultsMigration.BowZoomDrawSpeed(val.Value, BowZoomDrawSpeed.Value); BowZoomReleaseSpeed.Value = DefenseDefaultsMigration.BowZoomReleaseSpeed(val.Value, BowZoomReleaseSpeed.Value); val.Value = 2; _file.Save(); } } internal IEnumerable RegisterMetadata(ICoreServices services, ModuleId owner) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (Tuple entry2 in _entries) { ConfigEntryBase entry = entry2.Item1; yield return services.Configuration.Register(new ConfigSettingDescriptor(owner, entry.Definition.Section, entry.Definition.Key, entry2.Item2, (Func)(() => Convert.ToString(entry.BoxedValue, CultureInfo.InvariantCulture)), 1)); } } internal CombatPolicy CreatePolicy() { CombatPolicy custom = new CombatPolicy { Enabled = Enabled.Value, PerfectBlockWindow = PerfectBlockWindow.Value, BlockStaminaMultiplier = BlockStaminaMultiplier.Value, PerfectBlockStaminaMultiplier = PerfectBlockStaminaMultiplier.Value, BlockCancel = BlockCancel.Value, InstantBlockFacing = InstantBlockFacing.Value, PlayerKnockbackMultiplier = PlayerKnockbackMultiplier.Value, EnemyKnockbackMultiplier = EnemyKnockbackMultiplier.Value, BossKnockbackMultiplier = BossKnockbackMultiplier.Value, KnockbackSpeed = KnockbackSpeed.Value, KnockbackDurationLimit = KnockbackDurationLimit.Value, DodgeStaminaMultiplier = DodgeStaminaMultiplier.Value, PerfectDodgeRefundMultiplier = PerfectDodgeRefundMultiplier.Value, MeleeStaminaMultiplier = MeleeStaminaMultiplier.Value, BowStaminaMultiplier = BowStaminaMultiplier.Value, SpearStaminaMultiplier = SpearStaminaMultiplier.Value, MeleeVerticalAngle = MeleeVerticalAngle.Value, MeleeCharacterRadiusBonus = MeleeCharacterRadiusBonus.Value, BowTrajectory = BowTrajectory.Value, ProgressiveBowVelocityBonus = ProgressiveBowVelocityBonus.Value, ProgressiveBowGravityMultiplier = ProgressiveBowGravityMultiplier.Value, ProgressiveBowAimElevation = ProgressiveBowAimElevation.Value, BowVelocityMultiplier = BowVelocityMultiplier.Value, BowGravityMultiplier = BowGravityMultiplier.Value, SpearVelocityMultiplier = SpearVelocityMultiplier.Value, SpearGravityMultiplier = SpearGravityMultiplier.Value, ArrowRecoveryChance = ArrowRecoveryChance.Value, CombatClearDelaySeconds = CombatClearDelaySeconds.Value }; return CombatPresets.Apply(Preset.Value, custom); } private ConfigEntry Server(string section, string key, T value, string description) { return Add(_file.Bind(section, key, value, description), (ConfigScope)2); } private ConfigEntry ServerRange(string section, string key, float value, float min, float max, string description) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown return Add(_file.Bind(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(min, max), Array.Empty())), (ConfigScope)2); } private ConfigEntry Local(string section, string key, T value, string description) { return Add(_file.Bind(section, key, value, description), (ConfigScope)1); } private ConfigEntry LocalRange(string section, string key, float value, float min, float max, string description) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown return Add(_file.Bind(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(min, max), Array.Empty())), (ConfigScope)1); } private ConfigEntry Developer(string section, string key, T value, string description) { return Add(_file.Bind(section, key, value, description), (ConfigScope)5); } private ConfigEntry Add(ConfigEntry entry, ConfigScope scope) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) _entries.Add(Tuple.Create((ConfigEntryBase)(object)entry, scope)); return entry; } private void RemoveObsoletePoiseSettings() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown bool flag = false; ConfigDefinition[] array = (ConfigDefinition[])(object)new ConfigDefinition[8] { new ConfigDefinition("Poise", "EnemyPoiseMultiplier"), new ConfigDefinition("Poise", "BossPoise"), new ConfigDefinition("Poise", "BossPoiseHealthFactor"), new ConfigDefinition("HUD", "ShowPoiseBars"), new ConfigDefinition("HUD", "PoiseBarHeight"), new ConfigDefinition("HUD", "PoiseBarColor"), new ConfigDefinition("HUD", "PoiseBreakColor"), new ConfigDefinition("HUD", "PoiseFadeSeconds") }; foreach (ConfigDefinition val in array) { _file.Bind(val.Section, val.Key, string.Empty, (ConfigDescription)null); flag |= _file.Remove(val); } if (flag) { _file.Save(); } } } internal sealed class BetterCombatRuntime : IDisposable { private readonly ICoreServices _core; private readonly CombatNetwork _network; internal BetterCombatRuntime(ICoreServices core, BetterCombatConfig config, IList ownerRegistrations) { //IL_0021: 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) _core = core; _network = new CombatNetwork(core, config); ownerRegistrations.Add(core.Lifecycle.Subscribe(Plugin.ModuleId, (LifecycleEventKind)9, (Action)delegate { Reset(); }, 0)); ownerRegistrations.Add(core.Lifecycle.Subscribe(Plugin.ModuleId, (LifecycleEventKind)3, (Action)delegate { Reset(); }, 0)); } internal void Tick(double now) { _network.Tick(now); _core.CombatState.Tick(now); } internal void ObserveCombat(Character first, Character second) { //IL_0009: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown double num = Time.unscaledTime; CombatContextFlags val = CombatIdentity.Context(first, second); string text = CombatIdentity.PlayerId(first); string text2 = CombatIdentity.PlayerId(second); string text3 = (Object.op_Implicit((Object)(object)second) ? CombatIdentity.Actor(second).ActorId : string.Empty); string text4 = ((Object.op_Implicit((Object)(object)second) && second.IsBoss()) ? text3 : string.Empty); if (text.Length != 0) { _core.CombatState.Observe(new CombatObservation(text, num, val, text3, text4, (double)Plugin.Policy.CombatClearDelaySeconds, true)); } if (text2.Length != 0) { _core.CombatState.Observe(new CombatObservation(text2, num, val, Object.op_Implicit((Object)(object)first) ? CombatIdentity.Actor(first).ActorId : string.Empty, (Object.op_Implicit((Object)(object)first) && first.IsBoss()) ? CombatIdentity.Actor(first).ActorId : string.Empty, (double)Plugin.Policy.CombatClearDelaySeconds, true)); } } internal void Reset() { _network.ResetConnection(); ResponsiveBlock.Clear(); EquipmentAutomation.Clear(); DodgeHotkeyInput.Clear(); } public void Dispose() { Reset(); _network.Dispose(); } } internal static class CombatIdentity { internal static string Key(ZDOID id) { return ((ZDOID)(ref id)).UserID + ":" + ((ZDOID)(ref id)).ID; } internal static CombatActor Actor(Character character) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)character)) { return CombatActor.None; } ZDOID zDOID = character.GetZDOID(); string obj = (((ZDOID)(ref zDOID)).IsNone() ? ("scene:" + ((Object)character).GetInstanceID()) : ("zdo:" + Key(zDOID))); string prefabName = Utils.GetPrefabName(((Component)character).gameObject); return new CombatActor(obj, character, prefabName, character.GetHoverName(), character.IsPlayer(), character.IsBoss()); } internal static CombatContextFlags Context(Character first, Character second = null) { //IL_0001: 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_0024: 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_004a: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) CombatContextFlags val = (CombatContextFlags)1; if ((Object.op_Implicit((Object)(object)first) && first.IsBoss()) || (Object.op_Implicit((Object)(object)second) && second.IsBoss())) { val = (CombatContextFlags)(val | 2); } if (Object.op_Implicit((Object)(object)first) && Object.op_Implicit((Object)(object)second) && first.IsPlayer() && second.IsPlayer()) { val = (CombatContextFlags)(val | 4); } return val; } internal static string PlayerId(Character character) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)character) || !character.IsPlayer()) { return string.Empty; } ZDOID zDOID = character.GetZDOID(); if (!((ZDOID)(ref zDOID)).IsNone()) { return "player:" + Key(zDOID); } return "local:" + ((Object)character).GetInstanceID(); } } internal sealed class CombatNetwork : IDisposable { private static readonly NetworkMessageDescriptor PolicyRequest = new NetworkMessageDescriptor(Plugin.ModuleId, (ushort)1, 8, (NetworkDirection)1, 32, 0uL); private static readonly NetworkMessageDescriptor PolicySnapshot = new NetworkMessageDescriptor(Plugin.ModuleId, (ushort)2, 8, (NetworkDirection)2, 1056, 0uL); private readonly IAuthoritativePolicyRegistry _policies; private readonly INetworkRouter _network; private readonly Func _createPolicy; private readonly List _registrations = new List(); private IDisposable _policyRegistration; private byte[] _lastServerPolicy = Array.Empty(); private long _policyRevision; private double _nextRequest; private bool _clientHasPolicy; private bool _clientPolicySuppressed; private bool _disposed; internal CombatNetwork(ICoreServices core, BetterCombatConfig config) : this(core.Policies, core.Network, config.CreatePolicy) { } internal CombatNetwork(IAuthoritativePolicyRegistry policies, INetworkRouter network, Func createPolicy) { _policies = policies; _network = network; _createPolicy = createPolicy; try { RegisterPolicy(); _registrations.Add(network.Register(PolicyRequest, (Action)OnPolicyRequest)); _registrations.Add(network.Register(PolicySnapshot, (Action)OnPolicySnapshot)); } catch { Dispose(); throw; } } internal void Tick(double now) { if (!_disposed && Object.op_Implicit((Object)(object)ZNet.instance)) { Tick(now, ZNet.instance.IsServer()); } } internal void Tick(double now, bool isServer) { //IL_0067: 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: Expected O, but got Unknown if (_disposed) { return; } if (isServer) { CombatPolicy combatPolicy = _createPolicy(); byte[] array = combatPolicy.Serialize(); if (!Equal(array, _lastServerPolicy)) { _lastServerPolicy = array; _policyRevision = Math.Max(_policyRevision + 1, DateTime.UtcNow.Ticks); Plugin.Policy = combatPolicy; PolicySnapshot val = default(PolicySnapshot); string text = default(string); if (!_policies.TryApply(Plugin.ModuleId, _policyRevision, array, true, ref val, ref text)) { Plugin.Log.LogError((object)("Server combat policy rejected: " + text)); } else { _network.Broadcast(PolicySnapshot, PolicyPackage(_policyRevision, array), 0, (NetworkMessageFlags)9); } } } else if (!_clientHasPolicy && now >= _nextRequest) { if (!_clientPolicySuppressed) { Plugin.Policy = new CombatPolicy { Enabled = false }; _clientPolicySuppressed = true; } _nextRequest = now + 1.0; _network.SendToServer(PolicyRequest, new ZPackage(), _network.NextCorrelationId(), (NetworkMessageFlags)2); } } private void OnPolicyRequest(NetworkMessageContext context, ZPackage payload) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer()) { if (_lastServerPolicy.Length == 0) { Plugin.Policy = _createPolicy(); _lastServerPolicy = Plugin.Policy.Serialize(); _policyRevision = Math.Max(_policyRevision + 1, DateTime.UtcNow.Ticks); PolicySnapshot val = default(PolicySnapshot); string text = default(string); _policies.TryApply(Plugin.ModuleId, _policyRevision, _lastServerPolicy, true, ref val, ref text); } _network.SendToPeer(context.PeerId, PolicySnapshot, PolicyPackage(_policyRevision, _lastServerPolicy), context.CorrelationId, (NetworkMessageFlags)5); } } private void OnPolicySnapshot(NetworkMessageContext context, ZPackage payload) { if (_disposed || (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())) { return; } try { long revision = payload.ReadLong(); byte[] bytes = payload.ReadByteArray(); if (!TryApplyPolicy(revision, bytes, out var error)) { Plugin.Log.LogWarning((object)("Mod Core rejected server combat policy: " + error)); } else { Plugin.Debug("Applied server combat policy revision " + revision + "."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Malformed combat policy packet rejected: " + ex.Message)); } } internal bool TryApplyPolicy(long revision, byte[] bytes, out string error) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) error = string.Empty; if (_disposed) { error = "Combat policy connection is disposed."; return false; } PolicySnapshot val = default(PolicySnapshot); if (!CombatPolicy.TryDeserialize(bytes, out var policy, out error) || !_policies.TryApply(Plugin.ModuleId, revision, bytes, true, ref val, ref error)) { return false; } Plugin.Policy = policy; _clientHasPolicy = true; _clientPolicySuppressed = false; return true; } private static ZPackage PolicyPackage(long revision, byte[] bytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(revision); val.Write(bytes); return val; } private static bool Equal(byte[] first, byte[] second) { if (first == second) { return true; } if (first == null || second == null || first.Length != second.Length) { return false; } for (int i = 0; i < first.Length; i++) { if (first[i] != second[i]) { return false; } } return true; } internal void ResetConnection() { if (!_disposed) { _clientHasPolicy = false; _clientPolicySuppressed = false; _nextRequest = 0.0; _lastServerPolicy = Array.Empty(); _policyRevision = 0L; Plugin.Policy = new CombatPolicy { Enabled = false }; _policyRegistration?.Dispose(); _policyRegistration = null; RegisterPolicy(); } } private void RegisterPolicy() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown _policyRegistration = _policies.Register(new PolicyDescriptor(Plugin.ModuleId, 6, 1024, (Func)delegate(byte[] bytes) { CombatPolicy.TryDeserialize(bytes, out var _, out var error); return error; })); } public void Dispose() { if (!_disposed) { _disposed = true; for (int num = _registrations.Count - 1; num >= 0; num--) { _registrations[num].Dispose(); } _registrations.Clear(); _policyRegistration?.Dispose(); _policyRegistration = null; } } } public enum BowZoomMode : byte { Off, WhileDrawing, WhileEquipped } public enum BlockCancelMode : byte { Off, RecoveryOnly, Full } public enum BowTrajectoryMode : byte { Native, Progressive } public sealed class CombatPolicy { public const int SchemaVersion = 6; public const int MaximumBytes = 1024; public bool Enabled { get; set; } = true; public float PerfectBlockWindow { get; set; } = 0.25f; public float BlockStaminaMultiplier { get; set; } = 1f; public float PerfectBlockStaminaMultiplier { get; set; } = 1f; public BlockCancelMode BlockCancel { get; set; } = BlockCancelMode.RecoveryOnly; public bool InstantBlockFacing { get; set; } = true; public float PlayerKnockbackMultiplier { get; set; } = 0.35f; public float EnemyKnockbackMultiplier { get; set; } = 0.65f; public float BossKnockbackMultiplier { get; set; } = 0.35f; public float KnockbackSpeed { get; set; } = 8f; public float KnockbackDurationLimit { get; set; } = 0.25f; public float DodgeStaminaMultiplier { get; set; } = 1f; public float PerfectDodgeRefundMultiplier { get; set; } = 1f; public float MeleeStaminaMultiplier { get; set; } = 1f; public float BowStaminaMultiplier { get; set; } = 1f; public float SpearStaminaMultiplier { get; set; } = 1f; public float MeleeVerticalAngle { get; set; } = 55f; public float MeleeCharacterRadiusBonus { get; set; } = 0.08f; public BowTrajectoryMode BowTrajectory { get; set; } = BowTrajectoryMode.Progressive; public float ProgressiveBowVelocityBonus { get; set; } = 10f; public float ProgressiveBowGravityMultiplier { get; set; } = 2.75f; public float ProgressiveBowAimElevation { get; set; } = 0.05f; public float BowVelocityMultiplier { get; set; } = 1f; public float BowGravityMultiplier { get; set; } = 1f; public float SpearVelocityMultiplier { get; set; } = 1f; public float SpearGravityMultiplier { get; set; } = 1f; public float ArrowRecoveryChance { get; set; } = 0.25f; public float CombatClearDelaySeconds { get; set; } = 4f; public byte[] Serialize() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(6); binaryWriter.Write(Enabled); binaryWriter.Write(PerfectBlockWindow); binaryWriter.Write(BlockStaminaMultiplier); binaryWriter.Write(PerfectBlockStaminaMultiplier); binaryWriter.Write((byte)BlockCancel); binaryWriter.Write(InstantBlockFacing); binaryWriter.Write(PlayerKnockbackMultiplier); binaryWriter.Write(EnemyKnockbackMultiplier); binaryWriter.Write(BossKnockbackMultiplier); binaryWriter.Write(KnockbackSpeed); binaryWriter.Write(KnockbackDurationLimit); binaryWriter.Write(DodgeStaminaMultiplier); binaryWriter.Write(PerfectDodgeRefundMultiplier); binaryWriter.Write(MeleeStaminaMultiplier); binaryWriter.Write(BowStaminaMultiplier); binaryWriter.Write(SpearStaminaMultiplier); binaryWriter.Write(MeleeVerticalAngle); binaryWriter.Write(MeleeCharacterRadiusBonus); binaryWriter.Write((byte)BowTrajectory); binaryWriter.Write(ProgressiveBowVelocityBonus); binaryWriter.Write(ProgressiveBowGravityMultiplier); binaryWriter.Write(ProgressiveBowAimElevation); binaryWriter.Write(BowVelocityMultiplier); binaryWriter.Write(BowGravityMultiplier); binaryWriter.Write(SpearVelocityMultiplier); binaryWriter.Write(SpearGravityMultiplier); binaryWriter.Write(ArrowRecoveryChance); binaryWriter.Write(CombatClearDelaySeconds); return memoryStream.ToArray(); } public static bool TryDeserialize(byte[] bytes, out CombatPolicy policy, out string error) { policy = null; error = ValidatePayload(bytes); if (error.Length != 0) { return false; } try { using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes, writable: false)); int num = binaryReader.ReadInt32(); if (num != 6) { error = "Unsupported policy schema " + num + "."; return false; } CombatPolicy combatPolicy = new CombatPolicy { Enabled = binaryReader.ReadBoolean(), PerfectBlockWindow = binaryReader.ReadSingle(), BlockStaminaMultiplier = binaryReader.ReadSingle(), PerfectBlockStaminaMultiplier = binaryReader.ReadSingle(), BlockCancel = (BlockCancelMode)binaryReader.ReadByte(), InstantBlockFacing = binaryReader.ReadBoolean(), PlayerKnockbackMultiplier = binaryReader.ReadSingle(), EnemyKnockbackMultiplier = binaryReader.ReadSingle(), BossKnockbackMultiplier = binaryReader.ReadSingle(), KnockbackSpeed = binaryReader.ReadSingle(), KnockbackDurationLimit = binaryReader.ReadSingle(), DodgeStaminaMultiplier = binaryReader.ReadSingle(), PerfectDodgeRefundMultiplier = binaryReader.ReadSingle(), MeleeStaminaMultiplier = binaryReader.ReadSingle(), BowStaminaMultiplier = binaryReader.ReadSingle(), SpearStaminaMultiplier = binaryReader.ReadSingle(), MeleeVerticalAngle = binaryReader.ReadSingle(), MeleeCharacterRadiusBonus = binaryReader.ReadSingle(), BowTrajectory = (BowTrajectoryMode)binaryReader.ReadByte(), ProgressiveBowVelocityBonus = binaryReader.ReadSingle(), ProgressiveBowGravityMultiplier = binaryReader.ReadSingle(), ProgressiveBowAimElevation = binaryReader.ReadSingle(), BowVelocityMultiplier = binaryReader.ReadSingle(), BowGravityMultiplier = binaryReader.ReadSingle(), SpearVelocityMultiplier = binaryReader.ReadSingle(), SpearGravityMultiplier = binaryReader.ReadSingle(), ArrowRecoveryChance = binaryReader.ReadSingle(), CombatClearDelaySeconds = binaryReader.ReadSingle() }; if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length) { error = "Policy contains trailing data."; return false; } error = combatPolicy.Validate(); if (error.Length != 0) { return false; } policy = combatPolicy; return true; } catch (Exception ex) { error = "Policy could not be decoded: " + ex.Message; return false; } } public static string ValidatePayload(byte[] bytes) { if (bytes == null || bytes.Length == 0) { return "Policy payload is empty."; } if (bytes.Length > 1024) { return "Policy payload exceeds " + 1024 + " bytes."; } return string.Empty; } public string Validate() { string result; if ((result = Range(PerfectBlockWindow, 0.05f, 1f, "PerfectBlockWindow")).Length != 0) { return result; } if ((result = Range(BlockStaminaMultiplier, 0f, 5f, "BlockStaminaMultiplier")).Length != 0) { return result; } if ((result = Range(PerfectBlockStaminaMultiplier, 0f, 5f, "PerfectBlockStaminaMultiplier")).Length != 0) { return result; } if (!Enum.IsDefined(typeof(BlockCancelMode), BlockCancel)) { return "BlockCancel is invalid."; } if ((result = Range(PlayerKnockbackMultiplier, 0f, 2f, "PlayerKnockbackMultiplier")).Length != 0) { return result; } if ((result = Range(EnemyKnockbackMultiplier, 0f, 2f, "EnemyKnockbackMultiplier")).Length != 0) { return result; } if ((result = Range(BossKnockbackMultiplier, 0f, 2f, "BossKnockbackMultiplier")).Length != 0) { return result; } if ((result = Range(KnockbackSpeed, 0f, 20f, "KnockbackSpeed")).Length != 0) { return result; } if ((result = Range(KnockbackDurationLimit, 0f, 1f, "KnockbackDurationLimit")).Length != 0) { return result; } if ((result = Range(DodgeStaminaMultiplier, 0f, 5f, "DodgeStaminaMultiplier")).Length != 0) { return result; } if ((result = Range(PerfectDodgeRefundMultiplier, 0f, 5f, "PerfectDodgeRefundMultiplier")).Length != 0) { return result; } if ((result = Range(MeleeStaminaMultiplier, 0f, 5f, "MeleeStaminaMultiplier")).Length != 0) { return result; } if ((result = Range(BowStaminaMultiplier, 0f, 5f, "BowStaminaMultiplier")).Length != 0) { return result; } if ((result = Range(SpearStaminaMultiplier, 0f, 5f, "SpearStaminaMultiplier")).Length != 0) { return result; } if ((result = Range(MeleeVerticalAngle, 0f, 89f, "MeleeVerticalAngle")).Length != 0) { return result; } if ((result = Range(MeleeCharacterRadiusBonus, 0f, 1f, "MeleeCharacterRadiusBonus")).Length != 0) { return result; } if (!Enum.IsDefined(typeof(BowTrajectoryMode), BowTrajectory)) { return "BowTrajectory is invalid."; } if ((result = Range(ProgressiveBowVelocityBonus, 0f, 50f, "ProgressiveBowVelocityBonus")).Length != 0) { return result; } if ((result = Range(ProgressiveBowGravityMultiplier, 0f, 4f, "ProgressiveBowGravityMultiplier")).Length != 0) { return result; } if ((result = Range(ProgressiveBowAimElevation, -0.2f, 0.2f, "ProgressiveBowAimElevation")).Length != 0) { return result; } if ((result = Range(BowVelocityMultiplier, 0.25f, 4f, "BowVelocityMultiplier")).Length != 0) { return result; } if ((result = Range(BowGravityMultiplier, 0f, 4f, "BowGravityMultiplier")).Length != 0) { return result; } if ((result = Range(SpearVelocityMultiplier, 0.25f, 4f, "SpearVelocityMultiplier")).Length != 0) { return result; } if ((result = Range(SpearGravityMultiplier, 0f, 4f, "SpearGravityMultiplier")).Length != 0) { return result; } if ((result = Range(ArrowRecoveryChance, 0f, 1f, "ArrowRecoveryChance")).Length != 0) { return result; } return Range(CombatClearDelaySeconds, 0f, 60f, "CombatClearDelaySeconds"); } private static string Range(float value, float minimum, float maximum, string name) { if (!float.IsNaN(value) && !float.IsInfinity(value) && !(value < minimum) && !(value > maximum)) { return string.Empty; } return name + " must be between " + minimum + " and " + maximum + "."; } } public enum CombatPreset : byte { Custom, Standard, Relaxed, Precise } internal static class CombatPresets { internal static CombatPolicy Apply(CombatPreset preset, CombatPolicy custom) { if (preset == CombatPreset.Custom) { return custom; } CombatPolicy combatPolicy = new CombatPolicy { Enabled = custom.Enabled }; switch (preset) { case CombatPreset.Relaxed: { combatPolicy.PerfectBlockWindow = 0.4f; float num = (combatPolicy.SpearStaminaMultiplier = 0.75f); float num3 = (combatPolicy.BowStaminaMultiplier = num); float num5 = (combatPolicy.MeleeStaminaMultiplier = num3); float blockStaminaMultiplier = (combatPolicy.DodgeStaminaMultiplier = num5); combatPolicy.BlockStaminaMultiplier = blockStaminaMultiplier; combatPolicy.PerfectDodgeRefundMultiplier = 1.25f; break; } case CombatPreset.Precise: combatPolicy.PerfectBlockWindow = 0.15f; combatPolicy.BlockCancel = BlockCancelMode.Off; combatPolicy.MeleeCharacterRadiusBonus = 0f; combatPolicy.BowTrajectory = BowTrajectoryMode.Native; break; } return combatPolicy; } } internal static class CombatRulesBridge { private static long _action; internal static double Now => Time.unscaledTime; internal static string Action(string kind) { return kind + ":" + Interlocked.Increment(ref _action); } internal static CombatItem Item(ItemData item) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (item == null) { return CombatItem.None; } string text = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? Utils.GetPrefabName(item.m_dropPrefab) : item.m_shared.m_name); return new CombatItem(text + ":q" + item.m_quality + ":" + ((object)item).GetHashCode(), text, item); } internal static void ApplyDurability(Character actor, ItemData item, float before, DurabilityUseKind kind, string action) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown if (item != null && !(before < 0f)) { float num = Mathf.Max(0f, before - item.m_durability); if (!(num <= 0f)) { DurabilityRuleContext val = new DurabilityRuleContext(action, CombatIdentity.Actor(actor), Item(item), kind, before, num, CombatIdentity.Context(actor), Now, true); DurabilityRuleDecision val2 = new DurabilityRuleDecision(num, true, ""); PipelineEvaluation val3 = CombatRulePipelineExtensions.GetDurabilityPipeline(Plugin.Core.Rules).Evaluate(val, val2); DurabilityRuleDecision val4 = val3.Decision ?? val2; item.m_durability = ((val4.Apply && !val3.Vetoed) ? Mathf.Max(0f, before - val4.DurabilityLoss) : before); } } } } internal static class ConfigFileMigration { internal const string CurrentFileName = "jg224.bettercombat.cfg"; internal const string LegacyFileName = "com.jg224.bettercombat.cfg"; internal static ConfigFile Open(BaseUnityPlugin plugin, string configDirectory, ManualLogSource log) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown if (MoveLegacy(configDirectory) && log != null) { log.LogInfo((object)"Renamed legacy config com.jg224.bettercombat.cfg to jg224.bettercombat.cfg."); } return PluginConfigFiles.Attach(plugin, new ConfigFile(Path.Combine(configDirectory, "jg224.bettercombat.cfg"), true, plugin.Info.Metadata)); } internal static bool MoveLegacy(string configDirectory) { if (!Directory.Exists(configDirectory)) { Directory.CreateDirectory(configDirectory); } if (ExactPath(configDirectory, "jg224.bettercombat.cfg") != null) { return false; } string text = ExactPath(configDirectory, "com.jg224.bettercombat.cfg"); if (text == null) { return false; } string text2 = Path.Combine(configDirectory, "jg224.bettercombat.cfg"); if (!string.Equals(text, text2, StringComparison.OrdinalIgnoreCase)) { File.Move(text, text2); return true; } string text3 = text2 + ".rename-" + Guid.NewGuid().ToString("N") + ".tmp"; File.Move(text, text3); try { File.Move(text3, text2); } catch { if (File.Exists(text3) && !File.Exists(text)) { File.Move(text3, text); } throw; } return true; } private static string ExactPath(string directory, string fileName) { return Directory.EnumerateFiles(directory, "*.cfg", SearchOption.TopDirectoryOnly).FirstOrDefault((string path) => string.Equals(Path.GetFileName(path), fileName, StringComparison.Ordinal)); } } [BepInPlugin("com.jg224.bettercombat", "Better Combat", "0.5.0")] [BepInDependency("com.jg224.modcore", "0.5.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.jg224.bettercombat"; public const string PluginName = "Better Combat"; public const string PluginVersion = "0.5.0"; public const string ModCoreGuid = "com.jg224.modcore"; public const int ProtocolVersion = 8; public static readonly ModuleId ModuleId = new ModuleId("bettercombat"); private readonly List _registrations = new List(); private Harmony _harmony; private BetterCombatRuntime _runtime; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static BetterCombatConfig Settings { get; private set; } internal static CombatPolicy Policy { get; set; } = new CombatPolicy(); internal static ICoreServices Core { get; private set; } internal static BetterCombatRuntime Runtime => Instance?._runtime; private void Awake() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Settings = new BetterCombatConfig(ConfigFileMigration.Open((BaseUnityPlugin)(object)this, Paths.ConfigPath, ((BaseUnityPlugin)this).Logger)); try { if (!ModCoreApi.IsAvailable) { throw new InvalidOperationException("ModCore did not initialize before Better Combat."); } Core = ModCoreApi.Services; RegisterWithCore(); Policy = Settings.CreatePolicy(); _runtime = new BetterCombatRuntime(Core, Settings, _registrations); _harmony = new Harmony("com.jg224.bettercombat"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Core.Modules.SetState(ModuleId, (ModuleRuntimeState)2, "Combat hooks and protocol ready."); Game.isModded = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Better Combat 0.5.0 loaded; server policy schema " + 6 + ", protocol " + 8 + ".")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Better Combat failed to initialize safely: " + ex)); if (Core != null) { Core.Modules.SetState(ModuleId, (ModuleRuntimeState)5, ex.Message); } Shutdown(); throw; } } private void Update() { BowDrawCancelInput.Poll(); DodgeHotkeyInput.Poll(); _runtime?.Tick(Time.unscaledTimeAsDouble); } private void OnDestroy() { Shutdown(); } private void RegisterWithCore() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) SemanticVersion val = default(SemanticVersion); if (!SemanticVersion.TryParse("0.5.0", ref val)) { throw new InvalidOperationException("Better Combat has invalid release version metadata."); } _registrations.Add(Core.Modules.Register(new ModuleDescriptor(ModuleId, "com.jg224.bettercombat", "Better Combat", val, 8, (ModuleSide)3, (ModuleRequirement)4, 0uL, 1, 1))); foreach (IDisposable item in Settings.RegisterMetadata(Core, ModuleId)) { _registrations.Add(item); } _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)1, "jg224.bc.", 1, Array.Empty())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)5, "jg224.bc.net.", 1, Array.Empty())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)9, "jg224.bc.", 1, Array.Empty())); CombatRulePipelineExtensions.GetDamagePipeline(Core.Rules); CombatRulePipelineExtensions.GetBlockPipeline(Core.Rules); CombatRulePipelineExtensions.GetDeathPipeline(Core.Rules); CombatRulePipelineExtensions.GetDurabilityPipeline(Core.Rules); CombatRulePipelineExtensions.GetSkillGainPipeline(Core.Rules); } private void Shutdown() { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) BowDrawCancelInput.Reset(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _runtime?.Dispose(); _runtime = null; for (int num = _registrations.Count - 1; num >= 0; num--) { try { _registrations[num].Dispose(); } catch (Exception ex) { ManualLogSource logger = ((BaseUnityPlugin)this).Logger; if (logger != null) { logger.LogWarning((object)("Registration cleanup failed: " + ex.Message)); } } } _registrations.Clear(); if (Core != null) { Core.Metrics.RemoveOwner(ModuleId); } Core = null; Settings = null; Log = null; Instance = null; } internal static void Debug(string message) { BetterCombatConfig settings = Settings; if (settings != null && settings.DebugLogging.Value) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)message); } } } } } namespace JG224.BetterCombat.Patches { internal static class BowDrawCancelRules { internal static bool CanCancel(bool policyEnabled, bool localOwner, bool gameplayInputAvailable, bool hotkeyDown, bool drawingBow, bool drawBowWeapon) { return policyEnabled && localOwner && gameplayInputAvailable && hotkeyDown && drawingBow && drawBowWeapon; } } internal sealed class BowDrawCancelLatch { private bool _armed; internal void Arm() { _armed = true; } internal bool ShouldSuppress(bool attack, bool attackHold) { if (!_armed) { return false; } if (attack || attackHold) { return true; } _armed = false; return false; } internal void Reset() { _armed = false; } } internal static class BowDrawCancelInput { private static readonly FieldRef AttackDrawTime = AccessTools.FieldRefAccess("m_attackDrawTime"); private static readonly BowDrawCancelLatch HeldAttackLatch = new BowDrawCancelLatch(); private static Player _cancelledPlayer; private static int _cancelledFrame = -1; internal static void Poll() { TryCancel(Player.m_localPlayer); } internal static bool TryCancel(Player player) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_00dd: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut val = (KeyboardShortcut)(((??)Plugin.Settings?.BowDrawCancelHotkey.Value) ?? new KeyboardShortcut((KeyCode)0, Array.Empty())); ItemData val2 = (Object.op_Implicit((Object)(object)player) ? ((Humanoid)player).GetCurrentWeapon() : null); if (!BowDrawCancelRules.CanCancel(Plugin.Policy.Enabled, ResponsiveBlock.IsLocalOwner(player), DodgeHotkeyInput.GameplayInputAvailable(), ((KeyboardShortcut)(ref val)).IsDown(), Object.op_Implicit((Object)(object)player) && ((Character)player).IsDrawingBow(), val2 != null && val2.m_shared != null && WeaponKinds.IsDrawBow(val2.m_shared.m_skillType))) { return false; } AttackDrawTime.Invoke((Humanoid)(object)player) = -1f; string drawAnimationState = val2.m_shared.m_attack.m_drawAnimationState; ZSyncAnimation zAnim = ((Character)player).GetZAnim(); if ((Object)(object)zAnim != (Object)null && !string.IsNullOrEmpty(drawAnimationState)) { zAnim.SetBool(drawAnimationState, false); } ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "bow_draws_cancelled", 1L); } _cancelledPlayer = player; _cancelledFrame = Time.frameCount; HeldAttackLatch.Arm(); return true; } internal static bool ShouldSuppressControls(Player player, bool attack, bool attackHold) { if (TryCancel(player)) { return true; } if (_cancelledPlayer != player) { return false; } bool num = HeldAttackLatch.ShouldSuppress(attack, attackHold); if (!num && !attack && !attackHold) { _cancelledPlayer = null; } return num; } internal static bool ShouldAllowInteraction(Player player) { if (_cancelledPlayer == player && _cancelledFrame == Time.frameCount) { return false; } return !TryCancel(player); } internal static void Reset() { HeldAttackLatch.Reset(); _cancelledPlayer = null; _cancelledFrame = -1; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class BowDrawCancelControlsPatch { private static void Prefix(Player __instance, ref bool attack, ref bool attackHold) { if (BowDrawCancelInput.ShouldSuppressControls(__instance, attack, attackHold)) { attack = false; attackHold = false; } } } [HarmonyPatch(typeof(Player), "Interact")] internal static class BowDrawCancelInteractionPatch { private static bool Prefix(Player __instance) { return BowDrawCancelInput.ShouldAllowInteraction(__instance); } } internal sealed class DamageState { internal bool Processed; internal string Action; internal Character Attacker; internal Character Defender; internal HitData Effective; internal float HealthBefore; internal bool StaggeringBefore; } [HarmonyPatch(typeof(Character), "RPC_Damage")] internal static class DamagePipelinePatch { private static bool Prefix(Character __instance, ref HitData hit, out DamageState __state) { //IL_0066: 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_0077: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) __state = new DamageState(); if (hit == null || !Plugin.Policy.Enabled || Plugin.Core == null) { return true; } ZNetView component = ((Component)__instance).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner()) { return true; } Character attacker = hit.GetAttacker(); string text = CombatRulesBridge.Action("damage"); DamageRuleContext val = new DamageRuleContext(text, CombatIdentity.Actor(attacker), CombatIdentity.Actor(__instance), hit, (DamageRulePhase)1, CombatIdentity.Context(attacker, __instance), CombatRulesBridge.Now, true); DamageRuleDecision val2 = DamageRuleDecision.From(hit); PipelineEvaluation obj = CombatRulePipelineExtensions.GetDamagePipeline(Plugin.Core.Rules).Evaluate(val, val2); DamageRuleDecision val3 = obj.Decision ?? val2; if (obj.Vetoed || !val3.ApplyDamage) { Plugin.Core.Metrics.Increment(Plugin.ModuleId, "damage_vetoed", 1L); return false; } hit = val3.EffectiveHit; __state.Processed = true; __state.Action = text; __state.Attacker = attacker; __state.Defender = __instance; __state.Effective = hit.Clone(); __state.HealthBefore = Mathf.Max(0f, __instance.GetHealth()); __state.StaggeringBefore = __instance.IsStaggering(); return true; } private static void Postfix(DamageState __state) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: 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_00d7: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (__state != null && __state.Processed && Object.op_Implicit((Object)(object)__state.Defender)) { float num = Mathf.Max(0f, __state.Defender.GetHealth()); bool flag = !__state.StaggeringBefore && __state.Defender.IsStaggering(); bool flag2 = __state.Effective.m_dodgeable && __state.Defender.IsDodgeInvincible(); Plugin.Core.Events.Publish(Plugin.ModuleId, new CombatHitResolvedEvent(__state.Action, CombatActorSnapshot.From(CombatIdentity.Actor(__state.Attacker)), CombatActorSnapshot.From(CombatIdentity.Actor(__state.Defender)), new CombatHitSnapshot(__state.Effective), CombatIdentity.Context(__state.Attacker, __state.Defender), __state.HealthBefore, num, num == __state.HealthBefore && !flag2, flag2, flag, CombatRulesBridge.Now, true)); if (num < __state.HealthBefore) { Plugin.Runtime?.ObserveCombat(__state.Attacker, __state.Defender); Plugin.Core.Metrics.Increment(Plugin.ModuleId, "hits_resolved", 1L); } } } } [HarmonyPatch(typeof(Character), "CheckDeath")] internal static class DeathPipelinePatch { private static bool Prefix(Character __instance) { //IL_0057: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)__instance) || __instance.GetHealth() > 0f || Plugin.Core == null) { return true; } ZNetView component = ((Component)__instance).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner()) { return true; } DeathRuleContext val = new DeathRuleContext(CombatRulesBridge.Action("death"), CombatIdentity.Actor(__instance), CombatActor.None, (HitData)null, (CombatContextFlags)(CombatIdentity.Context(__instance) | 0x80), CombatRulesBridge.Now, true); DeathRuleDecision val2 = new DeathRuleDecision(true, (CombatPolicyOverride)0, (CombatPolicyOverride)0, (CombatPolicyOverride)0, ""); PipelineEvaluation obj = CombatRulePipelineExtensions.GetDeathPipeline(Plugin.Core.Rules).Evaluate(val, val2); DeathRuleDecision val3 = obj.Decision ?? val2; if (!obj.Vetoed && val3.AllowDeath) { return true; } __instance.SetHealth(1f); Plugin.Core.Metrics.Increment(Plugin.ModuleId, "deaths_vetoed", 1L); return false; } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] internal static class SkillGainPipelinePatch { private static readonly FieldRef PlayerRef = AccessTools.FieldRefAccess("m_player"); private static bool Prefix(Skills __instance, SkillType skillType, ref float factor) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (Plugin.Core == null || factor < 0f) { return true; } Player val = PlayerRef.Invoke(__instance); SkillGainRuleContext val2 = new SkillGainRuleContext(CombatRulesBridge.Action("skill"), CombatIdentity.Actor((Character)(object)val), skillType, factor, CombatIdentity.Context((Character)(object)val), CombatRulesBridge.Now, true); SkillGainRuleDecision val3 = new SkillGainRuleDecision(factor, true, ""); PipelineEvaluation obj = CombatRulePipelineExtensions.GetSkillGainPipeline(Plugin.Core.Rules).Evaluate(val2, val3); SkillGainRuleDecision val4 = obj.Decision ?? val3; if (obj.Vetoed || !val4.Apply) { return false; } factor = val4.Amount; return true; } } internal static class DefenseRules { internal static bool CanPerfectBlock(bool ranged) { return !ranged; } internal static bool ShouldCancelIntoBlock(BlockCancelMode mode, bool newBlockPress, bool isBlocking, bool inAttack, bool committed) { if (!isBlocking || !inAttack) { return false; } if (mode == BlockCancelMode.Full) { return newBlockPress; } return mode == BlockCancelMode.RecoveryOnly && committed && newBlockPress; } } internal static class ResponsiveBlock { private static readonly FieldRef RawBlocking = AccessTools.FieldRefAccess("m_blocking"); private static readonly FieldRef CurrentAttack = AccessTools.FieldRefAccess("m_currentAttack"); private static readonly FieldRef AttackAborted = AccessTools.FieldRefAccess("m_abortAttack"); private static readonly FieldRef QueuedPrimary = AccessTools.FieldRefAccess("m_queuedAttackTimer"); private static readonly FieldRef QueuedSecondary = AccessTools.FieldRefAccess("m_queuedSecondAttackTimer"); private static readonly HashSet CommittedAttacks = new HashSet(); internal static bool IsRawBlocking(Player player) { if (Object.op_Implicit((Object)(object)player)) { return RawBlocking.Invoke((Character)(object)player); } return false; } internal static void SetRawBlocking(Player player, bool value) { if (Object.op_Implicit((Object)(object)player)) { RawBlocking.Invoke((Character)(object)player) = value; } } internal static bool IsLocalOwner(Player player) { if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } ZNetView component = ((Component)player).GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.IsValid()) { return component.IsOwner(); } return true; } internal static bool CanBeginBlock(Player player) { if (Object.op_Implicit((Object)(object)player) && !((Character)player).IsDead() && !((Character)player).InDodge() && !((Character)player).InPlaceMode() && !((Character)player).IsEncumbered() && !((Character)player).InMinorAction()) { return !((Character)player).IsStaggering(); } return false; } internal static void ApplyInput(Player player, bool wasBlocking) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)player)) { return; } bool flag = IsRawBlocking(player); if (!flag || !Plugin.Policy.Enabled || !IsLocalOwner(player) || !CanBeginBlock(player)) { return; } bool flag2 = ((Character)player).InAttack(); Attack val = (flag2 ? CurrentAttack.Invoke((Humanoid)(object)player) : null); bool newBlockPress = !wasBlocking; bool committed = val != null && CommittedAttacks.Contains(val); bool flag3 = val != null && DefenseRules.ShouldCancelIntoBlock(Plugin.Policy.BlockCancel, newBlockPress, flag, flag2, committed); if (flag2 && !flag3) { return; } if (Plugin.Policy.InstantBlockFacing) { player.FaceLookDirection(); } if (flag3) { val.Abort(); QueuedPrimary.Invoke(player) = 0f; QueuedSecondary.Invoke(player) = 0f; ZSyncAnimation zAnim = ((Character)player).GetZAnim(); if ((Object)(object)zAnim != (Object)null) { zAnim.SetTrigger("attack_abort"); } ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "attacks_cancelled_into_block", 1L); } } } internal static bool IsCancelling(Player player) { if (!Plugin.Policy.Enabled || Plugin.Policy.BlockCancel == BlockCancelMode.Off || !IsLocalOwner(player) || !IsRawBlocking(player)) { return false; } Attack val = CurrentAttack.Invoke((Humanoid)(object)player); if (val != null) { return AttackAborted.Invoke(val); } return false; } internal static bool SuppressAttackWhileBlocking(Player player) { if (Plugin.Policy.Enabled && Plugin.Policy.BlockCancel != BlockCancelMode.Off && IsLocalOwner(player) && IsRawBlocking(player)) { return CanBeginBlock(player); } return false; } internal static void MarkCommitted(Attack attack, Character character) { if (attack != null) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && IsLocalOwner(val)) { CommittedAttacks.Add(attack); } } } internal static void ClearAttack(Attack attack) { if (attack != null) { CommittedAttacks.Remove(attack); } } internal static void Clear() { CommittedAttacks.Clear(); } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class BlockInputPatch { private static void Prefix(Player __instance, out bool __state) { __state = ResponsiveBlock.IsRawBlocking(__instance); } private static void Postfix(Player __instance, bool __state) { ResponsiveBlock.ApplyInput(__instance, __state); } } [HarmonyPatch(typeof(Player), "InAttack")] internal static class BlockCancelAttackStatePatch { private static void Postfix(Player __instance, ref bool __result) { if (__result && ResponsiveBlock.IsCancelling(__instance)) { __result = false; } } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] internal static class BlockPriorityPatch { private static bool Prefix(Humanoid __instance, ref bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val == null || !ResponsiveBlock.SuppressAttackWhileBlocking(val)) { return true; } __result = false; return false; } } internal sealed class BlockState { internal bool Changed; internal bool Vetoed; internal float Timer; internal float Drain; internal float PerfectDrain; internal float StaminaBefore; internal float DurabilityBefore = -1f; internal float Incoming; internal bool Perfect; internal string Action; internal ItemData Item; internal Character Attacker; internal Humanoid Defender; } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class BlockAttackPatch { private static readonly FieldRef BlockTimer = AccessTools.FieldRefAccess("m_blockTimer"); private static bool Prefix(Humanoid __instance, HitData hit, Character attacker, ref bool __result, out BlockState __state) { //IL_016a: 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_017b: Expected O, but got Unknown //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Invalid comparison between Unknown and I4 //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Invalid comparison between Unknown and I4 __state = new BlockState(); if (!Plugin.Policy.Enabled || hit == null || Plugin.Core == null) { return true; } __state.Changed = true; __state.Defender = __instance; __state.Attacker = attacker; __state.Timer = BlockTimer.Invoke(__instance); __state.Drain = __instance.m_blockStaminaDrain; __state.PerfectDrain = __instance.m_perfectBlockStaminaDrain; BlockState obj = __state; Player val = (Player)(object)((__instance is Player) ? __instance : null); obj.StaminaBefore = ((val != null) ? val.GetStamina() : 0f); __state.Incoming = hit.GetTotalDamage(); __state.Perfect = DefenseRules.CanPerfectBlock(hit.m_ranged) && __state.Timer >= 0f && __state.Timer < Plugin.Policy.PerfectBlockWindow; __state.Action = CombatRulesBridge.Action("block"); __state.Item = Traverse.Create((object)__instance).Method("GetCurrentBlocker", Array.Empty()).GetValue(); if (__state.Item != null) { __state.DurabilityBefore = __state.Item.m_durability; } BlockRuleContext val2 = new BlockRuleContext(__state.Action, CombatIdentity.Actor(attacker), CombatIdentity.Actor((Character)(object)__instance), CombatRulesBridge.Item(__state.Item), hit, (BlockRulePhase)1, __state.Perfect, Mathf.Max(0f, __state.Timer), 0f, CombatIdentity.Context(attacker, (Character)(object)__instance), CombatRulesBridge.Now, true); BlockRuleDecision val3 = new BlockRuleDecision(true, (CombatPolicyOverride)0, 1f, __state.Perfect ? Plugin.Policy.PerfectBlockStaminaMultiplier : Plugin.Policy.BlockStaminaMultiplier, 1f, true, ""); PipelineEvaluation obj2 = CombatRulePipelineExtensions.GetBlockPipeline(Plugin.Core.Rules).Evaluate(val2, val3); BlockRuleDecision val4 = obj2.Decision ?? val3; if (obj2.Vetoed || !val4.AllowBlock) { __state.Vetoed = true; __result = false; return false; } if ((int)val4.PerfectBlock == 1) { __state.Perfect = DefenseRules.CanPerfectBlock(hit.m_ranged); } if ((int)val4.PerfectBlock == 2) { __state.Perfect = false; } BlockTimer.Invoke(__instance) = (__state.Perfect ? 0f : Mathf.Max(0.251f, __state.Timer)); __instance.m_blockStaminaDrain *= val4.StaminaCostMultiplier; __instance.m_perfectBlockStaminaDrain *= val4.StaminaCostMultiplier; return true; } private static void Postfix(Humanoid __instance, HitData hit, bool __result, BlockState __state) { //IL_0074: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown if (__state == null || !__state.Changed) { return; } BlockTimer.Invoke(__instance) = __state.Timer; __instance.m_blockStaminaDrain = __state.Drain; __instance.m_perfectBlockStaminaDrain = __state.PerfectDrain; if (!__state.Vetoed) { CombatRulesBridge.ApplyDurability((Character)(object)__instance, __state.Item, __state.DurabilityBefore, (DurabilityUseKind)2, __state.Action); float num = ((hit != null) ? hit.GetTotalDamage() : __state.Incoming); IGameEventBus events = Plugin.Core.Events; ModuleId moduleId = Plugin.ModuleId; string action = __state.Action; CombatActorSnapshot obj = CombatActorSnapshot.From(CombatIdentity.Actor(__state.Attacker)); CombatActorSnapshot obj2 = CombatActorSnapshot.From(CombatIdentity.Actor((Character)(object)__instance)); CombatItemSnapshot obj3 = CombatItemSnapshot.From(CombatRulesBridge.Item(__state.Item)); bool perfect = __state.Perfect; float num2 = Mathf.Max(0f, __state.Incoming); float num3 = Mathf.Max(0f, __state.Incoming - num); float staminaBefore = __state.StaminaBefore; Player val = (Player)(object)((__instance is Player) ? __instance : null); events.Publish(moduleId, new CombatBlockResolvedEvent(action, obj, obj2, obj3, perfect, num2, num3, Mathf.Max(0f, staminaBefore - ((val != null) ? val.GetStamina() : 0f)), CombatIdentity.Context(__state.Attacker, (Character)(object)__instance), CombatRulesBridge.Now, true)); if (__result) { Plugin.Runtime?.ObserveCombat(__state.Attacker, (Character)(object)__instance); } if (__result && __state.Perfect) { TrainingFeedback.Show(__instance, "Perfect block ยท " + Mathf.RoundToInt(__state.Timer * 1000f) + " ms"); } } } } [HarmonyPatch(typeof(Player), "GetDodgeStaminaUse")] internal static class DodgeStaminaPatch { private static void Postfix(ref float __result) { if (Plugin.Policy.Enabled) { __result = Mathf.Max(0f, __result * Plugin.Policy.DodgeStaminaMultiplier); } } } [HarmonyPatch(typeof(Player), "RPC_HitWhileDodging")] internal static class PerfectDodgePatch { private static void Prefix(Player __instance, out float __state) { __state = __instance.m_perfectDodgeStaminaReturnMultiplier; if (Plugin.Policy.Enabled) { __instance.m_perfectDodgeStaminaReturnMultiplier *= Plugin.Policy.PerfectDodgeRefundMultiplier; } } private static void Postfix(Player __instance, float __state) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown __instance.m_perfectDodgeStaminaReturnMultiplier = __state; TrainingFeedback.Show((Humanoid)(object)__instance, "Perfect dodge"); if (Plugin.Core != null) { Plugin.Core.Events.Publish(Plugin.ModuleId, new CombatDodgeResolvedEvent(CombatRulesBridge.Action("dodge"), CombatActorSnapshot.From(CombatIdentity.Actor((Character)(object)__instance)), CombatActorSnapshot.None, true, 0f, 0f, CombatIdentity.Context((Character)(object)__instance), CombatRulesBridge.Now, true)); } } } internal static class DodgeDirection { internal static bool HasDirection(Vector3 direction) { return ((Vector3)(ref direction)).sqrMagnitude >= 0.01f; } internal static Vector3 ResolveWorld(Vector3 currentMoveDirection, Vector3 lookDirection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) currentMoveDirection.y = 0f; if (HasDirection(currentMoveDirection)) { return ((Vector3)(ref currentMoveDirection)).normalized; } lookDirection.y = 0f; if (HasDirection(lookDirection)) { return -((Vector3)(ref lookDirection)).normalized; } return Vector3.back; } } internal static class DodgePriorityRules { internal static bool IsNativeRequest(bool dodge, bool jump, bool blockPressed, bool blockHeld, bool alreadyBlocking) { if (!dodge) { if (jump) { return blockPressed || blockHeld || alreadyBlocking; } return false; } return true; } } internal sealed class DodgeHotkeyState { internal bool Requested; internal float QueuedBefore; internal double CapturedAt; } internal sealed class DodgePressBuffer { private readonly double _retentionSeconds; private bool _wasHeld; private bool _pending; private double _capturedAt; internal DodgePressBuffer(double retentionSeconds) { _retentionSeconds = Math.Max(0.0, retentionSeconds); } internal void Poll(bool held, bool inputAvailable, double now) { if (!inputAvailable) { _wasHeld = held; _pending = false; return; } if (held && !_wasHeld) { _pending = true; _capturedAt = now; } _wasHeld = held; if (_pending && now - _capturedAt > _retentionSeconds) { _pending = false; } } internal bool TryConsume(double now, out double capturedAt) { capturedAt = _capturedAt; if (!_pending || now - _capturedAt > _retentionSeconds) { _pending = false; return false; } _pending = false; return true; } internal void Discard() { _pending = false; } internal void Reset() { _wasHeld = false; _pending = false; _capturedAt = 0.0; } } internal static class DodgeHotkeyInput { private const double PressRetentionSeconds = 0.15; private static readonly FieldRef QueuedDodge = AccessTools.FieldRefAccess("m_queuedDodgeTimer"); private static readonly FieldRef MoveDirection = AccessTools.FieldRefAccess("m_moveDir"); private static readonly MethodInfo NativeDodge = AccessTools.DeclaredMethod(typeof(Player), "Dodge", new Type[1] { typeof(Vector3) }, (Type[])null); private static readonly DodgePressBuffer PressBuffer = new DodgePressBuffer(0.15); private static KeyboardShortcut _trackedShortcut = new KeyboardShortcut((KeyCode)0, Array.Empty()); internal static bool GameplayInputAvailable() { if (!Console.IsVisible() && !Menu.IsVisible() && !TextInput.IsVisible() && !InventoryGui.IsVisible() && !StoreGui.IsVisible() && !Minimap.IsOpen()) { if (Object.op_Implicit((Object)(object)Chat.instance)) { return !Chat.instance.HasFocus(); } return true; } return false; } internal static DodgeHotkeyState Request(Player player, bool nativeDodgePressed) { DodgeHotkeyState dodgeHotkeyState = new DodgeHotkeyState(); Poll(); if (Plugin.Settings == null || !Plugin.Policy.Enabled || !ResponsiveBlock.IsLocalOwner(player) || !GameplayInputAvailable()) { return dodgeHotkeyState; } double unscaledTimeAsDouble = Time.unscaledTimeAsDouble; if (!PressBuffer.TryConsume(unscaledTimeAsDouble, out var capturedAt) && !nativeDodgePressed) { return dodgeHotkeyState; } if (nativeDodgePressed) { capturedAt = unscaledTimeAsDouble; } dodgeHotkeyState.Requested = true; dodgeHotkeyState.QueuedBefore = QueuedDodge.Invoke(player); dodgeHotkeyState.CapturedAt = capturedAt; DodgePriority.CancelCompetingActions(player); DodgePriority.Mark(player); return dodgeHotkeyState; } internal static void Complete(Player player, DodgeHotkeyState state) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) if (state == null || !state.Requested || QueuedDodge.Invoke(player) > state.QueuedBefore) { return; } Vector3 val = DodgeDirection.ResolveWorld(MoveDirection.Invoke((Character)(object)player), ((Character)player).GetLookDir()); try { DodgePriority.CancelCompetingActions(player); DodgePriority.EnterLockOverride(player); try { NativeDodge.Invoke(player, new object[1] { val }); } finally { DodgePriority.ExitLockOverride(); } if (QueuedDodge.Invoke(player) > state.QueuedBefore) { ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "hotkey_dodges_queued", 1L); } } Plugin.Debug("Dodge hotkey consumed after " + ((Time.unscaledTimeAsDouble - state.CapturedAt) * 1000.0).ToString("0.0") + " ms."); } catch (TargetInvocationException ex) { Plugin.Log.LogError((object)("Native dodge invocation failed: " + (ex.InnerException ?? ex))); } } internal static void Poll() { //IL_0015: 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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) BetterCombatConfig settings = Plugin.Settings; if (settings == null) { Clear(); return; } KeyboardShortcut value = settings.DodgeHotkey.Value; object obj = value; if (!((object)Unsafe.As(ref _trackedShortcut)/*cast due to .constrained prefix*/).Equals(obj)) { _trackedShortcut = value; PressBuffer.Reset(); } bool held = IsShortcutHeld(value); bool inputAvailable = Plugin.Policy.Enabled && Object.op_Implicit((Object)(object)Player.m_localPlayer) && GameplayInputAvailable(); PressBuffer.Poll(held, inputAvailable, Time.unscaledTimeAsDouble); } internal static void Clear() { //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) _trackedShortcut = new KeyboardShortcut((KeyCode)0, Array.Empty()); PressBuffer.Reset(); DodgePriority.Clear(); } private static bool IsShortcutHeld(KeyboardShortcut shortcut) { //IL_0002: 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_002a: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey, true)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (!ZInput.GetKey(modifier, true)) { return false; } } return true; } } internal static class DodgePriority { private static readonly FieldRef CurrentAttack = AccessTools.FieldRefAccess("m_currentAttack"); private static readonly FieldRef QueuedPrimary = AccessTools.FieldRefAccess("m_queuedAttackTimer"); private static readonly FieldRef QueuedSecondary = AccessTools.FieldRefAccess("m_queuedSecondAttackTimer"); private static readonly FieldRef QueuedDodge = AccessTools.FieldRefAccess("m_queuedDodgeTimer"); private static readonly MethodInfo ClearActionQueue = AccessTools.Method(typeof(Player), "ClearActionQueue", (Type[])null, (Type[])null); private static readonly MethodInfo StopEmote = AccessTools.Method(typeof(Player), "StopEmote", (Type[])null, (Type[])null); [ThreadStatic] private static Player _lockOverridePlayer; [ThreadStatic] private static int _lockOverrideDepth; private static Player _priorityPlayer; internal static void Mark(Player player) { _priorityPlayer = player; } internal static bool IsMarked(Player player) { if (Object.op_Implicit((Object)(object)player)) { return _priorityPlayer == player; } return false; } internal static void CancelCompetingActions(Player player) { if (Object.op_Implicit((Object)(object)player)) { Attack val = CurrentAttack.Invoke((Humanoid)(object)player); if (val != null) { val.Abort(); } QueuedPrimary.Invoke(player) = 0f; QueuedSecondary.Invoke(player) = 0f; ClearActionQueue.Invoke(player, null); StopEmote.Invoke(player, null); ResponsiveBlock.SetRawBlocking(player, value: false); ZSyncAnimation zAnim = ((Character)player).GetZAnim(); if (val != null && (Object)(object)zAnim != (Object)null) { zAnim.SetTrigger("attack_abort"); } } } internal static bool BeginUpdate(Player player) { if (!IsMarked(player)) { return false; } CancelCompetingActions(player); EnterLockOverride(player); return true; } internal static void EndUpdate(Player player, bool active) { if (active) { ExitLockOverride(); if (!Object.op_Implicit((Object)(object)player) || QueuedDodge.Invoke(player) <= 0f) { _priorityPlayer = null; } } } internal static void EnterLockOverride(Player player) { _lockOverridePlayer = player; _lockOverrideDepth++; } internal static void ExitLockOverride() { if (_lockOverrideDepth > 0) { _lockOverrideDepth--; } if (_lockOverrideDepth == 0) { _lockOverridePlayer = null; } } internal static bool Overrides(Character character) { if (_lockOverrideDepth > 0 && Object.op_Implicit((Object)(object)character)) { return (object)_lockOverridePlayer == character; } return false; } internal static void Clear() { _priorityPlayer = null; _lockOverridePlayer = null; _lockOverrideDepth = 0; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class DodgeHotkeyPatch { private static void Prefix(Player __instance, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool dodge, out DodgeHotkeyState __state) { bool nativeDodgePressed = DodgePriorityRules.IsNativeRequest(dodge, jump, block, blockHold, ResponsiveBlock.IsRawBlocking(__instance)); __state = DodgeHotkeyInput.Request(__instance, nativeDodgePressed); if (__state != null && __state.Requested) { attack = false; attackHold = false; secondaryAttack = false; secondaryAttackHold = false; block = false; blockHold = false; jump = false; dodge = false; } } private static void Postfix(Player __instance, DodgeHotkeyState __state) { DodgeHotkeyInput.Complete(__instance, __state); } } [HarmonyPatch(typeof(Player), "UpdateDodge")] internal static class PriorityDodgeUpdatePatch { private static void Prefix(Player __instance, out bool __state) { __state = DodgePriority.BeginUpdate(__instance); } private static Exception Finalizer(Player __instance, bool __state, Exception __exception) { DodgePriority.EndUpdate(__instance, __state); return __exception; } } [HarmonyPatch(typeof(Character), "IsEncumbered")] internal static class PriorityDodgeEncumbrancePatch { private static void Postfix(Character __instance, ref bool __result) { if (__result && DodgePriority.Overrides(__instance)) { __result = false; } } } [HarmonyPatch(typeof(Character), "IsStaggering")] internal static class PriorityDodgeStaggerPatch { private static void Postfix(Character __instance, ref bool __result) { if (__result && DodgePriority.Overrides(__instance)) { __result = false; } } } [HarmonyPatch(typeof(Player), "InAttack")] internal static class PriorityDodgeAttackPatch { private static void Postfix(Player __instance, ref bool __result) { if (__result && DodgePriority.Overrides((Character)(object)__instance)) { __result = false; } } } internal static class EquipmentRules { internal static bool IsOneHandedWeapon(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 return (int)type == 3; } internal static bool IsOneHandedWeapon(ItemData item) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (item != null && item.m_shared != null) { return IsOneHandedWeapon(item.m_shared.m_itemType); } return false; } internal static bool IsShield(ItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 return (int)type == 5; } internal static bool IsShield(ItemData item) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (item != null && item.m_shared != null) { return IsShield(item.m_shared.m_itemType); } return false; } internal static bool IsUsable(ItemData item) { if (item != null && item.m_shared != null) { if (item.m_shared.m_useDurability) { return item.m_durability > 0f; } return true; } return false; } internal static bool CanRestore(bool swimming, bool onGround, bool dead, bool inAttack, bool inDodge) { if ((!swimming || onGround) && !dead && !inAttack) { return !inDodge; } return false; } internal static bool IsDeliberateReplacement(ItemData current, ItemData remembered) { if (current != null) { return current != remembered; } return false; } } internal static class EquipmentAutomation { private static readonly FieldRef RightItem = AccessTools.FieldRefAccess("m_rightItem"); private static readonly FieldRef LeftItem = AccessTools.FieldRefAccess("m_leftItem"); private static ItemData _lastShield; private static ItemData _waterRight; private static ItemData _waterLeft; private static bool _restoringWaterItems; internal static void OnEquipped(Player player, ItemData item, bool equipped) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) BetterCombatConfig settings = Plugin.Settings; if (!equipped || settings == null || !Plugin.Policy.Enabled || !ResponsiveBlock.IsLocalOwner(player)) { return; } if (EquipmentRules.IsShield(item)) { _lastShield = item; } else { if (_restoringWaterItems || !settings.AutoEquipShield.Value || !EquipmentRules.IsOneHandedWeapon(item) || GetLeftItem((Humanoid)(object)player) != null) { return; } ItemData val = FindShield(((Humanoid)player).GetInventory()); if (val != null && ((Humanoid)player).EquipItem(val, true)) { ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "shields_auto_equipped", 1L); } } } } internal static void CaptureSwimmingItems(Player player, bool onlyRightHand) { BetterCombatConfig settings = Plugin.Settings; if (settings != null && settings.RestoreWeaponsAfterSwimming.Value && Plugin.Policy.Enabled && ResponsiveBlock.IsLocalOwner(player) && ((Character)player).IsSwimming() && !((Character)player).IsOnGround()) { if (_waterRight == null && GetRightItem((Humanoid)(object)player) != null) { _waterRight = GetRightItem((Humanoid)(object)player); } if (!onlyRightHand && _waterLeft == null && GetLeftItem((Humanoid)(object)player) != null) { _waterLeft = GetLeftItem((Humanoid)(object)player); } } } internal static void TryRestoreSwimmingItems(Player player) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if (!ResponsiveBlock.IsLocalOwner(player) || (_waterRight == null && _waterLeft == null)) { return; } BetterCombatConfig settings = Plugin.Settings; if (settings == null || !settings.RestoreWeaponsAfterSwimming.Value || !Plugin.Policy.Enabled) { ClearWaterItems(); } else { if (!EquipmentRules.CanRestore(((Character)player).IsSwimming(), ((Character)player).IsOnGround(), ((Character)player).IsDead(), ((Character)player).InAttack(), ((Character)player).InDodge())) { return; } ItemData rightItem = GetRightItem((Humanoid)(object)player); ItemData leftItem = GetLeftItem((Humanoid)(object)player); if (EquipmentRules.IsDeliberateReplacement(rightItem, _waterRight) || EquipmentRules.IsDeliberateReplacement(leftItem, _waterLeft)) { ClearWaterItems(); return; } Inventory inventory = ((Humanoid)player).GetInventory(); _restoringWaterItems = true; try { ItemData obj = RestoreHand(player, inventory, _waterRight, rightHand: true); ItemData val = RestoreHand(player, inventory, _waterLeft, rightHand: false); if (obj == null && val == null) { ClearWaterItems(); } } finally { _restoringWaterItems = false; } if (_waterRight == null && _waterLeft == null) { ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "swim_loadouts_restored", 1L); } } } } internal static void Clear() { _lastShield = null; ClearWaterItems(); _restoringWaterItems = false; } private static ItemData FindShield(Inventory inventory) { if (inventory == null) { return null; } if (_lastShield != null && inventory.ContainsItem(_lastShield) && EquipmentRules.IsUsable(_lastShield)) { return _lastShield; } foreach (ItemData item in inventory.GetAllItemsInGridOrder()) { if (EquipmentRules.IsShield(item) && EquipmentRules.IsUsable(item)) { return item; } } return null; } private static ItemData RestoreHand(Player player, Inventory inventory, ItemData remembered, bool rightHand) { if (remembered == null) { return null; } if (inventory == null || !inventory.ContainsItem(remembered) || !EquipmentRules.IsUsable(remembered)) { return null; } ItemData val = (rightHand ? GetRightItem((Humanoid)(object)player) : GetLeftItem((Humanoid)(object)player)); if (val == remembered) { return null; } if (val != null) { return null; } if (!((Humanoid)player).EquipItem(remembered, true)) { return remembered; } return null; } private static ItemData GetRightItem(Humanoid humanoid) { return RightItem.Invoke(humanoid); } private static ItemData GetLeftItem(Humanoid humanoid) { return LeftItem.Invoke(humanoid); } private static void ClearWaterItems() { _waterRight = null; _waterLeft = null; } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] internal static class AutoEquipShieldPatch { private static void Postfix(Humanoid __instance, ItemData item, bool __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { EquipmentAutomation.OnEquipped(val, item, __result); } } } [HarmonyPatch(typeof(Humanoid), "HideHandItems")] internal static class RememberSwimmingItemsPatch { private static void Prefix(Humanoid __instance, bool onlyRightHand) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { EquipmentAutomation.CaptureSwimmingItems(val, onlyRightHand); } } } [HarmonyPatch(typeof(Player), "Update")] internal static class RestoreSwimmingItemsPatch { private static void Postfix(Player __instance) { EquipmentAutomation.TryRestoreSwimmingItems(__instance); } } internal static class KnockbackMath { internal const float NativeForceScale = 2.5f; internal const float NativeForceDissipation = 100f; internal static float ScaleAndCap(float pushForce, float targetMultiplier, float movementModifier, float bodyMass, float durationLimit) { if (!Finite(pushForce) || !Finite(targetMultiplier) || !Finite(movementModifier) || !Finite(bodyMass) || !Finite(durationLimit)) { return 0f; } if (pushForce <= 0f || targetMultiplier <= 0f || bodyMass <= 0f || durationLimit <= 0f) { return 0f; } float num = Mathf.Clamp01(1f + movementModifier); if (num <= 0f) { return 0f; } float num2 = pushForce * targetMultiplier; float num3 = 100f * durationLimit * bodyMass / (num * 2.5f); return Mathf.Min(num2, num3); } internal static void ApplyVelocity(ref Vector3 velocity, Vector3 storedPushForce, float targetSpeed, bool dampened) { //IL_0000: 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_001a: 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_0030: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (Finite(storedPushForce.x) && Finite(storedPushForce.y) && Finite(storedPushForce.z) && Finite(targetSpeed) && !(storedPushForce == Vector3.zero) && !(targetSpeed <= 0f)) { Vector3 normalized = ((Vector3)(ref storedPushForce)).normalized; float num = Vector3.Dot(normalized, velocity); if (num < targetSpeed) { velocity += normalized * (targetSpeed - num); } if (dampened) { velocity *= 0.5f; } } } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } [HarmonyPatch(typeof(Character), "ApplyPushback", new Type[] { typeof(Vector3), typeof(float) })] internal static class ApplyPushbackPatch { private static void Prefix(Character __instance, ref float pushForce) { if (Plugin.Policy.Enabled && !(pushForce <= 0f) && Object.op_Implicit((Object)(object)__instance)) { Rigidbody component = ((Component)__instance).GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { pushForce = 0f; return; } float targetMultiplier = (__instance.IsPlayer() ? Plugin.Policy.PlayerKnockbackMultiplier : (__instance.IsBoss() ? Plugin.Policy.BossKnockbackMultiplier : Plugin.Policy.EnemyKnockbackMultiplier)); pushForce = KnockbackMath.ScaleAndCap(pushForce, targetMultiplier, __instance.GetEquipmentMovementModifier(), component.mass, Plugin.Policy.KnockbackDurationLimit); } } } [HarmonyPatch(typeof(Character), "AddPushbackForce")] internal static class AddPushbackForcePatch { private static readonly FieldRef StoredPushForce = AccessTools.FieldRefAccess("m_pushForce"); private static bool Prefix(Character __instance, ref Vector3 velocity) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Policy.Enabled) { return true; } KnockbackMath.ApplyVelocity(ref velocity, StoredPushForce.Invoke(__instance), Plugin.Policy.KnockbackSpeed, __instance.IsSwimming() || __instance.IsFlying()); return false; } } internal static class WeaponKinds { internal static bool IsBow(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)skill != 8) { return (int)skill == 14; } return true; } internal static bool IsDrawBow(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 return (int)skill == 8; } internal static bool IsSpear(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 return (int)skill == 5; } } internal static class BowTrajectoryMath { internal static float MaximumVelocity(BowTrajectoryMode mode, float nativeMaximum, float progressiveBonus) { if (mode != BowTrajectoryMode.Progressive) { return nativeMaximum; } return Mathf.Max(0f, nativeMaximum) + Mathf.Max(0f, progressiveBonus); } internal static float GravityMultiplier(BowTrajectoryMode mode, float progressiveMultiplier, float finalMultiplier) { return ((mode == BowTrajectoryMode.Progressive) ? Mathf.Max(0f, progressiveMultiplier) : 1f) * Mathf.Max(0f, finalMultiplier); } internal static Vector3 Elevate(BowTrajectoryMode mode, Vector3 direction, float elevation) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (mode != BowTrajectoryMode.Progressive) { return direction; } direction.y += elevation; if (!(((Vector3)(ref direction)).sqrMagnitude < 0.0001f)) { return ((Vector3)(ref direction)).normalized; } return Vector3.forward; } } internal static class BowZoomMath { internal static bool CanApply(bool alive, bool teleporting, bool cutscene, bool attached, bool inputAvailable, bool cameraOwnedElsewhere) { if (alive && !teleporting && !cutscene && !attached && inputAvailable) { return !cameraOwnedElsewhere; } return false; } internal static float TransitionSpeed(bool zoomingIn, float drawSpeed, float releaseSpeed) { if (!zoomingIn) { return Mathf.Max(0f, releaseSpeed); } return Mathf.Max(0f, drawSpeed); } internal static float Step(float current, float target, float speed, float unscaledDeltaTime) { float num = Mathf.Max(0f, speed); float num2 = Mathf.Max(0f, unscaledDeltaTime); return Mathf.Lerp(current, target, 1f - Mathf.Exp((0f - num) * num2)); } } internal sealed class BowTrajectoryState { internal bool Changed; internal float MaximumVelocity; } [HarmonyPatch(typeof(Attack), "FireProjectileBurst")] internal static class ProgressiveBowVelocityPatch { private static void Prefix(Attack __instance, Humanoid ___m_character, ItemData ___m_weapon, out BowTrajectoryState __state) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) __state = new BowTrajectoryState(); CombatPolicy policy = Plugin.Policy; if (policy.Enabled && policy.BowTrajectory == BowTrajectoryMode.Progressive && Object.op_Implicit((Object)(object)___m_character) && ((Character)___m_character).IsPlayer() && ___m_weapon != null && WeaponKinds.IsDrawBow(___m_weapon.m_shared.m_skillType)) { __state.Changed = true; __state.MaximumVelocity = __instance.m_projectileVel; __instance.m_projectileVel = BowTrajectoryMath.MaximumVelocity(policy.BowTrajectory, __state.MaximumVelocity, policy.ProgressiveBowVelocityBonus); } } private static Exception Finalizer(Attack __instance, BowTrajectoryState __state, Exception __exception) { if (__state != null && __state.Changed) { __instance.m_projectileVel = __state.MaximumVelocity; } return __exception; } } [HarmonyPatch(typeof(Attack), "GetProjectileSpawnPoint")] internal static class ProgressiveBowAimPatch { private static void Postfix(Humanoid ___m_character, ItemData ___m_weapon, ref Vector3 aimDir) { //IL_0030: 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_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) CombatPolicy policy = Plugin.Policy; if (policy.Enabled && policy.BowTrajectory == BowTrajectoryMode.Progressive && Object.op_Implicit((Object)(object)___m_character) && ((Character)___m_character).IsPlayer() && ___m_weapon != null && WeaponKinds.IsDrawBow(___m_weapon.m_shared.m_skillType)) { aimDir = BowTrajectoryMath.Elevate(policy.BowTrajectory, aimDir, policy.ProgressiveBowAimElevation); } } } internal sealed class SpearProjectileOwner : MonoBehaviour { internal long PlayerId; } internal static class SpearDropContext { [ThreadStatic] private static long _ownerId; internal static long OwnerId => _ownerId; internal static long Enter(long ownerId) { long ownerId2 = _ownerId; _ownerId = ownerId; return ownerId2; } internal static void Restore(long previous) { _ownerId = previous; } } internal static class SpearPickupOwnership { internal const string OwnerKey = "jg224.bc.spear.owner"; internal static bool CanAutoPickup(long ownerId, long playerId) { if (ownerId != 0L) { return ownerId == playerId; } return true; } internal static long GetOwner(ItemDrop drop) { if (!Object.op_Implicit((Object)(object)drop)) { return 0L; } ZNetView component = ((Component)drop).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid()) { return 0L; } return component.GetZDO().GetLong("jg224.bc.spear.owner", 0L); } internal static void SetOwner(ItemDrop drop, long ownerId) { if (Object.op_Implicit((Object)(object)drop) && ownerId != 0L) { ZNetView component = ((Component)drop).GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.IsValid()) { component.GetZDO().Set("jg224.bc.spear.owner", ownerId); } } } } [HarmonyPatch(typeof(Attack), "GetAttackStamina")] internal static class AttackStaminaPatch { private static void Postfix(ItemData ___m_weapon, ref float __result) { //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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Policy.Enabled && ___m_weapon != null) { SkillType skillType = ___m_weapon.m_shared.m_skillType; float num = (WeaponKinds.IsSpear(skillType) ? Plugin.Policy.SpearStaminaMultiplier : (WeaponKinds.IsBow(skillType) ? Plugin.Policy.BowStaminaMultiplier : Plugin.Policy.MeleeStaminaMultiplier)); __result = Mathf.Max(0f, __result * num); } } } [HarmonyPatch(typeof(Attack), "GetMeleeAttackDir")] internal static class MeleeAimPatch { private static void Postfix(Humanoid ___m_character, Transform originJoint, ref Vector3 attackDir) { //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) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_004d: 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_0086: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Policy.Enabled && Object.op_Implicit((Object)(object)___m_character) && ((Character)___m_character).IsPlayer() && Object.op_Implicit((Object)(object)originJoint)) { Vector3 forward = ((Component)___m_character).transform.forward; Vector3 aimDir = ___m_character.GetAimDir(originJoint.position); aimDir.x = forward.x; aimDir.z = forward.z; if (!(((Vector3)(ref aimDir)).sqrMagnitude < 0.0001f)) { ((Vector3)(ref aimDir)).Normalize(); Vector3 val = Vector3.RotateTowards(forward, aimDir, (float)Math.PI / 180f * Plugin.Policy.MeleeVerticalAngle, 10f); attackDir = ((Vector3)(ref val)).normalized; } } } } internal sealed class AttackDurabilityState { internal bool RadiusRestored; internal Character Actor; internal ItemData Item; internal float Before = -1f; internal float Radius; internal string Action; } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] internal static class MeleeTraceAndDurabilityPatch { private static void Prefix(Attack __instance, Character ___m_character, ItemData ___m_weapon, out AttackDurabilityState __state) { ResponsiveBlock.MarkCommitted(__instance, ___m_character); __state = new AttackDurabilityState { Actor = ___m_character, Item = ___m_weapon, Before = (___m_weapon?.m_durability ?? (-1f)), Radius = __instance.m_attackRayWidthCharExtra, Action = CombatRulesBridge.Action("melee") }; if (Plugin.Policy.Enabled && Object.op_Implicit((Object)(object)___m_character) && ___m_character.IsPlayer()) { __instance.m_attackRayWidthCharExtra = Mathf.Max(__instance.m_attackRayWidthCharExtra, Plugin.Policy.MeleeCharacterRadiusBonus); } } private static void Postfix(Attack __instance, AttackDurabilityState __state) { if (__state != null) { RestoreRadius(__instance, __state); CombatRulesBridge.ApplyDurability(__state.Actor, __state.Item, __state.Before, (DurabilityUseKind)1, __state.Action); } } private static Exception Finalizer(Attack __instance, AttackDurabilityState __state, Exception __exception) { RestoreRadius(__instance, __state); return __exception; } private static void RestoreRadius(Attack attack, AttackDurabilityState state) { if (state != null && !state.RadiusRestored) { attack.m_attackRayWidthCharExtra = state.Radius; state.RadiusRestored = true; } } } [HarmonyPatch(typeof(Attack), "ProjectileAttackTriggered")] internal static class ProjectileDurabilityPatch { private static void Prefix(Attack __instance, Character ___m_character, ItemData ___m_weapon, out AttackDurabilityState __state) { ResponsiveBlock.MarkCommitted(__instance, ___m_character); __state = new AttackDurabilityState { Actor = ___m_character, Item = ___m_weapon, Before = (___m_weapon?.m_durability ?? (-1f)), Action = CombatRulesBridge.Action("projectile") }; } private static void Postfix(AttackDurabilityState __state) { if (__state != null) { CombatRulesBridge.ApplyDurability(__state.Actor, __state.Item, __state.Before, (DurabilityUseKind)3, __state.Action); } } } [HarmonyPatch(typeof(Attack), "DoAreaAttack")] internal static class AreaAttackCommitPatch { private static void Prefix(Attack __instance, Character ___m_character) { ResponsiveBlock.MarkCommitted(__instance, ___m_character); } } [HarmonyPatch(typeof(Attack), "DoNonAttack")] internal static class NonAttackCommitPatch { private static void Prefix(Attack __instance, Character ___m_character) { ResponsiveBlock.MarkCommitted(__instance, ___m_character); } } [HarmonyPatch(typeof(Attack), "Stop")] internal static class AttackCommitCleanupPatch { private static void Postfix(Attack __instance) { ResponsiveBlock.ClearAttack(__instance); } } [HarmonyPatch(typeof(Projectile), "Setup")] internal static class ProjectileSetupPatch { private static void Prefix(Projectile __instance, ref Vector3 velocity, HitData hitData) { //IL_0011: 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Policy.Enabled && hitData != null) { if (WeaponKinds.IsBow(hitData.m_skill)) { velocity *= Plugin.Policy.BowVelocityMultiplier; __instance.m_gravity *= (WeaponKinds.IsDrawBow(hitData.m_skill) ? BowTrajectoryMath.GravityMultiplier(Plugin.Policy.BowTrajectory, Plugin.Policy.ProgressiveBowGravityMultiplier, Plugin.Policy.BowGravityMultiplier) : Plugin.Policy.BowGravityMultiplier); } else if (WeaponKinds.IsSpear(hitData.m_skill)) { velocity *= Plugin.Policy.SpearVelocityMultiplier; __instance.m_gravity *= Plugin.Policy.SpearGravityMultiplier; } } } private static void Postfix(Projectile __instance, Character owner, HitData hitData, ItemData item, ItemData ammo) { //IL_0031: 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_00ec: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Policy.Enabled || hitData == null) { return; } ZNetView component = ((Component)__instance).GetComponent(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid() || !component.IsOwner()) { return; } if (WeaponKinds.IsSpear(hitData.m_skill)) { if (__instance.m_respawnItemOnHit && __instance.m_spawnItem == null && item != null) { __instance.m_spawnItem = item; } if (__instance.m_spawnItem == null) { return; } Player val = (Player)(object)((owner is Player) ? owner : null); if (val != null) { SpearProjectileOwner spearProjectileOwner = ((Component)__instance).GetComponent(); if (!Object.op_Implicit((Object)(object)spearProjectileOwner)) { spearProjectileOwner = ((Component)__instance).gameObject.AddComponent(); } spearProjectileOwner.PlayerId = val.GetPlayerID(); } } else if (WeaponKinds.IsBow(hitData.m_skill) && ammo != null && !__instance.m_respawnItemOnHit && __instance.m_spawnItem == null && !(Plugin.Policy.ArrowRecoveryChance <= 0f) && Random.value <= Plugin.Policy.ArrowRecoveryChance) { __instance.m_spawnItem = ammo; ICoreServices core = Plugin.Core; if (core != null) { core.Metrics.Increment(Plugin.ModuleId, "projectiles_marked_recoverable", 1L); } } } } [HarmonyPatch(typeof(Projectile), "SpawnOnHit")] internal static class SpearSpawnScopePatch { private static void Prefix(Projectile __instance, out long __state) { __state = SpearDropContext.OwnerId; SpearProjectileOwner component = ((Component)__instance).GetComponent(); if (Plugin.Policy.Enabled && Object.op_Implicit((Object)(object)component) && component.PlayerId != 0L && __instance.m_spawnItem != null) { SpearDropContext.Enter(component.PlayerId); } } private static Exception Finalizer(Exception __exception, long __state) { SpearDropContext.Restore(__state); return __exception; } } [HarmonyPatch(typeof(ItemDrop), "DropItem", new Type[] { typeof(ItemData), typeof(int), typeof(Vector3), typeof(Quaternion) })] internal static class SpearDropOwnerPatch { private static void Postfix(ItemDrop __result) { SpearPickupOwnership.SetOwner(__result, SpearDropContext.OwnerId); } } [HarmonyPatch(typeof(Player), "AutoPickup")] internal static class SpearAutoPickupPatch { private static readonly FieldInfo InstancesField = AccessTools.Field(typeof(ItemDrop), "s_instances"); private static void Prefix(Player __instance, out List __state) { __state = null; if (!Plugin.Policy.Enabled || !Object.op_Implicit((Object)(object)__instance) || !(InstancesField?.GetValue(null) is List list)) { return; } long playerID = __instance.GetPlayerID(); foreach (ItemDrop item in list) { if (Object.op_Implicit((Object)(object)item) && item.m_autoPickup && !SpearPickupOwnership.CanAutoPickup(SpearPickupOwnership.GetOwner(item), playerID)) { if (__state == null) { __state = new List(); } item.m_autoPickup = false; __state.Add(item); } } } private static void Finalizer(List __state) { if (__state == null) { return; } foreach (ItemDrop item in __state) { if (Object.op_Implicit((Object)(object)item)) { item.m_autoPickup = true; } } } } [HarmonyPatch(typeof(GameCamera), "UpdateCamera")] internal static class BowZoomPatch { private static readonly FieldRef MainCamera = AccessTools.FieldRefAccess("m_camera"); private static float _currentFov = -1f; private static GameCamera _owner; [HarmonyBefore(new string[] { "jg224.Monocular" })] private static void Postfix(GameCamera __instance) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) BetterCombatConfig settings = Plugin.Settings; if (settings == null || settings.BowZoom.Value == BowZoomMode.Off || !Object.op_Implicit((Object)(object)Player.m_localPlayer)) { _currentFov = -1f; return; } Player localPlayer = Player.m_localPlayer; Camera val = MainCamera.Invoke(__instance); bool cameraOwnedElsewhere = Object.op_Implicit((Object)(object)val) && Mathf.Abs(val.fieldOfView - __instance.m_fov) > 0.1f; if (!BowZoomMath.CanApply(!((Character)localPlayer).IsDead(), ((Character)localPlayer).IsTeleporting(), ((Character)localPlayer).InCutscene(), ((Character)localPlayer).IsAttached(), DodgeHotkeyInput.GameplayInputAvailable(), cameraOwnedElsewhere)) { _currentFov = -1f; _owner = null; return; } if ((Object)(object)_owner != (Object)(object)__instance) { _owner = __instance; _currentFov = -1f; } ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); int num; float num2; if (currentWeapon != null && WeaponKinds.IsBow(currentWeapon.m_shared.m_skillType)) { if (settings.BowZoom.Value != BowZoomMode.WhileEquipped) { num = (((Character)localPlayer).IsDrawingBow() ? 1 : 0); if (num == 0) { goto IL_00fa; } } else { num = 1; } num2 = settings.BowZoomFov.Value; goto IL_010d; } num = 0; goto IL_00fa; IL_00fa: num2 = __instance.m_fov; goto IL_010d; IL_010d: float target = num2; if (_currentFov < 0f) { _currentFov = __instance.m_fov; } float speed = BowZoomMath.TransitionSpeed((byte)num != 0, settings.BowZoomDrawSpeed.Value, settings.BowZoomReleaseSpeed.Value); _currentFov = BowZoomMath.Step(_currentFov, target, speed, Time.unscaledDeltaTime); if (Object.op_Implicit((Object)(object)val)) { val.fieldOfView = _currentFov; } if (Object.op_Implicit((Object)(object)__instance.m_skyCamera)) { __instance.m_skyCamera.fieldOfView = _currentFov; } } } internal static class TrainingFeedback { private static float _next; internal static void Show(Humanoid actor, string text) { BetterCombatConfig settings = Plugin.Settings; if (settings != null && settings.TrainingFeedback.Value && Object.op_Implicit((Object)(object)actor) && !((Object)(object)actor != (Object)(object)Player.m_localPlayer) && DodgeHotkeyInput.GameplayInputAvailable() && !(Time.unscaledTime < _next)) { _next = Time.unscaledTime + 0.7f; ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } } } }