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