Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterCombat v0.5.0
JG224.BetterCombat.dll
Decompiled 8 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.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<Tuple<ConfigEntryBase, ConfigScope>> _entries = new List<Tuple<ConfigEntryBase, ConfigScope>>(); internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<CombatPreset> Preset { get; } internal ConfigEntry<bool> TrainingFeedback { get; } internal ConfigEntry<float> PerfectBlockWindow { get; } internal ConfigEntry<float> BlockStaminaMultiplier { get; } internal ConfigEntry<float> PerfectBlockStaminaMultiplier { get; } internal ConfigEntry<BlockCancelMode> BlockCancel { get; } internal ConfigEntry<bool> InstantBlockFacing { get; } internal ConfigEntry<float> PlayerKnockbackMultiplier { get; } internal ConfigEntry<float> EnemyKnockbackMultiplier { get; } internal ConfigEntry<float> BossKnockbackMultiplier { get; } internal ConfigEntry<float> KnockbackSpeed { get; } internal ConfigEntry<float> KnockbackDurationLimit { get; } internal ConfigEntry<float> DodgeStaminaMultiplier { get; } internal ConfigEntry<float> PerfectDodgeRefundMultiplier { get; } internal ConfigEntry<float> MeleeStaminaMultiplier { get; } internal ConfigEntry<float> BowStaminaMultiplier { get; } internal ConfigEntry<float> SpearStaminaMultiplier { get; } internal ConfigEntry<float> MeleeVerticalAngle { get; } internal ConfigEntry<float> MeleeCharacterRadiusBonus { get; } internal ConfigEntry<BowTrajectoryMode> BowTrajectory { get; } internal ConfigEntry<float> ProgressiveBowVelocityBonus { get; } internal ConfigEntry<float> ProgressiveBowGravityMultiplier { get; } internal ConfigEntry<float> ProgressiveBowAimElevation { get; } internal ConfigEntry<float> BowVelocityMultiplier { get; } internal ConfigEntry<float> BowGravityMultiplier { get; } internal ConfigEntry<float> SpearVelocityMultiplier { get; } internal ConfigEntry<float> SpearGravityMultiplier { get; } internal ConfigEntry<float> ArrowRecoveryChance { get; } internal ConfigEntry<float> CombatClearDelaySeconds { get; } internal ConfigEntry<KeyboardShortcut> DodgeHotkey { get; } internal ConfigEntry<KeyboardShortcut> BowDrawCancelHotkey { get; } internal ConfigEntry<bool> AutoEquipShield { get; } internal ConfigEntry<bool> RestoreWeaponsAfterSwimming { get; } internal ConfigEntry<BowZoomMode> BowZoom { get; } internal ConfigEntry<float> BowZoomFov { get; } internal ConfigEntry<float> BowZoomDrawSpeed { get; } internal ConfigEntry<float> BowZoomReleaseSpeed { get; } internal ConfigEntry<bool> 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<KeyboardShortcut>("Dodge", "DodgeHotkey", new KeyboardShortcut((KeyCode)308, Array.Empty<KeyCode>()), "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<KeyboardShortcut>("Archery", "BowDrawCancelHotkey", new KeyboardShortcut((KeyCode)101, Array.Empty<KeyCode>()), "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<int> val = _file.Bind<int>("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<IDisposable> 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<ConfigEntryBase, ConfigScope> entry2 in _entries) { ConfigEntryBase entry = entry2.Item1; yield return services.Configuration.Register(new ConfigSettingDescriptor(owner, entry.Definition.Section, entry.Definition.Key, entry2.Item2, (Func<string>)(() => 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<T> Server<T>(string section, string key, T value, string description) { return Add<T>(_file.Bind<T>(section, key, value, description), (ConfigScope)2); } private ConfigEntry<float> 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<float>(_file.Bind<float>(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())), (ConfigScope)2); } private ConfigEntry<T> Local<T>(string section, string key, T value, string description) { return Add<T>(_file.Bind<T>(section, key, value, description), (ConfigScope)1); } private ConfigEntry<float> 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<float>(_file.Bind<float>(section, key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>())), (ConfigScope)1); } private ConfigEntry<T> Developer<T>(string section, string key, T value, string description) { return Add<T>(_file.Bind<T>(section, key, value, description), (ConfigScope)5); } private ConfigEntry<T> Add<T>(ConfigEntry<T> entry, ConfigScope scope) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) _entries.Add(Tuple.Create<ConfigEntryBase, ConfigScope>((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<string>(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<IDisposable> 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<LifecycleEvent>)delegate { Reset(); }, 0)); ownerRegistrations.Add(core.Lifecycle.Subscribe(Plugin.ModuleId, (LifecycleEventKind)3, (Action<LifecycleEvent>)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<CombatPolicy> _createPolicy; private readonly List<IDisposable> _registrations = new List<IDisposable>(); private IDisposable _policyRegistration; private byte[] _lastServerPolicy = Array.Empty<byte>(); 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<CombatPolicy> createPolicy) { _policies = policies; _network = network; _createPolicy = createPolicy; try { RegisterPolicy(); _registrations.Add(network.Register(PolicyRequest, (Action<NetworkMessageContext, ZPackage>)OnPolicyRequest)); _registrations.Add(network.Register(PolicySnapshot, (Action<NetworkMessageContext, ZPackage>)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<byte>(); _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<byte[], string>)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<DurabilityRuleDecision> 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<IDisposable> _registrations = new List<IDisposable>(); 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<string>())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)5, "jg224.bc.net.", 1, Array.Empty<string>())); _registrations.Add(Core.Namespaces.Register(ModuleId, (NamespaceKind)9, "jg224.bc.", 1, Array.Empty<string>())); 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<Humanoid, float> AttackDrawTime = AccessTools.FieldRefAccess<Humanoid, float>("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<KeyCode>())); 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<ZNetView>(); 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<DamageRuleDecision> 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<CombatHitResolvedEvent>(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<ZNetView>(); 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<DeathRuleDecision> 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<Skills, Player> PlayerRef = AccessTools.FieldRefAccess<Skills, Player>("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<SkillGainRuleDecision> 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<Character, bool> RawBlocking = AccessTools.FieldRefAccess<Character, bool>("m_blocking"); private static readonly FieldRef<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack"); private static readonly FieldRef<Attack, bool> AttackAborted = AccessTools.FieldRefAccess<Attack, bool>("m_abortAttack"); private static readonly FieldRef<Player, float> QueuedPrimary = AccessTools.FieldRefAccess<Player, float>("m_queuedAttackTimer"); private static readonly FieldRef<Player, float> QueuedSecondary = AccessTools.FieldRefAccess<Player, float>("m_queuedSecondAttackTimer"); private static readonly HashSet<Attack> CommittedAttacks = new HashSet<Attack>(); 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<ZNetView>(); 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<Humanoid, float> BlockTimer = AccessTools.FieldRefAccess<Humanoid, float>("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<object>()).GetValue<ItemData>(); 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<BlockRuleDecision> 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<CombatBlockResolvedEvent>(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<CombatDodgeResolvedEvent>(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<Player, float> QueuedDodge = AccessTools.FieldRefAccess<Player, float>("m_queuedDodgeTimer"); private static readonly FieldRef<Character, Vector3> MoveDirection = AccessTools.FieldRefAccess<Character, Vector3>("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<KeyCode>()); 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<KeyboardShortcut, KeyboardShortcut>(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<KeyCode>()); 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<Humanoid, Attack> CurrentAttack = AccessTools.FieldRefAccess<Humanoid, Attack>("m_currentAttack"); private static readonly FieldRef<Player, float> QueuedPrimary = AccessTools.FieldRefAccess<Player, float>("m_queuedAttackTimer"); private static readonly FieldRef<Player, float> QueuedSecondary = AccessTools.FieldRefAccess<Player, float>("m_queuedSecondAttackTimer"); private static readonly FieldRef<Player, float> QueuedDodge = AccessTools.FieldRefAccess<Player, float>("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<Humanoid, ItemData> RightItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem"); private static readonly FieldRef<Humanoid, ItemData> LeftItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("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; } } }