feat(B.5): pickup feedback chat line + toast ("You pick up the X.")
After B.5 shipped, the actual pickup was invisible feedback-wise: the item left the ground, ACE despawned it via PickupEvent (0xF74A), and the ItemRepository got updated — but the player had no visual acknowledgement that anything happened. The M1 demo's "pick up an item" target visually felt like the item just vanished into the void. Add a new EntityPickedUp event to WorldSession that fires from the PickupEvent (0xF74A) dispatch branch BEFORE EntityDeleted, so the subscriber can still read the entity's display name from _entitiesByServerGuid before the despawn handler clears it. GameWindow subscribes during the live-session wiring block and emits a retail-style system chat line plus a debug toast on every successful pickup, mirroring retail behavior (retail synthesized this line client-side; ACE doesn't echo it). Closes the M1 demo "pick up" target's visible-payoff gap.
This commit is contained in:
parent
cf22f9c031
commit
87ba5c9a98
2 changed files with 32 additions and 2 deletions
|
|
@ -1921,6 +1921,17 @@ public sealed class GameWindow : IDisposable
|
||||||
_liveSession.PlayerKilledReceived += pk =>
|
_liveSession.PlayerKilledReceived += pk =>
|
||||||
Chat.OnPlayerKilled(pk.DeathMessage, pk.VictimGuid, pk.KillerGuid);
|
Chat.OnPlayerKilled(pk.DeathMessage, pk.VictimGuid, pk.KillerGuid);
|
||||||
|
|
||||||
|
// B.5 polish (2026-05-14): surface successful pickups as a
|
||||||
|
// retail-style "You pick up the X." system chat line plus a
|
||||||
|
// toast. PickupEvent fires BEFORE the EntityDeleted despawn
|
||||||
|
// chain so the entity-name lookup still hits.
|
||||||
|
_liveSession.EntityPickedUp += parsed =>
|
||||||
|
{
|
||||||
|
string name = DescribeLiveEntity(parsed.Guid);
|
||||||
|
Chat.OnSystemMessage($"You pick up the {name}.", chatType: 0);
|
||||||
|
_debugVm?.AddToast($"Picked up: {name}");
|
||||||
|
};
|
||||||
|
|
||||||
_liveSession.TurbineChatReceived += parsed =>
|
_liveSession.TurbineChatReceived += parsed =>
|
||||||
{
|
{
|
||||||
if (parsed.Body is AcDream.Core.Net.Messages.TurbineChat.Payload.EventSendToRoom ev)
|
if (parsed.Body is AcDream.Core.Net.Messages.TurbineChat.Payload.EventSendToRoom ev)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,17 @@ public sealed class WorldSession : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DeleteObject.Parsed>? EntityDeleted;
|
public event Action<DeleteObject.Parsed>? EntityDeleted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the session parses a 0xF74A PickupEvent game message —
|
||||||
|
/// distinguishes a despawn caused by a player pickup from a generic
|
||||||
|
/// <c>DeleteObject</c> despawn. Fires BEFORE <see cref="EntityDeleted"/>
|
||||||
|
/// for the same guid so subscribers can read entity state (e.g. name)
|
||||||
|
/// before the despawn handler removes the entity. Useful for "You pick
|
||||||
|
/// up the <i>X</i>" chat/toast feedback that needs the entity's display
|
||||||
|
/// name at the moment of pickup.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<PickupEvent.Parsed>? EntityPickedUp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Payload for <see cref="MotionUpdated"/>: the server guid of the entity
|
/// Payload for <see cref="MotionUpdated"/>: the server guid of the entity
|
||||||
/// whose motion changed and its new server-side stance + forward command.
|
/// whose motion changed and its new server-side stance + forward command.
|
||||||
|
|
@ -717,13 +728,21 @@ public sealed class WorldSession : IDisposable
|
||||||
// ACE sends PickupEvent (0xF74A) instead of DeleteObject
|
// ACE sends PickupEvent (0xF74A) instead of DeleteObject
|
||||||
// when a player picks up a world item (Player_Tracking
|
// when a player picks up a world item (Player_Tracking
|
||||||
// .RemoveTrackedObject with fromPickup=true). Downstream
|
// .RemoveTrackedObject with fromPickup=true). Downstream
|
||||||
// view-removal semantics are identical, so we adapt to
|
// view-removal semantics are identical to DeleteObject, so
|
||||||
// DeleteObject.Parsed and reuse the existing handler.
|
// we adapt to DeleteObject.Parsed and reuse the existing
|
||||||
|
// EntityDeleted handler. We also fire a distinct
|
||||||
|
// EntityPickedUp event BEFORE the despawn so a subscriber
|
||||||
|
// can read entity-side state (e.g. its display name) for
|
||||||
|
// pickup-feedback chat / toast lines while the entity is
|
||||||
|
// still resident.
|
||||||
var parsed = PickupEvent.TryParse(body);
|
var parsed = PickupEvent.TryParse(body);
|
||||||
if (parsed is not null)
|
if (parsed is not null)
|
||||||
|
{
|
||||||
|
EntityPickedUp?.Invoke(parsed.Value);
|
||||||
EntityDeleted?.Invoke(
|
EntityDeleted?.Invoke(
|
||||||
new DeleteObject.Parsed(
|
new DeleteObject.Parsed(
|
||||||
parsed.Value.Guid, parsed.Value.InstanceSequence));
|
parsed.Value.Guid, parsed.Value.InstanceSequence));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (op == UpdateMotion.Opcode)
|
else if (op == UpdateMotion.Opcode)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue