fix(items): port retail useability and autowear rejection
Use the exact low USEABLE_NO-bit predicate so reset/zero-valued direct-use items such as Blackmoor's Favor reach the ordinary Use request. Port AutoWear's clothing-priority blocker lookup and exact named system notice through the shared activation owner. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
60387668d0
commit
f71f947475
13 changed files with 289 additions and 57 deletions
|
|
@ -279,6 +279,29 @@ public sealed class WorldSelectionQueryTests
|
|||
Assert.True(h.Query.IsPickupable(looseComponent));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseabilityUsesRetailLowNoBitForAbsentAndExplicitValues()
|
||||
{
|
||||
var h = new Harness();
|
||||
const uint absent = 0x7000_0022u;
|
||||
const uint zero = 0x7000_0023u;
|
||||
const uint no = 0x7000_0024u;
|
||||
const uint neverWalk = 0x7000_0025u;
|
||||
h.Add(absent, Vector3.UnitX, ItemType.Gem);
|
||||
h.Add(zero, Vector3.UnitX * 2f, ItemType.Gem, useability: 0u);
|
||||
h.Add(no, Vector3.UnitX * 3f, ItemType.Gem, useability: ItemUseability.No);
|
||||
h.Add(
|
||||
neverWalk,
|
||||
Vector3.UnitX * 4f,
|
||||
ItemType.Gem,
|
||||
useability: ItemUseability.NeverWalk);
|
||||
|
||||
Assert.True(h.Query.IsUseable(absent));
|
||||
Assert.True(h.Query.IsUseable(zero));
|
||||
Assert.False(h.Query.IsUseable(no));
|
||||
Assert.True(h.Query.IsUseable(neverWalk));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectionSphereAppliesSetupOffsetScaleAndRotation()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -436,6 +436,24 @@ public sealed class ItemInteractionControllerTests
|
|||
Assert.Equal(new[] { 0x50000A03u }, h.Uses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroValuedUseabilityGem_sendsUseLikeBlackmoorsFavor()
|
||||
{
|
||||
var h = new Harness();
|
||||
const uint favor = 0x50000A30u;
|
||||
h.AddContained(favor, item =>
|
||||
{
|
||||
item.Name = "Blackmoor's Favor";
|
||||
item.Type = ItemType.Gem;
|
||||
item.Useability = ItemUseability.Undef;
|
||||
});
|
||||
|
||||
Assert.True(h.Controller.ActivateItem(favor));
|
||||
|
||||
Assert.Equal(new[] { favor }, h.Uses);
|
||||
Assert.Equal(1, h.Controller.BusyCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainerItem_sendsUseToOpen()
|
||||
{
|
||||
|
|
@ -527,6 +545,7 @@ public sealed class ItemInteractionControllerTests
|
|||
h.Objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000AF2u,
|
||||
Name = "Chainmail Hauberk",
|
||||
Type = ItemType.Clothing,
|
||||
CurrentlyEquippedLocation = EquipMask.UpperArmWear,
|
||||
Priority = 0x00000004u,
|
||||
|
|
@ -547,6 +566,10 @@ public sealed class ItemInteractionControllerTests
|
|||
|
||||
Assert.Empty(h.Wields);
|
||||
Assert.Equal(Pack, h.Objects.Get(0x50000A17u)!.ContainerId);
|
||||
Assert.Equal(
|
||||
new[] { "You must remove your Chainmail Hauberk to wear that" },
|
||||
h.SystemMessages);
|
||||
Assert.Empty(h.Toasts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -563,12 +586,15 @@ public sealed class ItemInteractionControllerTests
|
|||
h.AddContained(0x50000A06u, item =>
|
||||
{
|
||||
item.Type = ItemType.Armor;
|
||||
item.CombatUse = 4; // COMBAT_USE_SHIELD
|
||||
item.ValidLocations = EquipMask.Shield;
|
||||
item.Useability = ItemUseability.No;
|
||||
});
|
||||
|
||||
Assert.False(h.Controller.ActivateItem(0x50000A06u));
|
||||
bool activated = h.Controller.ActivateItem(0x50000A06u);
|
||||
|
||||
Assert.Empty(h.Wields);
|
||||
Assert.False(activated);
|
||||
Assert.Equal(Pack, h.Objects.Get(0x50000A06u)!.ContainerId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,30 @@ public sealed class ItemInteractionPolicyTests
|
|||
Determine(owned with { Useability = ItemUseability.Contained }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineUseResult_zeroValuedOwnedGem_isRetailItemUse()
|
||||
{
|
||||
var favor = Obj(0x6002) with
|
||||
{
|
||||
Type = ItemType.Gem,
|
||||
ContainerId = Player,
|
||||
OwnedByPlayer = true,
|
||||
Flags = PublicWeenieFlags.RequiresPackSlot,
|
||||
Useability = ItemUseability.Undef,
|
||||
};
|
||||
|
||||
Assert.Equal(ItemPrimaryUseResult.ItemUse, Determine(favor));
|
||||
|
||||
var decision = ItemInteractionPolicy.DecideUse(Use(favor));
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
ItemPolicyActionKind.SendUse,
|
||||
ItemPolicyActionKind.IncrementBusy,
|
||||
},
|
||||
decision.Actions.Select(action => action.Kind));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineUseResult_nonOwnedGameAndPlayerPaths_areDistinct()
|
||||
{
|
||||
|
|
@ -259,7 +283,7 @@ public sealed class ItemInteractionPolicyTests
|
|||
CombatUse: 0,
|
||||
ItemsCapacity: 0,
|
||||
ContainersCapacity: 0,
|
||||
Useability: 0,
|
||||
Useability: ItemUseability.No,
|
||||
TargetType: 0,
|
||||
OwnedByPlayer: false,
|
||||
IsContainer: false,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,23 @@ public sealed class ItemUseabilityTests
|
|||
Assert.False(ItemUseability.IsDirectUseable(ItemUseability.No));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ItemUseability.Undef, true)]
|
||||
[InlineData(ItemUseability.No, false)]
|
||||
[InlineData(ItemUseability.Contained, true)]
|
||||
[InlineData(ItemUseability.NeverWalk, true)]
|
||||
[InlineData(ItemUseability.No | ItemUseability.Contained, false)]
|
||||
public void IsUseable_matchesRetailLowNoBit(uint useability, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, ItemUseability.IsUseable(useability));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Undef_isAZeroValuedDirectUse()
|
||||
{
|
||||
Assert.True(ItemUseability.IsDirectUseable(ItemUseability.Undef));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LeastLimitedSourceUse_matchesRetailPriorityAndIgnoresObjSelf()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue