Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions EXILED/Exiled.Events/EventArgs/Player/ShowingHitMarkerEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="ShowingHitMarkerEventArgs.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.Events.EventArgs.Player
{
using API.Features;
using Interfaces;

/// <summary>
/// Contains all information before a hitmarker is show to player.
/// </summary>
Comment on lines +14 to +15

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML doc summary has grammatical errors ("is show to player"); please correct to something like "...before a hitmarker is shown to a player." so generated docs/readers are clear.

Copilot uses AI. Check for mistakes.
public class ShowingHitMarkerEventArgs : IPlayerEvent, IDeniableEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="ShowingHitMarkerEventArgs" /> class.
/// </summary>
/// <param name="hub">
/// <inheritdoc cref="Player" />
/// </param>
/// <param name="size">
/// <inheritdoc cref="Size" />
/// </param>
/// <param name="shouldPlayAudio">
/// <inheritdoc cref="ShouldPlayAudio" />
/// </param>
/// <param name="hitmarkerType">
/// <inheritdoc cref="HitmarkerType" />
/// </param>
public ShowingHitMarkerEventArgs(ReferenceHub hub, float size, bool shouldPlayAudio, HitmarkerType hitmarkerType)
{
Player = Player.Get(hub);
Size = size;
ShouldPlayAudio = shouldPlayAudio;
HitmarkerType = hitmarkerType;
}

/// <summary>
/// Gets or sets the player that the hitmarker is being shown to.
/// </summary>
public Player Player { get; set; }

/// <summary>
/// Gets or sets the target size multiplier.
/// </summary>
public float Size { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the hitmarker sound effect should play.
/// </summary>
public bool ShouldPlayAudio { get; set; }

/// <summary>
/// Gets or sets a the type of the hitmarker.
/// </summary>
public HitmarkerType HitmarkerType { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the hitmarker should be shown.
/// </summary>
public bool IsAllowed { get; set; } = true;
}
}
11 changes: 11 additions & 0 deletions EXILED/Exiled.Events/Handlers/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public class Player
/// </summary>
public static Event<HitEventArgs> Hit { get; set; } = new ();

/// <summary>
/// Invoked before a player is shown a hitmarker.
/// </summary>
public static Event<ShowingHitMarkerEventArgs> ShowingHitMarker { get; set; } = new ();

/// <summary>
/// Invoked before authenticating a <see cref="API.Features.Player"/>.
/// </summary>
Expand Down Expand Up @@ -1423,6 +1428,12 @@ public static void OnItemRemoved(ReferenceHub referenceHub, InventorySystem.Item
/// <param name="ev">The <see cref="HitEventArgs"/> instance.</param>
public static void OnHit(HitEventArgs ev) => Hit.InvokeSafely(ev);

/// <summary>
/// Called before a player is shown a hitmarker.
/// </summary>
/// <param name="ev">The <see cref="ShowingHitMarkerEventArgs"/> instance.</param>
public static void OnShowingHitMarker(ShowingHitMarkerEventArgs ev) => ShowingHitMarker.InvokeSafely(ev);

/// <summary>
/// Called before Emergency Release Button is pressed.
/// </summary>
Expand Down
81 changes: 81 additions & 0 deletions EXILED/Exiled.Events/Patches/Events/Player/ShowingHitMarker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// -----------------------------------------------------------------------
// <copyright file="ShowingHitMarker.cs" company="ExMod Team">
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
// </copyright>
// -----------------------------------------------------------------------

namespace Exiled.Events.Patches.Events.Player
{
using System.Collections.Generic;
using System.Reflection.Emit;

using Exiled.API.Features.Pools;
using Exiled.Events.Attributes;
using Exiled.Events.EventArgs.Player;
using HarmonyLib;

using static HarmonyLib.AccessTools;

/// <summary>
/// Patch the <see cref="Hitmarker.SendHitmarkerDirectly(ReferenceHub, float, bool, HitmarkerType)"/> method.
/// Adds the <see cref="Handlers.Player.ShowingHitMarker"/> event.
/// </summary>
[EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ShowingHitMarker))]
[HarmonyPatch(typeof(Hitmarker), nameof(Hitmarker.SendHitmarkerDirectly), typeof(ReferenceHub), typeof(float), typeof(bool), typeof(HitmarkerType))]
internal static class ShowingHitMarker
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);

LocalBuilder ev = generator.DeclareLocal(typeof(ShowingHitMarkerEventArgs));
Label continueLabel = generator.DefineLabel();

int offset = 1;

int index = newInstructions.FindIndex(instruction => instruction.opcode == OpCodes.Ret) + offset;

newInstructions.InsertRange(index, new[]
{
// ShowingHitMarkerEventArgs ev = new(hub, size, playAudio, hitmarkerType)
new CodeInstruction(OpCodes.Ldarg_0).MoveLabelsFrom(newInstructions[index]),
new(OpCodes.Ldarg_1),
new(OpCodes.Ldarg_2),
new(OpCodes.Ldarg_3),
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ShowingHitMarkerEventArgs))[0]),
new(OpCodes.Dup),
new(OpCodes.Stloc_S, ev.LocalIndex),

// Handlers.Player.OnShowingHitMarker(ev)
new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnShowingHitMarker))),

// if (!ev.IsAllowed) return;
new(OpCodes.Ldloc_S, ev.LocalIndex),
new(OpCodes.Callvirt, PropertyGetter(typeof(ShowingHitMarkerEventArgs), nameof(ShowingHitMarkerEventArgs.IsAllowed))),
new(OpCodes.Brtrue_S, continueLabel),
new(OpCodes.Ret),

// size = ev.Size;
new CodeInstruction(OpCodes.Ldloc_S, ev.LocalIndex).WithLabels(continueLabel),
new(OpCodes.Callvirt, PropertyGetter(typeof(ShowingHitMarkerEventArgs), nameof(ShowingHitMarkerEventArgs.Size))),
new(OpCodes.Starg_S, 1),

// playAudio = ev.ShouldPlayAudio;
new(OpCodes.Ldloc_S, ev.LocalIndex),
new(OpCodes.Callvirt, PropertyGetter(typeof(ShowingHitMarkerEventArgs), nameof(ShowingHitMarkerEventArgs.ShouldPlayAudio))),
new(OpCodes.Starg_S, 2),

// hitmarkerType = ev.HitmarkerType;
new(OpCodes.Ldloc_S, ev.LocalIndex),
new(OpCodes.Callvirt, PropertyGetter(typeof(ShowingHitMarkerEventArgs), nameof(ShowingHitMarkerEventArgs.HitmarkerType))),
new(OpCodes.Starg_S, 3),
});

for (int z = 0; z < newInstructions.Count; z++)
yield return newInstructions[z];

ListPool<CodeInstruction>.Pool.Return(newInstructions);
}
}
}
Loading