diff --git a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs index 1b0650720f..0eb6a5c69d 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs @@ -19,49 +19,36 @@ namespace Exiled.Events.EventArgs.Player public class ShotEventArgs : IPlayerEvent, IFirearmEvent { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// The instance. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ShotEventArgs(HitscanHitregModuleBase instance, InventorySystem.Items.Firearms.Firearm firearm, Ray ray, float maxDistance) + /// Hitreg module that calculated the shot. + /// Raycast hit info. + /// The firearm used. + /// The IDestructible that was hit. Can be null. + public ShotEventArgs(HitscanHitregModuleBase hitregModule, RaycastHit hitInfo, InventorySystem.Items.Firearms.Firearm firearm, IDestructible destructible) { - Player = Player.Get(firearm.Owner); + HitregModule = hitregModule; + RaycastHit = hitInfo; + Destructible = destructible; Firearm = Item.Get(firearm); - MaxDistance = maxDistance; - - if (!Physics.Raycast(ray, out RaycastHit hitInfo, maxDistance, HitscanHitregModuleBase.HitregMask)) - return; - Distance = hitInfo.distance; - Position = hitInfo.point; - Damage = hitInfo.collider.TryGetComponent(out IDestructible component) ? instance.DamageAtDistance(hitInfo.distance) : 0f; - Destructible = component; - RaycastHit = hitInfo; + Player = Firearm.Owner; + Damage = Destructible is not null ? HitregModule.DamageAtDistance(hitInfo.distance) : 0f; - if (component is HitboxIdentity identity) + if (Destructible is HitboxIdentity hitboxIdentity) { - Hitbox = identity; + Hitbox = hitboxIdentity; Target = Player.Get(Hitbox.TargetHub); } } /// - /// Gets the player who shot. + /// Gets the player who fired the shot. /// public Player Player { get; } /// - /// Gets the firearm used to shoot. + /// Gets the firearm used to fire the shot. /// public Firearm Firearm { get; } @@ -69,43 +56,53 @@ public ShotEventArgs(HitscanHitregModuleBase instance, InventorySystem.Items.Fir public Item Item => Firearm; /// - /// Gets the max distance of the shot. + /// Gets the firearm hitreg module responsible for the shot. /// - public float MaxDistance { get; } + public HitscanHitregModuleBase HitregModule { get; } /// - /// Gets the shot distance. Can be 0.0f if the raycast doesn't hit collider. + /// Gets the raycast info. /// - public float Distance { get; } + public RaycastHit RaycastHit { get; } /// - /// Gets the shot position. Can be if the raycast doesn't hit collider. + /// Gets the bullet travel distance. /// - public Vector3 Position { get; } + public float Distance => RaycastHit.distance; /// - /// Gets the component of the hit collider. Can be . + /// Gets the position of the hit. /// - public IDestructible Destructible { get; } + public Vector3 Position => RaycastHit.point; /// - /// Gets the inflicted damage. + /// Gets the firearm base damage at the hit distance. Actual inflicted damage may vary. /// public float Damage { get; } /// - /// Gets the raycast result. + /// Gets the target player. Can be null. /// - public RaycastHit RaycastHit { get; } + public Player Target { get; } /// - /// Gets the target of the shot. Can be . + /// Gets the component of the target player that was hit. Can be null. /// - public Player Target { get; } + public HitboxIdentity Hitbox { get; } /// - /// Gets the component of the hit collider. Can be . + /// Gets the component of the hit collider. Can be null. /// - public HitboxIdentity Hitbox { get; } + public IDestructible Destructible { get; } + + /// + /// Gets or sets a value indicating whether the shot can deal damage. + /// + public bool CanHurt { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the shot can produce impact effects (e.g. bullet holes). + /// + public bool CanSpawnImpactEffects { get; set; } = true; } } diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs b/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs index d3d4dcc9c7..07206dc3b4 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Player { using System.Collections.Generic; + using System.Reflection; using System.Reflection.Emit; using API.Features.Pools; @@ -15,6 +16,7 @@ namespace Exiled.Events.Patches.Events.Player using Exiled.Events.EventArgs.Player; using HarmonyLib; using InventorySystem.Items.Firearms.Modules; + using UnityEngine; using static HarmonyLib.AccessTools; @@ -26,37 +28,110 @@ namespace Exiled.Events.Patches.Events.Player [HarmonyPatch(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.ServerPerformHitscan))] internal static class Shot { + private static void ProcessRaycastMiss(HitscanHitregModuleBase hitregModule, Ray ray, float maxDistance) + { + RaycastHit hit = new() + { + distance = maxDistance, + point = ray.GetPoint(maxDistance), + normal = -ray.direction, + }; + + var ev = new ShotEventArgs(hitregModule, hit, hitregModule.Firearm, null); + Handlers.Player.OnShot(ev); + } + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { List newInstructions = ListPool.Pool.Get(instructions); - Label returnLabel = generator.DefineLabel(); - - int offset = 3; - int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Ldarg_2) + offset; + /* + IL_0020: ldarg.1 // targetRay + IL_0021: ldloca.s hitInfo + IL_0023: ldloc.0 // maxDistance + IL_0024: ldsfld class CachedLayerMask InventorySystem.Items.Firearms.Modules.HitscanHitregModuleBase::HitregMask + IL_0029: call int32 CachedLayerMask::op_Implicit(class CachedLayerMask) + IL_002e: call bool [UnityEngine.PhysicsModule]UnityEngine.Physics::Raycast(valuetype [UnityEngine.CoreModule]UnityEngine.Ray, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit&, float32, int32) + IL_0033: brtrue.s IL_0037 + [] <= Here + */ + MethodInfo raycastMethod = Method(typeof(Physics), nameof(Physics.Raycast), new[] { typeof(Ray), typeof(RaycastHit).MakeByRefType(), typeof(float), typeof(int) }); + int raycastFailIndex = newInstructions.FindIndex(i => i.Calls(raycastMethod)) + 2; newInstructions.InsertRange( - index, + raycastFailIndex, new CodeInstruction[] { - // instance + // ProcessRaycastMiss(this, targetRay, maxDistance); new(OpCodes.Ldarg_0), + new(OpCodes.Ldarg_1), + new(OpCodes.Ldloc_0), + new(OpCodes.Call, Method(typeof(Shot), nameof(ProcessRaycastMiss))), + }); - // this.Firearm - new(OpCodes.Ldarg_0), - new(OpCodes.Callvirt, PropertyGetter(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.Firearm))), + /* + IL_0037: ldloca.s hitInfo + IL_0039: call instance class [UnityEngine.PhysicsModule]UnityEngine.Collider [UnityEngine.PhysicsModule]UnityEngine.RaycastHit::get_collider() + IL_003e: ldloca.s component + IL_0040: callvirt instance bool [UnityEngine.CoreModule]UnityEngine.Component::TryGetComponent(!!0/*class IDestructible* /&) + [] <= Here // This position is reached whether IDestructible is found or not. + IL_0045: brfalse.s IL_005e + */ + int destructibleGetIndex = newInstructions.FindIndex(i => i.operand is MethodInfo { Name: nameof(Component.TryGetComponent) }) + 1; - // ray - new(OpCodes.Ldarg_1), + Label continueLabel = generator.DefineLabel(); - // maxDistance - new(OpCodes.Ldloc_0), + LocalBuilder ev = generator.DeclareLocal(typeof(ShotEventArgs)); - // ShotEventArgs ev = new(HitscanHitregModuleBase, Firearm, Ray, float) + newInstructions.InsertRange( + destructibleGetIndex, + new[] + { + // var ev = new ShotEventArgs(this, hitInfo, firearm, component); + new(OpCodes.Ldarg_0), // this + new(OpCodes.Ldloc_1), // hitInfo + new(OpCodes.Ldarg_0), // this.Firearm + new(OpCodes.Callvirt, PropertyGetter(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.Firearm))), + new(OpCodes.Ldloc_2), // component new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ShotEventArgs))[0]), + new(OpCodes.Dup), // Leave ShotEventArgs on the stack + new(OpCodes.Stloc_S, ev.LocalIndex), - // Handlers.Player.OnShot(ev) + new(OpCodes.Dup), // Leave ShotEventArgs on the stack new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnShot))), + + // if (!ev.CanHurt) hitInfo.distance = maxDistance; + new(OpCodes.Callvirt, PropertyGetter(typeof(ShotEventArgs), nameof(ShotEventArgs.CanHurt))), + new(OpCodes.Brtrue, continueLabel), + + new(OpCodes.Ldloca_S, 1), // hitInfo address + new(OpCodes.Ldloc_0), // maxDistance + new(OpCodes.Call, PropertySetter(typeof(RaycastHit), nameof(RaycastHit.distance))), // hitInfo.distance = maxDistance + + new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel), + }); + + /* + [] <= Here + IL_0067: ldarg.0 // this + IL_0068: call instance class InventorySystem.Items.Firearms.Firearm InventorySystem.Items.Firearms.FirearmSubcomponentBase::get_Firearm() + IL_006d: ldloca.s module + IL_006f: ldc.i4.1 + IL_0070: call bool InventorySystem.Items.Firearms.Modules.ModulesUtils::TryGetModule(class InventorySystem.Items.Firearms.Firearm, !!0/*class InventorySystem.Items.Firearms.Modules.ImpactEffectsModule* /&, bool) + IL_0075: brtrue.s IL_0079 + */ + int impactEffectsIndex = newInstructions.FindIndex(i => i.operand is MethodInfo { Name: nameof(ModulesUtils.TryGetModule) }) - 4; + List