feat(inventory): port retail weapon switching

Sequence primary weapon replacement through the server-confirmed AutoWield transaction: return the occupied weapon to the player pack, wait for its move event, then wield the requested item. Route authoritative CombatMode property updates so peace stays peace and war adopts the new weapon stance without client-synthesized animation.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-12 23:06:31 +02:00
parent 8e9c538519
commit 17b5712d53
14 changed files with 736 additions and 155 deletions

View file

@ -10,6 +10,17 @@ namespace AcDream.Core.Items;
/// </summary>
public readonly record struct ContainerContentEntry(uint Guid, uint ContainerType);
/// <summary>
/// Server rejection of an inventory move request. <paramref name="RolledBack"/>
/// is true when the table restored a locally optimistic move; false for
/// server-confirmed transactions which deliberately left the local projection
/// unchanged while waiting.
/// </summary>
public readonly record struct MoveRequestFailure(
uint ItemId,
uint WeenieError,
bool RolledBack);
/// <summary>
/// The client's table of every server object (retail <c>weenie_object_table</c> /
/// <c>CObjectMaint</c>). Resolve by guid via <c>Get</c>.
@ -74,12 +85,22 @@ public sealed class ClientObjectTable
/// </summary>
public event Action<ClientObject>? MoveRolledBack;
/// <summary>
/// Fires for every InventoryServerSaveFailed response, including requests
/// which did not mutate the table optimistically. Transaction coordinators
/// use this to abandon server-confirmed multi-step moves without timers.
/// </summary>
public event Action<MoveRequestFailure>? MoveRequestFailed;
/// <summary>Fires when an object is removed from the session.</summary>
public event Action<ClientObject>? ObjectRemoved;
/// <summary>Fires when an object's properties are updated (typically after Appraise).</summary>
public event Action<ClientObject>? ObjectUpdated;
/// <summary>Fires after all session object and pending-move state is flushed.</summary>
public event Action? Cleared;
/// <summary>PropertyInt.UiEffects (ACE enum value 18) — the icon effect bitfield;
/// the typed mirror <see cref="UpdateIntProperty"/> maintains on
/// <see cref="ClientObject.Effects"/>.</summary>
@ -254,6 +275,19 @@ public sealed class ClientObjectTable
return true;
}
/// <summary>
/// Reconcile an InventoryServerSaveFailed response and publish the failure
/// even when no optimistic snapshot exists. Retail's blocked AutoWield path
/// waits for the server before changing its local item lists, so failure is
/// still a meaningful transaction event in that case.
/// </summary>
public bool RejectMove(uint itemId, uint weenieError)
{
bool rolledBack = RollbackMove(itemId);
MoveRequestFailed?.Invoke(new MoveRequestFailure(itemId, weenieError, rolledBack));
return rolledBack;
}
/// <summary>
/// Handle a server-driven remove (destroyed item, dropped into 3D
/// space, stolen, etc).
@ -592,5 +626,6 @@ public sealed class ClientObjectTable
_containers.Clear();
_containerIndex.Clear();
_pendingMoves.Clear(); // B-Drag: drop in-flight optimistic snapshots (a recycled guid must not mis-rollback)
Cleared?.Invoke();
}
}