Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using Anvil.API;
using Anvil.Services;
using Anvil.Services.Item;
using Anvil.Tests.Resources;
using NUnit.Framework;

namespace Anvil.Tests.Services.API.Item
{
[TestFixture(Category = "Services.API")]
public class ItemMinEquipLevelOverrideServiceTests
{
[Inject]
private static ItemMinEquipLevelOverrideService ItemMinEquipLevelOverrideService { get; set; } = null!;

private readonly List<NwGameObject> createdTestObjects = [];

[Test(Description = "Setting an item minimum level overrides for a given item.")]
[TestCase(StandardResRef.Item.nw_ashmlw009, 1, 0)]
[TestCase(StandardResRef.Item.x2_wdrowls003, 15, 5)]
[TestCase(StandardResRef.Item.x2_it_mcloak007, 19, 30)]
public void SetItemMinEquipLevelOverrideChangesMinEquipLevel(string itemResRef, int standardMinLevel, byte overrideMinLevel)
{
Location startLocation = NwModule.Instance.StartingLocation;

NwItem? item = NwItem.Create(itemResRef, startLocation);
Assert.That(item, Is.Not.Null, "Item was null after creation.");

createdTestObjects.Add(item!);

Assert.That(item!.MinEquipLevel, Is.EqualTo(standardMinLevel), "Item has expected base min equip level.");

ItemMinEquipLevelOverrideService.SetMinEquipLevelOverride(item, overrideMinLevel);

Assert.That(ItemMinEquipLevelOverrideService.GetMinEquipLevelOverride(item), Is.EqualTo(overrideMinLevel));
Assert.That(item.GetMinEquipLevelOverride(), Is.EqualTo(overrideMinLevel));
Assert.That(item.MinEquipLevel, Is.EqualTo(overrideMinLevel));
}

[TearDown]
public void CleanupTestObjects()
{
foreach (NwGameObject testObject in createdTestObjects)
{
testObject.PlotFlag = false;
testObject.Destroy();
}

createdTestObjects.Clear();
}
}
}
32 changes: 31 additions & 1 deletion NWN.Anvil/src/main/API/Objects/NwItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Linq;
using System.Threading.Tasks;
using Anvil.Native;
using Anvil.Services;
using Anvil.Services.Item;
using NWN.Core;
using NWN.Native.API;

Expand All @@ -15,6 +17,9 @@ namespace Anvil.API
[ObjectFilter(ObjectTypes.Item)]
public sealed partial class NwItem : NwGameObject
{
[Inject]
private static Lazy<ItemMinEquipLevelOverrideService> ItemMinEquipLevelOverrideService { get; set; } = null!;

private readonly CNWSItem item;

internal CNWSItem Item
Expand Down Expand Up @@ -174,7 +179,8 @@ public IEnumerable<ItemProperty> ItemProperties
}

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

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

/// <summary>
/// Clears any override that is set for the item's min equip level.<br/>
/// </summary>
public void ClearMinEquipLevelOverride()
{
ItemMinEquipLevelOverrideService.Value.ClearMinEquipLevelOverride(this);
}

/// <summary>
/// Creates a copy of this item.
/// </summary>
Expand Down Expand Up @@ -423,6 +437,14 @@ public bool CompareItem(NwItem otherItem)
return Item.CompareItem(otherItem.Item).ToBool();
}

/// <summary>
/// Gets the override that is set for the item's min equip level.<br/>
/// </summary>
public byte? GetMinEquipLevelOverride()
{
return ItemMinEquipLevelOverrideService.Value.GetMinEquipLevelOverride(this);
}

/// <summary>
/// Gets the number of uses per day remaining for the specified item property on this item.
/// </summary>
Expand Down Expand Up @@ -505,6 +527,14 @@ public void RemoveItemProperties(ItemPropertyTableEntry? propertyType = null, It
return NativeUtils.SerializeGff("UTI", (resGff, resStruct) => Item.SaveItem(resGff, resStruct, 0).ToBool());
}

/// <summary>
/// Sets the override value to use for this item's min equip level.<br/>
/// </summary>
public void SetMinEquipLevelOverride(byte equipLevel)
{
ItemMinEquipLevelOverrideService.Value.SetMinEquipLevelOverride(this, equipLevel);
}

/// <summary>
/// Sets the number of uses per day remaining for the specified item property on this item.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions NWN.Anvil/src/main/API/Variables/InternalVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal static class InternalVariables
public static InternalVariableBool AlwaysWalk(NwObject creature) => creature.GetObjectVariable<InternalVariableBool>("ALWAYS_WALK");
public static InternalVariableInt InitiativeMod(NwObject creature) => creature.GetObjectVariable<InternalVariableInt>("INITIATIVE_MOD");
public static InternalVariableInt DamageLevelOverride(NwCreature creature) => creature.GetObjectVariable<InternalVariableInt>("DAMAGE_LEVEL");
public static InternalVariableInt MinEquipLevelOverride(NwItem item) => item.GetObjectVariable<InternalVariableInt>("MINIMUM_EQUIP_LEVEL_OVERRIDE");
public static InternalVariableEnum<VisibilityMode> GlobalVisibilityOverride(NwObject gameObject) => gameObject.GetObjectVariable<InternalVariableEnum<VisibilityMode>>("VISIBILITY_OVERRIDE");
public static InternalVariableEnum<VisibilityMode> PlayerVisibilityOverride(NwPlayer player, NwObject targetGameObject) => player.ControlledCreature!.GetObjectVariable<InternalVariableEnum<VisibilityMode>>("VISIBILITY_OVERRIDE" + targetGameObject.ObjectId);
public static InternalVariableFloat WalkRateCap(NwObject creature) => creature.GetObjectVariable<InternalVariableFloat>("WALK_RATE_CAP");
Expand Down
3 changes: 3 additions & 0 deletions NWN.Anvil/src/main/Native/Functions/Functions.CNWSItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public static class CNWSItem
[NativeFunction("_ZN8CNWSItem14CloseInventoryEji", "?CloseInventory@CNWSItem@@QEAAXIH@Z")]
public delegate void CloseInventory(void* pItem, uint oidCloser, int bUpdatePlayer);

[NativeFunction("_ZN8CNWSItem16GetMinEquipLevelEv", "?GetMinEquipLevel@CNWSItem@@QEAAEXZ")]
public delegate byte GetMinEquipLevel(void* pItem);

[NativeFunction("_ZN8CNWSItem13OpenInventoryEj", "?OpenInventory@CNWSItem@@QEAAXI@Z")]
public delegate void OpenInventory(void* pItem, uint oidOpener);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Anvil.API;
using Anvil.Native;
using NLog;
using NWN.Native.API;

namespace Anvil.Services.Item
{
[ServiceBinding(typeof(ItemMinEquipLevelOverrideService))]
[ServiceBindingOptions(InternalBindingPriority.API, Lazy = true)]
internal sealed unsafe class ItemMinEquipLevelOverrideService
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();

private readonly FunctionHook<Functions.CNWSItem.GetMinEquipLevel> minEquipLevelHook;

public ItemMinEquipLevelOverrideService(HookService hookService)
{
Log.Info($"Initialising optional service {nameof(ItemMinEquipLevelOverrideService)}");
minEquipLevelHook = hookService.RequestHook<Functions.CNWSItem.GetMinEquipLevel>(OnGetMinEquipLevel, HookOrder.Late);
}

public byte? GetMinEquipLevelOverride(NwItem item)
{
InternalVariableInt overrideValue = InternalVariables.MinEquipLevelOverride(item);
return overrideValue.HasValue ? (byte)overrideValue.Value : null;
}

public void SetMinEquipLevelOverride(NwItem item, byte value)
{
InternalVariables.MinEquipLevelOverride(item).Value = value;
}

public void ClearMinEquipLevelOverride(NwItem item)
{
InternalVariables.MinEquipLevelOverride(item).Delete();
}

private byte OnGetMinEquipLevel(void* pItem)
{
NwItem? item = CNWSItem.FromPointer(pItem).ToNwObject<NwItem>();
if (item != null)
{
InternalVariableInt overrideValue = InternalVariables.MinEquipLevelOverride(item);
if (overrideValue.HasValue)
{
return (byte)overrideValue.Value;
}
}

return minEquipLevelHook.CallOriginal(pItem);
}
}
}
Loading