Skip to content

Commit ee25511

Browse files
authored
Add ILR override API/service. (#812)
* Add ILR restriction override API/service. * Fix base equip level. Update test description. * Fix pointer cast to item.
1 parent 9dad8d7 commit ee25511

File tree

5 files changed

+140
-1
lines changed

5 files changed

+140
-1
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Collections.Generic;
2+
using Anvil.API;
3+
using Anvil.Services;
4+
using Anvil.Services.Item;
5+
using Anvil.Tests.Resources;
6+
using NUnit.Framework;
7+
8+
namespace Anvil.Tests.Services.API.Item
9+
{
10+
[TestFixture(Category = "Services.API")]
11+
public class ItemMinEquipLevelOverrideServiceTests
12+
{
13+
[Inject]
14+
private static ItemMinEquipLevelOverrideService ItemMinEquipLevelOverrideService { get; set; } = null!;
15+
16+
private readonly List<NwGameObject> createdTestObjects = [];
17+
18+
[Test(Description = "Setting an item minimum level overrides for a given item.")]
19+
[TestCase(StandardResRef.Item.nw_ashmlw009, 1, 0)]
20+
[TestCase(StandardResRef.Item.x2_wdrowls003, 15, 5)]
21+
[TestCase(StandardResRef.Item.x2_it_mcloak007, 19, 30)]
22+
public void SetItemMinEquipLevelOverrideChangesMinEquipLevel(string itemResRef, int standardMinLevel, byte overrideMinLevel)
23+
{
24+
Location startLocation = NwModule.Instance.StartingLocation;
25+
26+
NwItem? item = NwItem.Create(itemResRef, startLocation);
27+
Assert.That(item, Is.Not.Null, "Item was null after creation.");
28+
29+
createdTestObjects.Add(item!);
30+
31+
Assert.That(item!.MinEquipLevel, Is.EqualTo(standardMinLevel), "Item has expected base min equip level.");
32+
33+
ItemMinEquipLevelOverrideService.SetMinEquipLevelOverride(item, overrideMinLevel);
34+
35+
Assert.That(ItemMinEquipLevelOverrideService.GetMinEquipLevelOverride(item), Is.EqualTo(overrideMinLevel));
36+
Assert.That(item.GetMinEquipLevelOverride(), Is.EqualTo(overrideMinLevel));
37+
Assert.That(item.MinEquipLevel, Is.EqualTo(overrideMinLevel));
38+
}
39+
40+
[TearDown]
41+
public void CleanupTestObjects()
42+
{
43+
foreach (NwGameObject testObject in createdTestObjects)
44+
{
45+
testObject.PlotFlag = false;
46+
testObject.Destroy();
47+
}
48+
49+
createdTestObjects.Clear();
50+
}
51+
}
52+
}

NWN.Anvil/src/main/API/Objects/NwItem.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using Anvil.Native;
6+
using Anvil.Services;
7+
using Anvil.Services.Item;
68
using NWN.Core;
79
using NWN.Native.API;
810

@@ -15,6 +17,9 @@ namespace Anvil.API
1517
[ObjectFilter(ObjectTypes.Item)]
1618
public sealed partial class NwItem : NwGameObject
1719
{
20+
[Inject]
21+
private static Lazy<ItemMinEquipLevelOverrideService> ItemMinEquipLevelOverrideService { get; set; } = null!;
22+
1823
private readonly CNWSItem item;
1924

2025
internal CNWSItem Item
@@ -174,7 +179,8 @@ public IEnumerable<ItemProperty> ItemProperties
174179
}
175180

176181
/// <summary>
177-
/// Gets the minimum level required to equip this item.
182+
/// Gets the minimum level required to equip this item.<br/>
183+
/// If an override is set with <see cref="SetMinEquipLevelOverride"/>, this property will return the override value.
178184
/// </summary>
179185
public byte MinEquipLevel => Item.GetMinEquipLevel();
180186

@@ -360,6 +366,14 @@ public void AddItemProperty(ItemProperty itemProperty, EffectDuration durationTy
360366
NWScript.AddItemProperty((int)durationType, itemProperty, this, (float)duration.TotalSeconds);
361367
}
362368

369+
/// <summary>
370+
/// Clears any override that is set for the item's min equip level.<br/>
371+
/// </summary>
372+
public void ClearMinEquipLevelOverride()
373+
{
374+
ItemMinEquipLevelOverrideService.Value.ClearMinEquipLevelOverride(this);
375+
}
376+
363377
/// <summary>
364378
/// Creates a copy of this item.
365379
/// </summary>
@@ -423,6 +437,14 @@ public bool CompareItem(NwItem otherItem)
423437
return Item.CompareItem(otherItem.Item).ToBool();
424438
}
425439

440+
/// <summary>
441+
/// Gets the override that is set for the item's min equip level.<br/>
442+
/// </summary>
443+
public byte? GetMinEquipLevelOverride()
444+
{
445+
return ItemMinEquipLevelOverrideService.Value.GetMinEquipLevelOverride(this);
446+
}
447+
426448
/// <summary>
427449
/// Gets the number of uses per day remaining for the specified item property on this item.
428450
/// </summary>
@@ -505,6 +527,14 @@ public void RemoveItemProperties(ItemPropertyTableEntry? propertyType = null, It
505527
return NativeUtils.SerializeGff("UTI", (resGff, resStruct) => Item.SaveItem(resGff, resStruct, 0).ToBool());
506528
}
507529

530+
/// <summary>
531+
/// Sets the override value to use for this item's min equip level.<br/>
532+
/// </summary>
533+
public void SetMinEquipLevelOverride(byte equipLevel)
534+
{
535+
ItemMinEquipLevelOverrideService.Value.SetMinEquipLevelOverride(this, equipLevel);
536+
}
537+
508538
/// <summary>
509539
/// Sets the number of uses per day remaining for the specified item property on this item.
510540
/// </summary>

NWN.Anvil/src/main/API/Variables/InternalVariables.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal static class InternalVariables
1313
public static InternalVariableBool AlwaysWalk(NwObject creature) => creature.GetObjectVariable<InternalVariableBool>("ALWAYS_WALK");
1414
public static InternalVariableInt InitiativeMod(NwObject creature) => creature.GetObjectVariable<InternalVariableInt>("INITIATIVE_MOD");
1515
public static InternalVariableInt DamageLevelOverride(NwCreature creature) => creature.GetObjectVariable<InternalVariableInt>("DAMAGE_LEVEL");
16+
public static InternalVariableInt MinEquipLevelOverride(NwItem item) => item.GetObjectVariable<InternalVariableInt>("MINIMUM_EQUIP_LEVEL_OVERRIDE");
1617
public static InternalVariableEnum<VisibilityMode> GlobalVisibilityOverride(NwObject gameObject) => gameObject.GetObjectVariable<InternalVariableEnum<VisibilityMode>>("VISIBILITY_OVERRIDE");
1718
public static InternalVariableEnum<VisibilityMode> PlayerVisibilityOverride(NwPlayer player, NwObject targetGameObject) => player.ControlledCreature!.GetObjectVariable<InternalVariableEnum<VisibilityMode>>("VISIBILITY_OVERRIDE" + targetGameObject.ObjectId);
1819
public static InternalVariableFloat WalkRateCap(NwObject creature) => creature.GetObjectVariable<InternalVariableFloat>("WALK_RATE_CAP");

NWN.Anvil/src/main/Native/Functions/Functions.CNWSItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public static class CNWSItem
1010
[NativeFunction("_ZN8CNWSItem14CloseInventoryEji", "?CloseInventory@CNWSItem@@QEAAXIH@Z")]
1111
public delegate void CloseInventory(void* pItem, uint oidCloser, int bUpdatePlayer);
1212

13+
[NativeFunction("_ZN8CNWSItem16GetMinEquipLevelEv", "?GetMinEquipLevel@CNWSItem@@QEAAEXZ")]
14+
public delegate byte GetMinEquipLevel(void* pItem);
15+
1316
[NativeFunction("_ZN8CNWSItem13OpenInventoryEj", "?OpenInventory@CNWSItem@@QEAAXI@Z")]
1417
public delegate void OpenInventory(void* pItem, uint oidOpener);
1518
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Anvil.API;
2+
using Anvil.Native;
3+
using NLog;
4+
using NWN.Native.API;
5+
6+
namespace Anvil.Services.Item
7+
{
8+
[ServiceBinding(typeof(ItemMinEquipLevelOverrideService))]
9+
[ServiceBindingOptions(InternalBindingPriority.API, Lazy = true)]
10+
internal sealed unsafe class ItemMinEquipLevelOverrideService
11+
{
12+
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
13+
14+
private readonly FunctionHook<Functions.CNWSItem.GetMinEquipLevel> minEquipLevelHook;
15+
16+
public ItemMinEquipLevelOverrideService(HookService hookService)
17+
{
18+
Log.Info($"Initialising optional service {nameof(ItemMinEquipLevelOverrideService)}");
19+
minEquipLevelHook = hookService.RequestHook<Functions.CNWSItem.GetMinEquipLevel>(OnGetMinEquipLevel, HookOrder.Late);
20+
}
21+
22+
public byte? GetMinEquipLevelOverride(NwItem item)
23+
{
24+
InternalVariableInt overrideValue = InternalVariables.MinEquipLevelOverride(item);
25+
return overrideValue.HasValue ? (byte)overrideValue.Value : null;
26+
}
27+
28+
public void SetMinEquipLevelOverride(NwItem item, byte value)
29+
{
30+
InternalVariables.MinEquipLevelOverride(item).Value = value;
31+
}
32+
33+
public void ClearMinEquipLevelOverride(NwItem item)
34+
{
35+
InternalVariables.MinEquipLevelOverride(item).Delete();
36+
}
37+
38+
private byte OnGetMinEquipLevel(void* pItem)
39+
{
40+
NwItem? item = CNWSItem.FromPointer(pItem).ToNwObject<NwItem>();
41+
if (item != null)
42+
{
43+
InternalVariableInt overrideValue = InternalVariables.MinEquipLevelOverride(item);
44+
if (overrideValue.HasValue)
45+
{
46+
return (byte)overrideValue.Value;
47+
}
48+
}
49+
50+
return minEquipLevelHook.CallOriginal(pItem);
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)