merge: integrate main (D.2b paperdoll/inventory UI line) into the physics/collision branch

Brings the 134 D.2b UI commits onto the physics/collision development line so
main can fast-forward. Today's #149 (BSP-less static collision) + #150 (open
doors fully passable) + the full collision/streaming/dense-town-FPS arc meet
the paperdoll/inventory work.

# Conflicts:
#	docs/ISSUES.md
#	docs/architecture/retail-divergence-register.md
This commit is contained in:
Erik 2026-06-25 12:57:46 +02:00
commit 01594b4cfd
95 changed files with 17201 additions and 167 deletions

View file

@ -196,6 +196,25 @@ public sealed class WorldSession : IDisposable
/// </summary>
public event Action<ObjectIntPropertyUpdate>? ObjectIntPropertyUpdated;
/// <summary>Payload for <see cref="PlayerIntPropertyUpdated"/>: a PropertyInt change on
/// the player's OWN object (from PrivateUpdatePropertyInt 0x02CD — no guid on the wire).</summary>
public readonly record struct PlayerIntPropertyUpdate(uint Property, int Value);
/// <summary>Fires when the session parses a PrivateUpdatePropertyInt (0x02CD) — one
/// PropertyInt updated on the player. B-Wire routes EncumbranceVal (5) to the burden bar.</summary>
public event Action<PlayerIntPropertyUpdate>? PlayerIntPropertyUpdated;
/// <summary>Payload for <see cref="StackSizeUpdated"/>: SetStackSize (0x0197) — a stack's
/// count + value after a merge / split.</summary>
public readonly record struct StackSizeUpdate(uint Guid, int StackSize, int Value);
/// <summary>Fires when the session parses a SetStackSize (0x0197) top-level GameMessage.</summary>
public event Action<StackSizeUpdate>? StackSizeUpdated;
/// <summary>Fires when the session parses an InventoryRemoveObject (0x0024) — the guid left
/// the player's inventory view.</summary>
public event Action<uint>? InventoryObjectRemoved;
/// <summary>
/// Fires when the server sends a PlayerTeleport (0xF751) game message,
/// signalling that the player is entering portal space. The uint payload
@ -817,16 +836,18 @@ public sealed class WorldSession : IDisposable
}
else if (op == PickupEvent.Opcode)
{
// ACE sends PickupEvent (0xF74A) instead of DeleteObject
// when a player picks up a world item (Player_Tracking
// .RemoveTrackedObject with fromPickup=true). Downstream
// view-removal semantics are identical, so we adapt to
// DeleteObject.Parsed and reuse the existing handler.
// ACE sends PickupEvent (0xF74A) when an object leaves the 3-D world view
// (player picks it up, or a player unwields gear that goes back to their pack).
// The WEENIE is NOT destroyed — it is moving into a container. We adapt to
// DeleteObject.Parsed so the render/entity layer (GameWindow.OnLiveEntityDeleted)
// still removes the 3-D WorldEntity, but FromPickup = true tells ObjectTableWiring
// to skip the ClientObjectTable eviction (retail two-table model: PickupEvent
// targets object_table only; DeleteObject 0xF747 targets weenie_object_table).
var parsed = PickupEvent.TryParse(body);
if (parsed is not null)
EntityDeleted?.Invoke(
new DeleteObject.Parsed(
parsed.Value.Guid, parsed.Value.InstanceSequence));
parsed.Value.Guid, parsed.Value.InstanceSequence, FromPickup: true));
}
else if (op == UpdateMotion.Opcode)
{
@ -987,6 +1008,25 @@ public sealed class WorldSession : IDisposable
ObjectIntPropertyUpdated?.Invoke(
new ObjectIntPropertyUpdate(p.Value.Guid, p.Value.Property, p.Value.Value));
}
else if (op == PrivateUpdatePropertyInt.Opcode)
{
var p = PrivateUpdatePropertyInt.TryParse(body);
if (p is not null)
PlayerIntPropertyUpdated?.Invoke(
new PlayerIntPropertyUpdate(p.Value.Property, p.Value.Value));
}
else if (op == SetStackSize.Opcode)
{
var p = SetStackSize.TryParse(body);
if (p is not null)
StackSizeUpdated?.Invoke(
new StackSizeUpdate(p.Value.Guid, p.Value.StackSize, p.Value.Value));
}
else if (op == InventoryRemoveObject.Opcode)
{
var p = InventoryRemoveObject.TryParse(body);
if (p is not null) InventoryObjectRemoved?.Invoke(p.Value.Guid);
}
else if (op == GameEventEnvelope.Opcode)
{
// Phase F.1: 0xF7B0 is the GameEvent envelope. Parse the
@ -1169,6 +1209,61 @@ public sealed class WorldSession : IDisposable
SendGameAction(body);
}
/// <summary>Send AddShortcut (0x019C) — pin an item to toolbar slot <paramref name="index"/>.
/// Retail: CM_Character::Event_AddShortCut. Mirrors the SendChangeCombatMode pattern.</summary>
public void SendAddShortcut(uint index, uint objectGuid, ushort spellId = 0, ushort layer = 0)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildAddShortcut(seq, index, objectGuid, spellId, layer));
}
/// <summary>Send RemoveShortcut (0x019D) — clear toolbar slot <paramref name="index"/>.
/// Retail: CM_Character::Event_RemoveShortCut.</summary>
public void SendRemoveShortcut(uint index)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildRemoveShortcut(seq, index));
}
/// <summary>Send DropItem (0x001B) — drop an item on the ground.</summary>
public void SendDropItem(uint itemGuid)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildDropItem(seq, itemGuid));
}
/// <summary>Send GetAndWieldItem (0x001A) — equip an item to an equip slot.</summary>
public void SendGetAndWieldItem(uint itemGuid, uint equipMask)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildGetAndWieldItem(seq, itemGuid, equipMask));
}
/// <summary>Send NoLongerViewingContents (0x0195) — close a container view.</summary>
public void SendNoLongerViewingContents(uint containerGuid)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildNoLongerViewingContents(seq, containerGuid));
}
/// <summary>Send Use (0x0036) — open/use an object by guid (e.g. open a container in your
/// inventory). A direct wire send: opening a pack you already hold needs no autowalk, unlike
/// GameWindow.SendUse (the world-interaction path). Retail: CM_Physics::Event_Use.</summary>
public void SendUse(uint targetGuid)
{
uint seq = NextGameActionSequence();
SendGameAction(InteractRequests.BuildUse(seq, targetGuid));
}
/// <summary>Send PutItemInContainer (0x0019) — move an item into a container at a slot. placement
/// = the target slot (server packs/shifts); the drag-drop drop handler computes it. Retail:
/// CM_Inventory::Event_PutItemInContainer → ACE Player.HandleActionPutItemInContainer.</summary>
public void SendPutItemInContainer(uint itemGuid, uint containerGuid, int placement)
{
uint seq = NextGameActionSequence();
SendGameAction(InteractRequests.BuildPickUp(seq, itemGuid, containerGuid, placement));
}
/// <summary>Send retail QueryHealth (0x01BF). Server replies UpdateHealth (0x01C0).</summary>
/// <remarks>
/// Retail anchor: <c>CM_Combat::Event_QueryHealth</c> / <c>gmToolbarUI::HandleSelectionChanged:198635</c>