Skip to content

Commit de2367d

Browse files
committed
Refactored CustomItems to use CustomObject as a base method which provides registration and lookup
1 parent 442a6dd commit de2367d

3 files changed

Lines changed: 193 additions & 65 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using YamlDotNet.Serialization;
2+
3+
namespace LabExtended.API.Custom
4+
{
5+
/// <summary>
6+
/// Base class for custom objects.
7+
/// </summary>
8+
public abstract class CustomObject<T> where T : class
9+
{
10+
private static Dictionary<string, T> registered = new();
11+
12+
/// <summary>
13+
/// Gets a read-only dictionary of all registered custom objects.
14+
/// </summary>
15+
public static IReadOnlyDictionary<string, T> RegisteredObjects => registered;
16+
17+
/// <summary>
18+
/// Retrieves the registered object associated with the specified identifier.
19+
/// </summary>
20+
/// <param name="id">The unique identifier of the registered object to retrieve. Cannot be null or empty.</param>
21+
/// <returns>The object of type T that is registered with the specified identifier.</returns>
22+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="id"/> is null or empty.</exception>
23+
/// <exception cref="KeyNotFoundException">Thrown if no object is registered with the specified <paramref name="id"/>.</exception>
24+
public static T Get(string id)
25+
{
26+
if (string.IsNullOrEmpty(id))
27+
throw new ArgumentNullException(nameof(id));
28+
29+
if (!registered.TryGetValue(id, out var customObject))
30+
throw new KeyNotFoundException($"No custom object with ID '{id}' is registered.");
31+
32+
return customObject;
33+
}
34+
35+
/// <summary>
36+
/// Retrieves the first registered object that matches the specified predicate.
37+
/// </summary>
38+
/// <param name="predicate">A delegate that defines the conditions of the object to search for. The predicate is applied to each
39+
/// registered object until a match is found.</param>
40+
/// <returns>The first registered object that satisfies the specified predicate.</returns>
41+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="predicate"/> is <see langword="null"/>.</exception>
42+
/// <exception cref="KeyNotFoundException">Thrown if no registered object matches the specified predicate.</exception>
43+
public static T Get(Predicate<T> predicate)
44+
{
45+
if (predicate is null)
46+
throw new ArgumentNullException(nameof(predicate));
47+
48+
foreach (var obj in registered.Values)
49+
{
50+
if (predicate(obj))
51+
{
52+
return obj;
53+
}
54+
}
55+
56+
throw new KeyNotFoundException("No custom object matching the given predicate is registered.");
57+
}
58+
59+
/// <summary>
60+
/// Attempts to retrieve a registered object of type T associated with the specified identifier.
61+
/// </summary>
62+
/// <param name="id">The unique identifier of the object to retrieve. Cannot be null or empty.</param>
63+
/// <param name="customObject">When this method returns, contains the object associated with the specified identifier, if found; otherwise,
64+
/// the default value for type T.</param>
65+
/// <returns>true if an object with the specified identifier is found; otherwise, false.</returns>
66+
public static bool TryGet(string id, out T customObject)
67+
{
68+
customObject = null!;
69+
70+
if (string.IsNullOrEmpty(id))
71+
return false;
72+
73+
return registered.TryGetValue(id, out customObject);
74+
}
75+
76+
/// <summary>
77+
/// Attempts to retrieve the first registered object that matches the specified predicate.
78+
/// </summary>
79+
/// <param name="predicate">A delegate that defines the conditions of the object to search for. Cannot be null.</param>
80+
/// <param name="customObject">When this method returns, contains the first object that matches the predicate, if found; otherwise, the
81+
/// default value for type T.</param>
82+
/// <returns>true if an object matching the predicate is found; otherwise, false.</returns>
83+
/// <exception cref="ArgumentNullException">Thrown if predicate is null.</exception>
84+
public static bool TryGet(Predicate<T> predicate, out T customObject)
85+
{
86+
if (predicate is null)
87+
throw new ArgumentNullException(nameof(predicate));
88+
89+
customObject = null!;
90+
91+
foreach (var obj in registered.Values)
92+
{
93+
if (predicate(obj))
94+
{
95+
customObject = obj;
96+
return true;
97+
}
98+
}
99+
100+
return false;
101+
}
102+
103+
/// <summary>
104+
/// Gets the ID of the custom object.
105+
/// </summary>
106+
[YamlIgnore]
107+
public abstract string Id { get; }
108+
109+
/// <summary>
110+
/// Whether or not the object is registered.
111+
/// </summary>
112+
[YamlIgnore]
113+
public bool IsRegistered { get; private set; }
114+
115+
/// <summary>
116+
/// Registers this custom object.
117+
/// </summary>
118+
/// <returns>true if the object got registered</returns>
119+
public bool Register()
120+
{
121+
if (registered.ContainsKey(Id))
122+
return false;
123+
124+
registered.Add(Id, (T)(object)this);
125+
126+
IsRegistered = true;
127+
128+
OnRegistered();
129+
return true;
130+
}
131+
132+
/// <summary>
133+
/// Unregisters this custom object.
134+
/// </summary>
135+
/// <returns>true if the object got unregistered</returns>
136+
public bool Unregister()
137+
{
138+
if (!registered.Remove(Id))
139+
return false;
140+
141+
IsRegistered = false;
142+
143+
OnUnregistered();
144+
return true;
145+
}
146+
147+
/// <summary>
148+
/// Gets called after being registered.
149+
/// </summary>
150+
public virtual void OnRegistered()
151+
{
152+
153+
}
154+
155+
/// <summary>
156+
/// Gets called after being unregistered.
157+
/// </summary>
158+
public virtual void OnUnregistered()
159+
{
160+
161+
}
162+
}
163+
}

LabExtended/API/Custom/Items/CustomItem.cs

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace LabExtended.API.Custom.Items
3131
/// <summary>
3232
/// Base class for custom item implementations.
3333
/// </summary>
34-
public abstract class CustomItem
34+
public abstract class CustomItem : CustomObject<CustomItem>
3535
{
3636
#region Delegates
3737
/// <summary>
@@ -80,14 +80,8 @@ public abstract class CustomItem
8080
public delegate void ForEachPickupDelegate<T>(ItemPickupBase item, ref T? itemData);
8181
#endregion
8282

83-
internal static Dictionary<string, CustomItem> itemsById = new();
8483
internal static Dictionary<ushort, TrackedCustomItem> itemsBySerial = new();
8584

86-
/// <summary>
87-
/// Gets all registered custom items.
88-
/// </summary>
89-
public static IReadOnlyDictionary<string, CustomItem> RegisteredItems => itemsById;
90-
9185
/// <summary>
9286
/// Gets all tracked custom items.
9387
/// </summary>
@@ -269,12 +263,6 @@ public static bool IsCustomItem<TItem, TData>(ushort itemSerial, out TItem custo
269263
public static bool IsTrackedItem(ushort itemSerial, out TrackedCustomItem trackedItem)
270264
=> itemsBySerial.TryGetValue(itemSerial, out trackedItem);
271265

272-
/// <summary>
273-
/// Gets the ID of the custom item.
274-
/// </summary>
275-
[YamlIgnore]
276-
public abstract string Id { get; }
277-
278266
/// <summary>
279267
/// Gets the name of the custom item.
280268
/// </summary>
@@ -318,46 +306,6 @@ public static bool IsTrackedItem(ushort itemSerial, out TrackedCustomItem tracke
318306
[Description("Sets if custom items dropped by a player should be destroyed when the player who dropped them leaves.")]
319307
public virtual bool DestroyOnOwnerLeave { get; set; } = false;
320308

321-
/// <summary>
322-
/// Registers this custom item.
323-
/// </summary>
324-
/// <returns>true if the item was registered</returns>
325-
public bool Register()
326-
{
327-
if (itemsById.ContainsKey(Id))
328-
return false;
329-
330-
itemsById.Add(Id, this);
331-
332-
Registered?.Invoke(this);
333-
334-
OnRegistered();
335-
336-
ApiLog.Info("Custom Items", $"&2Registered&r custom item &3{Name}&r (&6{Id}&r)");
337-
return true;
338-
}
339-
340-
/// <summary>
341-
/// Unregisters this custom item and removes all active instances.
342-
/// </summary>
343-
/// <returns></returns>
344-
public bool Unregister()
345-
{
346-
if (itemsById.Remove(Id))
347-
{
348-
DestroyInstances();
349-
350-
Unregistered?.Invoke(this);
351-
352-
OnUnregistered();
353-
354-
ApiLog.Info("Custom Items", $"&1Unregistered&r custom item &3{Name}&r (&6{Id}&r)");
355-
return true;
356-
}
357-
358-
return false;
359-
}
360-
361309
/// <summary>
362310
/// Invokes the specified delegate for each item currently held by a valid tracker and its associated owner.
363311
/// </summary>
@@ -1299,12 +1247,24 @@ public virtual void OnCollided(PickupCollidedEventArgs args, ref object? pickupD
12991247
/// <summary>
13001248
/// Gets called once the item has been registered.
13011249
/// </summary>
1302-
public virtual void OnRegistered() { }
1250+
public override void OnRegistered()
1251+
{
1252+
Registered?.InvokeSafe(this);
1253+
1254+
ApiLog.Info("Custom Items", $"&2Registered&r custom item &3{Name}&r (&6{Id}&r)");
1255+
}
13031256

13041257
/// <summary>
13051258
/// Gets called once the item has been unregistered.
13061259
/// </summary>
1307-
public virtual void OnUnregistered() { }
1260+
public override void OnUnregistered()
1261+
{
1262+
DestroyInstances();
1263+
1264+
Unregistered?.InvokeSafe(this);
1265+
1266+
ApiLog.Info("Custom Items", $"&1Unregistered&r custom item &3{Name}&r (&6{Id}&r)");
1267+
}
13081268

13091269
/// <summary>
13101270
/// Called when an item is added to a player -or- when the player picks up a spawned pickup.

LabExtended/Commands/Custom/CustomItems/CustomItemsCommand.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,17 @@ public void Inventory(
102102
[CommandOverload("list", "Lists all registered custom items.")]
103103
public void List()
104104
{
105-
if (CustomItem.RegisteredItems.Count == 0)
105+
if (CustomItem.RegisteredObjects.Count == 0)
106106
{
107107
Fail($"No items have been registered.");
108108
return;
109109
}
110110

111111
Ok(x =>
112112
{
113-
foreach (var pair in CustomItem.RegisteredItems)
113+
x.AppendLine();
114+
115+
foreach (var pair in CustomItem.RegisteredObjects)
114116
{
115117
var type = pair.Value.GetType();
116118
var baseType = type.BaseType ?? type;
@@ -126,15 +128,17 @@ public void List()
126128
[CommandOverload("active", "Lists all active custom item instances.")]
127129
public void Active()
128130
{
129-
if (CustomItem.RegisteredItems.Count == 0)
131+
if (CustomItem.RegisteredObjects.Count == 0)
130132
{
131133
Fail($"No items have been registered.");
132134
return;
133135
}
134136

135137
Ok(x =>
136138
{
137-
foreach (var pair in CustomItem.RegisteredItems)
139+
x.AppendLine();
140+
141+
foreach (var pair in CustomItem.RegisteredObjects)
138142
{
139143
var baseType = pair.Value.GetType().BaseType ?? pair.Value.GetType();
140144

@@ -184,7 +188,7 @@ public void Active()
184188
public void Destroy(
185189
[CommandParameter("Serial", "The serial number of the item to destroy. Specify 0 to destroy all.")] ushort itemSerial)
186190
{
187-
if (CustomItem.RegisteredItems.Count == 0)
191+
if (CustomItem.RegisteredObjects.Count == 0)
188192
{
189193
Fail($"No items have been registered.");
190194
return;
@@ -196,7 +200,7 @@ public void Destroy(
196200
{
197201
x.AppendLine();
198202

199-
foreach (var pair in CustomItem.RegisteredItems)
203+
foreach (var pair in CustomItem.RegisteredObjects)
200204
x.AppendLine($"- [ID: {pair.Key}] {pair.Value.Name}; {pair.Value.DestroyInstances()} instance(s) destroyed!");
201205
});
202206
}
@@ -249,19 +253,20 @@ public void Destroy(
249253
/// Adds a custom item to a player's inventory.
250254
/// </summary>
251255
[CommandOverload("add", "Adds a custom item to a player's inventory.")]
256+
[CommandOverload("give", "Adds a custom item to a player's inventory.")]
252257
public void Add(
253258
[CommandParameter("ID", "The ID of the custom item to add.")] string itemId,
254259
[CommandParameter("Target", "The player to add the item to (defaults to you).")] ExPlayer? target = null)
255260
{
256261
target ??= Sender;
257262

258-
if (CustomItem.RegisteredItems.Count == 0)
263+
if (CustomItem.RegisteredObjects.Count == 0)
259264
{
260265
Fail($"No items have been registered.");
261266
return;
262267
}
263268

264-
if (!CustomItem.RegisteredItems.TryGetValue(itemId, out var customItem))
269+
if (!CustomItem.RegisteredObjects.TryGetValue(itemId, out var customItem))
265270
{
266271
Fail($"Unknown custom item ID");
267272
return;
@@ -287,13 +292,13 @@ public void Spawn(
287292
[CommandParameter("ID", "The ID of the custom item to spawn.")] string itemId,
288293
[CommandParameter("Position", "The position to spawn the item at.")] Vector3 position)
289294
{
290-
if (CustomItem.RegisteredItems.Count == 0)
295+
if (CustomItem.RegisteredObjects.Count == 0)
291296
{
292297
Fail($"No items have been registered.");
293298
return;
294299
}
295300

296-
if (!CustomItem.RegisteredItems.TryGetValue(itemId, out var customItem))
301+
if (!CustomItem.RegisteredObjects.TryGetValue(itemId, out var customItem))
297302
{
298303
Fail($"Unknown custom item ID");
299304
return;

0 commit comments

Comments
 (0)