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
|
|
@ -19,6 +19,9 @@ internal sealed class AutoWieldController : IDisposable
|
|||
| EquipMask.Held
|
||||
| EquipMask.TwoHanded;
|
||||
|
||||
private const byte CombatUseMissile = 0x02;
|
||||
private const byte CombatUseTwoHanded = 0x05;
|
||||
|
||||
private static readonly EquipMask[] AutoEquipOrder =
|
||||
{
|
||||
EquipMask.HeadWear,
|
||||
|
|
@ -105,6 +108,20 @@ internal sealed class AutoWieldController : IDisposable
|
|||
if (blocker is not null)
|
||||
return BeginWeaponReplacement(item.ObjectId, blocker.ObjectId);
|
||||
|
||||
// Retail checks secondary blockers only after the primary weapon
|
||||
// slot is vacant, then re-enters AutoWield after each confirmed
|
||||
// move. ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0.
|
||||
if (BlocksUseOfShield(item)
|
||||
&& GetEquippedObjectAtLocation(
|
||||
EquipMask.Shield, priority: 0, item.ObjectId) is { } shield)
|
||||
return BeginWeaponReplacement(item.ObjectId, shield.ObjectId);
|
||||
|
||||
if (item.AmmoType is > 0
|
||||
&& GetEquippedObjectAtLocation(
|
||||
EquipMask.MissileAmmo, priority: 0, item.ObjectId) is { } ammo
|
||||
&& ammo.AmmoType != item.AmmoType)
|
||||
return BeginWeaponReplacement(item.ObjectId, ammo.ObjectId);
|
||||
|
||||
return SendWield(item, weaponLocation);
|
||||
}
|
||||
|
||||
|
|
@ -270,6 +287,14 @@ internal sealed class AutoWieldController : IDisposable
|
|||
&& (item.WielderId == player || item.ContainerId == player);
|
||||
}
|
||||
|
||||
internal static bool BlocksUseOfShield(ClientObject item)
|
||||
{
|
||||
byte combatUse = item.CombatUse ?? 0;
|
||||
return combatUse == CombatUseTwoHanded
|
||||
|| (combatUse == CombatUseMissile && item.AmmoType is > 0)
|
||||
|| item.Type.HasFlag(ItemType.Caster);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
|
|
|
|||
|
|
@ -205,7 +205,10 @@ public static class CreateObject
|
|||
// ACCWeenieObject::GetObjectName(NAME_APPROPRIATE) selects this for stacks.
|
||||
string? PluralName = null,
|
||||
// PublicWeenieDesc._pet_owner, gated by second-header flag 0x8.
|
||||
uint? PetOwnerId = null);
|
||||
uint? PetOwnerId = null,
|
||||
// PublicWeenieDesc._ammoType, gated by WeenieHeader flag 0x100.
|
||||
// AMMO_NONE is the explicit wire value zero; null means absent.
|
||||
ushort? AmmoType = null);
|
||||
|
||||
/// <summary>
|
||||
/// The relevant subset of the server-sent <c>MovementData</c> /
|
||||
|
|
@ -772,6 +775,7 @@ public static class CreateObject
|
|||
byte? radarBlipColor = null;
|
||||
byte? radarBehavior = null;
|
||||
byte? combatUse = null;
|
||||
ushort? ammoType = null;
|
||||
uint iconOverlayId = 0;
|
||||
uint iconUnderlayId = 0;
|
||||
uint uiEffects = 0;
|
||||
|
|
@ -810,6 +814,7 @@ public static class CreateObject
|
|||
if ((weenieFlags & 0x00000100u) != 0) // AmmoType u16
|
||||
{
|
||||
if (body.Length - pos < 2) throw new FormatException("trunc AmmoType");
|
||||
ammoType = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos));
|
||||
pos += 2;
|
||||
}
|
||||
if ((weenieFlags & 0x00000008u) != 0) // Value u32
|
||||
|
|
@ -1020,7 +1025,8 @@ public static class CreateObject
|
|||
RadarBlipColor: radarBlipColor, RadarBehavior: radarBehavior,
|
||||
CombatUse: combatUse,
|
||||
PluralName: pluralName,
|
||||
PetOwnerId: petOwnerId);
|
||||
PetOwnerId: petOwnerId,
|
||||
AmmoType: ammoType);
|
||||
|
||||
// Local helper: if we ran out of fields past PhysicsData, still
|
||||
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion).
|
||||
|
|
|
|||
|
|
@ -89,5 +89,6 @@ public static class ObjectTableWiring
|
|||
PublicWeenieBitfield: s.ObjectDescriptionFlags,
|
||||
CombatUse: s.CombatUse,
|
||||
PluralName: s.PluralName,
|
||||
PetOwnerId: s.PetOwnerId);
|
||||
PetOwnerId: s.PetOwnerId,
|
||||
AmmoType: s.AmmoType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,8 @@ public sealed class WorldSession : IDisposable
|
|||
byte? RadarBehavior = null,
|
||||
byte? CombatUse = null,
|
||||
string? PluralName = null,
|
||||
uint? PetOwnerId = null);
|
||||
uint? PetOwnerId = null,
|
||||
ushort? AmmoType = null);
|
||||
|
||||
/// <summary>
|
||||
/// Projects the wire-level CreateObject result into the stable session
|
||||
|
|
@ -187,7 +188,8 @@ public sealed class WorldSession : IDisposable
|
|||
RadarBehavior: parsed.RadarBehavior,
|
||||
CombatUse: parsed.CombatUse,
|
||||
PluralName: parsed.PluralName,
|
||||
PetOwnerId: parsed.PetOwnerId);
|
||||
PetOwnerId: parsed.PetOwnerId,
|
||||
AmmoType: parsed.AmmoType);
|
||||
|
||||
/// <summary>Fires when the session finishes parsing a CreateObject.</summary>
|
||||
public event Action<EntitySpawn>? EntitySpawned;
|
||||
|
|
|
|||
|
|
@ -189,6 +189,8 @@ public sealed class ClientObject
|
|||
public uint PetOwnerId { get; set; }
|
||||
/// <summary>Retail <c>PublicWeenieDesc._combatUse</c>; null when its header flag was absent.</summary>
|
||||
public byte? CombatUse { get; set; }
|
||||
/// <summary>Retail <c>PublicWeenieDesc._ammoType</c>; zero is <c>AMMO_NONE</c>.</summary>
|
||||
public ushort? AmmoType { get; set; }
|
||||
/// <summary>Client-side trade state used by ItemHolder legality gates.</summary>
|
||||
public int TradeState { get; set; }
|
||||
/// <summary>Resolved membership in SpellComponentTable (retail IsComponentPack).</summary>
|
||||
|
|
@ -255,7 +257,8 @@ public readonly record struct WeenieData(
|
|||
uint? PublicWeenieBitfield = null,
|
||||
byte? CombatUse = null,
|
||||
string? PluralName = null,
|
||||
uint? PetOwnerId = null);
|
||||
uint? PetOwnerId = null,
|
||||
ushort? AmmoType = null);
|
||||
|
||||
/// <summary>
|
||||
/// Retail ITEM_USEABLE helpers (acclient.h:6478, ItemUses::* at 0x004fccd0).
|
||||
|
|
|
|||
|
|
@ -436,6 +436,7 @@ public sealed class ClientObjectTable
|
|||
if (d.PublicWeenieBitfield is { } bitfield) obj.PublicWeenieBitfield = bitfield;
|
||||
if (d.PetOwnerId is { } petOwnerId) obj.PetOwnerId = petOwnerId;
|
||||
if (d.CombatUse is { } combatUse) obj.CombatUse = combatUse;
|
||||
if (d.AmmoType is { } ammoType) obj.AmmoType = ammoType;
|
||||
if (d.RadarBlipColor is { } radarBlipColor) obj.RadarBlipColor = radarBlipColor;
|
||||
if (d.RadarBehavior is { } radarBehavior) obj.RadarBehavior = radarBehavior;
|
||||
if (d.ItemsCapacity is { } ic) obj.ItemsCapacity = ic;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue