Skip to content

Commit a1f8bf2

Browse files
authored
feat: Consuming Item Event (#805)
* one shot * fix doc * d
1 parent acb8bec commit a1f8bf2

4 files changed

Lines changed: 160 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="IConsumableEvent.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.Events.EventArgs.Interfaces
9+
{
10+
using Exiled.API.Features.Items;
11+
12+
/// <summary>
13+
/// Event args used for all <see cref="API.Features.Items.Consumable" /> related events.
14+
/// </summary>
15+
public interface IConsumableEvent : IItemEvent
16+
{
17+
/// <summary>
18+
/// Gets the <see cref="API.Features.Items.Consumable" /> triggering the event.
19+
/// </summary>
20+
public Consumable Consumable { get; }
21+
}
22+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ConsumingItemEventArgs.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.Events.EventArgs.Player
9+
{
10+
using API.Features;
11+
using API.Features.Items;
12+
13+
using Exiled.Events.EventArgs.Interfaces;
14+
15+
/// <summary>
16+
/// Contains all information before a player's consumable item effects are applied.
17+
/// </summary>
18+
public class ConsumingItemEventArgs : IPlayerEvent, IDeniableEvent, IConsumableEvent
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="ConsumingItemEventArgs" /> class.
22+
/// </summary>
23+
/// <param name="hub">The player who is consuming the item.</param>
24+
/// <param name="item">The consumable item to be consumed.</param>
25+
public ConsumingItemEventArgs(ReferenceHub hub, InventorySystem.Items.Usables.Consumable item)
26+
{
27+
Player = Player.Get(hub);
28+
Consumable = Item.Get(item) as Consumable;
29+
}
30+
31+
/// <summary>
32+
/// Gets the consumable item to be consumed.
33+
/// </summary>
34+
public Consumable Consumable { get; }
35+
36+
/// <inheritdoc/>
37+
public Item Item => Consumable;
38+
39+
/// <summary>
40+
/// Gets the player who is consuming the item.
41+
/// </summary>
42+
public Player Player { get; }
43+
44+
/// <summary>
45+
/// Gets or sets a value indicating whether the item's being consumed should be allowed or not.
46+
/// </summary>
47+
public bool IsAllowed { get; set; } = true;
48+
}
49+
}

EXILED/Exiled.Events/Handlers/Player.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public class Player
9898
/// </remarks>
9999
public static Event<UsedItemEventArgs> UsedItem { get; set; } = new();
100100

101+
/// <summary>
102+
/// Invoked before a <see cref="API.Features.Player"/> consumes an <see cref="API.Features.Items.Consumable"/>. In other words, it is invoked before the consumable item logic are applied.
103+
/// </summary>
104+
public static Event<ConsumingItemEventArgs> ConsumingItem { get; set; } = new();
105+
101106
/// <summary>
102107
/// Invoked before a <see cref="API.Features.Player"/> has stopped the use of a <see cref="API.Features.Items.Usable"/>.
103108
/// </summary>
@@ -723,6 +728,12 @@ public class Player
723728
/// <param name="ev">The <see cref="UsedItemEventArgs"/> instance.</param>
724729
public static void OnUsedItem(UsedItemEventArgs ev) => UsedItem.InvokeSafely(ev);
725730

731+
/// <summary>
732+
/// Called before a <see cref="API.Features.Player"/> consumes a <see cref="API.Features.Items.Consumable"/> item.
733+
/// </summary>
734+
/// <param name="ev">The <see cref="ConsumingItemEventArgs"/> instance.</param>
735+
public static void OnConsumingItem(ConsumingItemEventArgs ev) => ConsumingItem.InvokeSafely(ev);
736+
726737
/// <summary>
727738
/// Called before a <see cref="API.Features.Player"/> has stopped the use of a <see cref="API.Features.Items.Usable"/> item.
728739
/// </summary>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ConsumingItem.cs" company="ExMod Team">
3+
// Copyright (c) ExMod Team. All rights reserved.
4+
// Licensed under the CC BY-SA 3.0 license.
5+
// </copyright>
6+
// -----------------------------------------------------------------------
7+
8+
namespace Exiled.Events.Patches.Events.Player
9+
{
10+
using System.Collections.Generic;
11+
using System.Reflection.Emit;
12+
13+
using Exiled.API.Features.Pools;
14+
using Exiled.Events.Attributes;
15+
using Exiled.Events.EventArgs.Player;
16+
17+
using HarmonyLib;
18+
19+
using InventorySystem.Items.Usables;
20+
21+
using static HarmonyLib.AccessTools;
22+
23+
/// <summary>
24+
/// Patches <see cref="Consumable.ActivateEffects" />.
25+
/// Adds the <see cref="Handlers.Player.ConsumingItem" /> event.
26+
/// </summary>
27+
[EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.ConsumingItem))]
28+
[HarmonyPatch(typeof(Consumable), nameof(Consumable.ActivateEffects))]
29+
internal static class ConsumingItem
30+
{
31+
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
32+
{
33+
List<CodeInstruction> newInstructions = ListPool<CodeInstruction>.Pool.Get(instructions);
34+
35+
Label skip = generator.DefineLabel();
36+
37+
int offset = -1;
38+
int index = newInstructions.FindIndex(instruction => instruction.Calls(Method(typeof(Consumable), nameof(Consumable.OnEffectsActivated)))) + offset;
39+
40+
List<Label> mainLabels = newInstructions[index].ExtractLabels();
41+
newInstructions[index].WithLabels(skip);
42+
43+
newInstructions.InsertRange(0, new CodeInstruction[]
44+
{
45+
// this.Owner;
46+
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(mainLabels),
47+
new(OpCodes.Callvirt, PropertyGetter(typeof(Consumable), nameof(Consumable.Owner))),
48+
49+
// this;
50+
new(OpCodes.Ldarg_0),
51+
52+
// ConsumingItemEventArgs ev = new(this.Owner, this);
53+
new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ConsumingItemEventArgs))[0]),
54+
new(OpCodes.Dup),
55+
56+
// Player.OnConsumingItem(ev);
57+
new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnConsumingItem))),
58+
59+
// if (!ev.IsAllowed)
60+
// this._alreadyActivated = true;
61+
// return;
62+
new(OpCodes.Callvirt, PropertyGetter(typeof(ConsumingItemEventArgs), nameof(ConsumingItemEventArgs.IsAllowed))),
63+
new(OpCodes.Brtrue_S, skip),
64+
65+
new(OpCodes.Ldarg_0),
66+
new(OpCodes.Ldc_I4_1),
67+
new(OpCodes.Stfld, Field(typeof(Consumable), nameof(Consumable._alreadyActivated))),
68+
69+
new(OpCodes.Ret),
70+
});
71+
72+
for (int z = 0; z < newInstructions.Count; z++)
73+
yield return newInstructions[z];
74+
75+
ListPool<CodeInstruction>.Pool.Return(newInstructions);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)