diff --git a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs index 18d2303353..de9e7fc478 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/ShotEventArgs.cs @@ -7,42 +7,61 @@ namespace Exiled.Events.EventArgs.Player { + using System; + using API.Features; using Exiled.API.Features.Items; using Interfaces; using InventorySystem.Items.Firearms.Modules; + using InventorySystem.Items.Firearms.Modules.Misc; using UnityEngine; /// /// Contains all information after a player has fired a weapon. /// - public class ShotEventArgs : IPlayerEvent, IFirearmEvent + public class ShotEventArgs : IFirearmEvent { /// /// Initializes a new instance of the class. /// + /// Hitscan result that contains all hit information. /// 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, float damage) + public ShotEventArgs(HitscanResult hitscanResult, HitscanHitregModuleBase hitregModule) { HitregModule = hitregModule; - RaycastHit = hitInfo; - Destructible = destructible; - Firearm = Item.Get(firearm); - Damage = damage; - + ShotResult = hitscanResult; + Firearm = Item.Get(HitregModule.Firearm); Player = Firearm.Owner; - if (Destructible is HitboxIdentity hitboxIdentity) + if (ShotResult.Destructibles.Count != 0) { - Hitbox = hitboxIdentity; - Target = Player.Get(Hitbox.TargetHub); + DestructibleHitPair firstPair = ShotResult.Destructibles[0]; + RaycastHit = firstPair.Hit; + Destructible = firstPair.Destructible; + Hitbox = Destructible as HitboxIdentity; + Target = Player.Get(Hitbox?.TargetHub); + } + else + { + if (ShotResult.Obstacles.Count > 0) + RaycastHit = ShotResult.Obstacles[0].Hit; + else + RaycastHit = ApproximateHit(); + } + + foreach (DestructibleDamageRecord damageRecord in ShotResult.DamagedDestructibles) + { + Damage += damageRecord.AppliedDamage; } } + /// + /// Gets the HitscanResult object which represents the result of weapon shot raycasts and calculations. + /// Each weapon controls how it is generated to determine the specific behavior. You can change that behaviour by modifying that object. + /// + /// + public HitscanResult ShotResult { get; } + /// /// Gets the player who fired the shot. /// @@ -62,48 +81,87 @@ public ShotEventArgs(HitscanHitregModuleBase hitregModule, RaycastHit hitInfo, I public HitscanHitregModuleBase HitregModule { get; } /// - /// Gets the raycast info. + /// Gets the hit info of the first hit. /// public RaycastHit RaycastHit { get; } /// - /// Gets the bullet travel distance. + /// Gets the bullet travel distance of the first bullet. /// public float Distance => RaycastHit.distance; /// - /// Gets the position of the hit. + /// Gets the position of the first hit. /// public Vector3 Position => RaycastHit.point; /// - /// Gets the firearm base damage at the hit distance. Actual inflicted damage may vary. + /// Gets the direction of the first bullet. + /// + public Vector3 Direction => Position - Player.CameraTransform.position; + + /// + /// Gets or sets a value indicating whether the shot can deal damage to objects such as players or glass windows. + /// Damage can be controlled on per instance basis using . + /// + public bool CanDamageDestructibles { get; set; } = true; + + /// + /// Gets or sets a value indicating whether the shot can deal damage to obstacles such as walls - spawning impact effects, or doors - breaking them in case of . + /// Damage can be controlled on per instance basis using . + /// + public bool CanDamageObstacles { get; set; } = true; + + /// + /// Gets the sum of the damage that is going to be dealt by the shot if remains unchanged. /// public float Damage { get; } /// - /// Gets the target player. Can be null. + /// Gets or sets a value indicating whether the shot can produce impact effects (e.g. bullet holes). /// - public Player Target { get; } + [Obsolete("Use CanDamageObstacles instead.")] + public bool CanSpawnImpactEffects { get => CanDamageObstacles; set => CanDamageObstacles = value; } /// - /// Gets the component of the target player that was hit. Can be null. + /// Gets or sets a value indicating whether the shot can deal damage. /// - public HitboxIdentity Hitbox { get; } + [Obsolete("Use CanDamageDestructibles instead.")] + public bool CanHurt { get => CanDamageDestructibles; set => CanDamageDestructibles = value; } /// - /// Gets the component of the hit collider. Can be null. + /// Gets the component of the first hit collider. Can be null. /// public IDestructible Destructible { get; } /// - /// Gets or sets a value indicating whether the shot can deal damage. + /// Gets the component of the first target player that was hit. Can be null. /// - public bool CanHurt { get; set; } = true; + public HitboxIdentity Hitbox { get; } /// - /// Gets or sets a value indicating whether the shot can produce impact effects (e.g. bullet holes). + /// Gets the first target player. Can be null. + /// + public Player Target { get; } + + /// + /// Reset the shot result generated by the firearm, allowing you to generate your own result. /// - public bool CanSpawnImpactEffects { get; set; } = true; + public void ResetShotResult() + { + ShotResult.Clear(); + } + + private RaycastHit ApproximateHit() + { + Ray ray = new(Player.CameraTransform.position, Player.CameraTransform.forward); + float maxDistance = HitregModule.DamageFalloffDistance + HitregModule.FullDamageDistance; + return new RaycastHit + { + distance = maxDistance, + point = ray.GetPoint(maxDistance), + normal = -ray.direction, + }; + } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs b/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs index afe7a08bfc..9404938e42 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/Shot.cs @@ -7,145 +7,53 @@ namespace Exiled.Events.Patches.Events.Player { -#pragma warning disable SA1402 -#pragma warning disable SA1649 - using System.Collections.Generic; - using System.Reflection; - using System.Reflection.Emit; + using System; - using API.Features.Pools; + using Exiled.API.Features; using Exiled.Events.Attributes; using Exiled.Events.EventArgs.Player; using HarmonyLib; using InventorySystem.Items.Firearms.Modules; using InventorySystem.Items.Firearms.Modules.Misc; - using UnityEngine; - - using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// Adds the event. /// [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.Shot))] - [HarmonyPatch(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.ServerApplyDestructibleDamage))] - internal static class ShotTarget + [HarmonyPatch(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.ServerApplyDamage))] + internal static class Shot { - private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + // public virtual void ServerApplyDamage(HitscanResult result) +#pragma warning disable SA1313 + private static bool Prefix(HitscanResult result, HitscanHitregModuleBase __instance) +#pragma warning restore SA1313 { - List newInstructions = ListPool.Pool.Get(instructions); - - int index = newInstructions.FindIndex(i => i.opcode == OpCodes.Ldloc_2); - - Label continueLabel = generator.DefineLabel(); + ShotEventArgs args = new(result, __instance); + Handlers.Player.OnShot(args); - LocalBuilder ev = generator.DeclareLocal(typeof(ShotEventArgs)); - - newInstructions.InsertRange( - index, - new[] + try + { + if (args.CanDamageDestructibles) { - // this - new(OpCodes.Ldarg_0), - - // target.Raycast.Hit - new(OpCodes.Ldarg_1), - new(OpCodes.Ldfld, Field(typeof(DestructibleHitPair), nameof(DestructibleHitPair.Raycast))), - new(OpCodes.Ldfld, Field(typeof(HitRayPair), nameof(HitRayPair.Hit))), - - // this.Firearm - new(OpCodes.Ldarg_0), - new(OpCodes.Callvirt, PropertyGetter(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.Firearm))), - - // destructible - new(OpCodes.Ldloc_2), - - // damage - new(OpCodes.Ldloc_0), + foreach (DestructibleHitPair destructible in result.Destructibles) + __instance.ServerApplyDestructibleDamage(destructible, result); + } - // ShotEventArgs ev = new ShotEventArgs(this, hitInfo, firearm, component, damage); - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ShotEventArgs))[0]), - new(OpCodes.Dup), - new(OpCodes.Dup), - new(OpCodes.Stloc_S, ev.LocalIndex), - - new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnShot))), - - // if (!ev.CanHurt) num = 0; - new(OpCodes.Callvirt, PropertyGetter(typeof(ShotEventArgs), nameof(ShotEventArgs.CanHurt))), - new(OpCodes.Brtrue, continueLabel), - - new(OpCodes.Ldc_I4_0), - new(OpCodes.Stloc_0), - - new CodeInstruction(OpCodes.Nop).WithLabels(continueLabel), - }); - - index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldarg_0); - Label returnLabel = generator.DefineLabel(); - - newInstructions.InsertRange( - index, - new CodeInstruction[] + if (args.CanDamageObstacles) { - // if (!ev.CanSpawnImpactEffects) return; - new(OpCodes.Ldloc_S, ev.LocalIndex), - new(OpCodes.Callvirt, PropertyGetter(typeof(ShotEventArgs), nameof(ShotEventArgs.CanSpawnImpactEffects))), - new(OpCodes.Brfalse_S, returnLabel), - }); - - newInstructions[newInstructions.Count - 1].labels.Add(returnLabel); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; + foreach (HitRayPair obstacle in result.Obstacles) + __instance.ServerApplyObstacleDamage(obstacle, result); + } - ListPool.Pool.Return(newInstructions); - } - } - - /// - /// Patches . - /// Adds the event. - /// - [HarmonyPatch(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.ServerAppendPrescan))] - internal static class ShotMiss - { - private static IEnumerable Transpiler(IEnumerable instructions) - { - List newInstructions = ListPool.Pool.Get(instructions); - - CodeInstruction[] eventInstructions = + __instance.ServerSendAllIndicators(result); + } + catch (Exception e) { - // hitInfo - new(OpCodes.Ldloc_1), - - // this.Firearm - new(OpCodes.Ldarg_0), - new(OpCodes.Callvirt, PropertyGetter(typeof(HitscanHitregModuleBase), nameof(HitscanHitregModuleBase.Firearm))), - - // (IDestructible)null - new(OpCodes.Ldnull), - - // 0f - new(OpCodes.Ldc_R4, 0f), - - // ShotEventArgs = new(this, hitInfo, this.Firearm, (IDestructible)null, 0f) - new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ShotEventArgs))[0]), - - // Handlers.Player.OnShot(ev); - new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnShot))), - }; - - int index = newInstructions.FindIndex(x => x.opcode == OpCodes.Brtrue_S) + 1; - newInstructions.InsertRange(index, new CodeInstruction[] { new(OpCodes.Ldarg_0), }.AddRangeToArray(eventInstructions)); - - index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ldarg_2); - newInstructions.InsertRange(index, new[] { new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]) }.AddRangeToArray(eventInstructions)); - - for (int z = 0; z < newInstructions.Count; z++) - yield return newInstructions[z]; + Log.Error($"Error in {nameof(Shot)} event - invalid modification of ev.{nameof(ShotEventArgs.ShotResult)} has caused an exception: {e}"); + } - ListPool.Pool.Return(newInstructions); + return false; } } }