feat(net): Phase F.1 GameEvent (0xF7B0) envelope dispatcher
Implements the inbound GameEvent routing layer — the single biggest
network-protocol gap per r08 (94 sub-opcodes, zero handled before).
WorldSession now detects 0xF7B0, parses the 16-byte header (guid +
gameEventSequence + eventType), and forwards to a pluggable
GameEventDispatcher.
Added:
- GameEventEnvelope record + TryParse with layout from
ACE GameEventMessage.cs.
- GameEventType enum: all 94 S→C sub-opcodes from
ACE.Server.Network.GameEvent.GameEventType, named per ACE conventions.
- GameEventDispatcher: handler registry + unhandled-counts bag for
diagnostics ("which server events are firing that we don't parse?").
Handlers invoked synchronously on the decode thread; thrown exceptions
are swallowed + logged to stderr so one bad handler can't take down
the packet loop.
- GameEvents parsers: ChannelBroadcast, Tell, TransientMessage,
PopupString, WeenieError (+ WithString), UpdateHealth, PingResponse,
MagicUpdateSpell. Each returns a typed record or null on malformed
payload. String16L helper matches the existing CharacterList pattern
(u16 length + ASCII bytes + 4-byte pad).
- WorldSession.GameEvents property exposing the dispatcher so
GameWindow / UI / chat can register handlers at startup.
Wired into WorldSession.ProcessDatagram: new `else if (op ==
GameEventEnvelope.Opcode)` branch with TryParse + Dispatch.
Tests (13 new):
- Envelope: valid round-trip, wrong outer opcode, too-short body.
- Dispatcher: handler invoked, unhandled count, exception isolation,
unregister + rollover to unhandled.
- Event parsers: ChannelBroadcast, Tell, UpdateHealth, WeenieError,
Transient, MagicUpdateSpell.
Total: 521 tests pass (up from 508).
With this dispatcher in place, Phase F.2 (items + appraise), F.3 (combat
+ damage), F.4 (spell cast state machine), chat UI, allegiance, quest
tracker — all of which depend on GameEvent handling — are unblocked.
Ref: r08 §4 (GameEvent sub-opcode table), §2 (envelope wire shape).
Ref: ACE GameEventMessage.cs / GameEventType.cs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d3165f99d7
commit
d86fd08011
6 changed files with 656 additions and 0 deletions
|
|
@ -115,6 +115,14 @@ public sealed class WorldSession : IDisposable
|
|||
/// <summary>Raised every time the state machine transitions.</summary>
|
||||
public event Action<State>? StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Phase F.1: inbound 0xF7B0 GameEvent dispatcher. Each sub-opcode
|
||||
/// handler is registered here (by GameWindow / UI layer / chat
|
||||
/// system) and routed on each incoming GameEvent. Unhandled
|
||||
/// sub-opcodes are counted for diagnostic overlays.
|
||||
/// </summary>
|
||||
public GameEventDispatcher GameEvents { get; } = new();
|
||||
|
||||
public State CurrentState { get; private set; } = State.Disconnected;
|
||||
|
||||
/// <summary>Movement sequence counters for outbound MoveToState/AutonomousPosition.</summary>
|
||||
|
|
@ -479,6 +487,15 @@ public sealed class WorldSession : IDisposable
|
|||
posUpdate.Value.Velocity));
|
||||
}
|
||||
}
|
||||
else if (op == GameEventEnvelope.Opcode)
|
||||
{
|
||||
// Phase F.1: 0xF7B0 is the GameEvent envelope. Parse the
|
||||
// header (guid + sequence + eventType) and dispatch to the
|
||||
// registered handler for that sub-opcode. Unregistered
|
||||
// types get counted for diagnostic overlays.
|
||||
var env = GameEventEnvelope.TryParse(body);
|
||||
if (env is not null) GameEvents.Dispatch(env.Value);
|
||||
}
|
||||
else if (op == 0xF751u) // PlayerTeleport — server is moving us through a portal
|
||||
{
|
||||
// Phase B.3: holtburger opcodes.rs confirms 0xF751 is the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue