fix(inventory): sequence secondary wield blockers
Preserve retail AmmoType from CreateObject and port BlocksUseOfShield so bow, caster, and two-handed switches remove incompatible shields, while mismatched missile ammo is also returned to the pack. Each blocker remains server-confirmed before AutoWield re-enters. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
17b5712d53
commit
815ce0dec4
14 changed files with 206 additions and 16 deletions
|
|
@ -523,6 +523,115 @@ public sealed class ItemInteractionControllerTests
|
|||
h.Objects.Get(sword)!.CurrentlyEquippedLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeaponReplacement_bow_removesPrimaryThenIncompatibleShield()
|
||||
{
|
||||
var h = new Harness();
|
||||
const uint sword = 0x50000B31u;
|
||||
const uint shield = 0x50000B32u;
|
||||
const uint bow = 0x50000B33u;
|
||||
h.Objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = sword,
|
||||
Type = ItemType.MeleeWeapon,
|
||||
CombatUse = 1,
|
||||
ValidLocations = EquipMask.MeleeWeapon,
|
||||
});
|
||||
h.Objects.MoveItem(sword, Player, -1, EquipMask.MeleeWeapon);
|
||||
h.Objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = shield,
|
||||
Type = ItemType.Armor,
|
||||
CombatUse = 4,
|
||||
ValidLocations = EquipMask.Shield,
|
||||
});
|
||||
h.Objects.MoveItem(shield, Player, -1, EquipMask.Shield);
|
||||
h.AddContained(bow, item =>
|
||||
{
|
||||
item.Type = ItemType.MissileWeapon;
|
||||
item.CombatUse = 2;
|
||||
item.AmmoType = 1;
|
||||
item.ValidLocations = EquipMask.MissileWeapon;
|
||||
});
|
||||
|
||||
Assert.True(h.Controller.ActivateItem(bow));
|
||||
Assert.Equal(new[] { (sword, Player, 0) }, h.Puts);
|
||||
|
||||
h.Objects.MoveItem(sword, Player, 0, EquipMask.None);
|
||||
Assert.Equal(
|
||||
new[] { (sword, Player, 0), (shield, Player, 0) },
|
||||
h.Puts);
|
||||
Assert.Empty(h.Wields);
|
||||
|
||||
h.Objects.MoveItem(shield, Player, 0, EquipMask.None);
|
||||
Assert.Equal(new[] { (bow, (uint)EquipMask.MissileWeapon) }, h.Wields);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeaponReplacement_bow_removesMismatchedAmmoAfterPrimaryBlocker()
|
||||
{
|
||||
var h = new Harness();
|
||||
const uint sword = 0x50000B41u;
|
||||
const uint arrows = 0x50000B42u;
|
||||
const uint bow = 0x50000B43u;
|
||||
h.Objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = sword,
|
||||
Type = ItemType.MeleeWeapon,
|
||||
CombatUse = 1,
|
||||
ValidLocations = EquipMask.MeleeWeapon,
|
||||
});
|
||||
h.Objects.MoveItem(sword, Player, -1, EquipMask.MeleeWeapon);
|
||||
h.Objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = arrows,
|
||||
Type = ItemType.MissileWeapon,
|
||||
CombatUse = 3,
|
||||
AmmoType = 2,
|
||||
ValidLocations = EquipMask.MissileAmmo,
|
||||
});
|
||||
h.Objects.MoveItem(arrows, Player, -1, EquipMask.MissileAmmo);
|
||||
h.AddContained(bow, item =>
|
||||
{
|
||||
item.Type = ItemType.MissileWeapon;
|
||||
item.CombatUse = 2;
|
||||
item.AmmoType = 1;
|
||||
item.ValidLocations = EquipMask.MissileWeapon;
|
||||
});
|
||||
|
||||
Assert.True(h.Controller.ActivateItem(bow));
|
||||
h.Objects.MoveItem(sword, Player, 0, EquipMask.None);
|
||||
Assert.Equal(
|
||||
new[] { (sword, Player, 0), (arrows, Player, 0) },
|
||||
h.Puts);
|
||||
|
||||
h.Objects.MoveItem(arrows, Player, 0, EquipMask.None);
|
||||
Assert.Equal(new[] { (bow, (uint)EquipMask.MissileWeapon) }, h.Wields);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlocksUseOfShield_matchesRetailCombatUseAndAmmoRules()
|
||||
{
|
||||
Assert.True(AutoWieldController.BlocksUseOfShield(new ClientObject
|
||||
{
|
||||
CombatUse = 2,
|
||||
AmmoType = 1,
|
||||
}));
|
||||
Assert.False(AutoWieldController.BlocksUseOfShield(new ClientObject
|
||||
{
|
||||
CombatUse = 2,
|
||||
AmmoType = 0,
|
||||
}));
|
||||
Assert.True(AutoWieldController.BlocksUseOfShield(new ClientObject
|
||||
{
|
||||
CombatUse = 5,
|
||||
}));
|
||||
Assert.True(AutoWieldController.BlocksUseOfShield(new ClientObject
|
||||
{
|
||||
Type = ItemType.Caster,
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InventoryDragOutsideUi_sendsDropAndMovesToWorldOptimistically()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -188,6 +188,22 @@ public sealed class CreateObjectTests
|
|||
Assert.Equal((byte)2, parsed.Value.CombatUse);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_AmmoTypeFlag_CapturesValue()
|
||||
{
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x5000000Eu,
|
||||
name: "Bow",
|
||||
itemType: (uint)ItemType.MissileWeapon,
|
||||
weenieFlags: 0x00000100u,
|
||||
ammoType: 1);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal((ushort)1, parsed.Value.AmmoType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ParentedChild_CapturesPlacementParentAndPositionSequence()
|
||||
{
|
||||
|
|
@ -647,6 +663,7 @@ public sealed class CreateObjectTests
|
|||
byte? radarBlipColor = null,
|
||||
byte? radarBehavior = null,
|
||||
byte? combatUse = null,
|
||||
ushort? ammoType = null,
|
||||
ushort movementSeq = 0,
|
||||
uint? placementId = null,
|
||||
uint? parentGuid = null,
|
||||
|
|
@ -712,7 +729,7 @@ public sealed class CreateObjectTests
|
|||
if ((weenieFlags & 0x00000001u) != 0) WriteString16L(bytes, pluralName ?? "");
|
||||
if ((weenieFlags & 0x00000002u) != 0) bytes.Add(itemsCapacity ?? 0); // ItemsCapacity u8
|
||||
if ((weenieFlags & 0x00000004u) != 0) bytes.Add(containersCapacity ?? 0); // ContainersCapacity u8
|
||||
if ((weenieFlags & 0x00000100u) != 0) WriteU16(bytes, 0); // AmmoType u16
|
||||
if ((weenieFlags & 0x00000100u) != 0) WriteU16(bytes, ammoType ?? 0); // AmmoType u16
|
||||
if ((weenieFlags & 0x00000008u) != 0) WriteU32(bytes, value ?? 0u); // Value u32
|
||||
if ((weenieFlags & 0x00000010u) != 0) WriteU32(bytes, useability ?? 0u); // Usable u32
|
||||
if ((weenieFlags & 0x00000020u) != 0) // UseRadius f32
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ public sealed class ObjectTableWiringTests
|
|||
RadarBehavior = 3,
|
||||
ObjectDescriptionFlags = 0x20800200u,
|
||||
CombatUse = 2,
|
||||
AmmoType = 1,
|
||||
PluralName = "Iron Swords",
|
||||
PetOwnerId = 0x50000001u,
|
||||
};
|
||||
|
|
@ -110,6 +111,7 @@ public sealed class ObjectTableWiringTests
|
|||
Assert.Equal((byte)3, d.RadarBehavior);
|
||||
Assert.Equal(0x20800200u, d.PublicWeenieBitfield);
|
||||
Assert.Equal((byte)2, d.CombatUse);
|
||||
Assert.Equal((ushort)1, d.AmmoType);
|
||||
Assert.Equal("Iron Swords", d.PluralName);
|
||||
Assert.Equal(0x50000001u, d.PetOwnerId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,6 +310,22 @@ public sealed class ClientObjectTableTests
|
|||
Assert.Equal("Pyreal Scarabs", table.Get(0x500000B9u)!.GetAppropriateName());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_AmmoType_PreservesWireValueForAutoWieldCompatibility()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
|
||||
table.Ingest(FullWeenie(0x500000BAu, name: "Bow",
|
||||
type: ItemType.MissileWeapon) with
|
||||
{
|
||||
CombatUse = 2,
|
||||
AmmoType = 1,
|
||||
});
|
||||
|
||||
Assert.Equal((byte)2, table.Get(0x500000BAu)!.CombatUse);
|
||||
Assert.Equal((ushort)1, table.Get(0x500000BAu)!.AmmoType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_RadarMetadata_PatchesOnlyPresentWireFields()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue