feat(net): FA2 -- fellowship/allegiance outbound wrappers + inbound wiring
Adds the missing WorldSession.Send* link for every FA1 fellowship/ allegiance builder (SendFellowshipCreate/Quit/Dismiss/Recruit/ UpdateRequest/AssignNewLeader/ChangeOpenness, SendAllegianceSwear/ Break/Kick/UpdateRequest) and 15 new GameEventWiring.WireAll delegate holes covering the 11 S->C fellowship/allegiance events. Delegate holes (not state-object params) because Core.Net cannot reference AcDream.Runtime, matching the onCharacterOptions/onConfirmationRequest precedent. Fixes a real bug found during implementation: GameEventDispatcher. Dispatch invokes only the single most-recently-registered handler per GameEventType (RegisterOwned REPLACES, it does not chain-invoke) -- contradicts the seam doc's "the dispatcher supports multiple owned handlers per type" claim. A literal second registrar.Register call for AllegianceInfoResponse would have silently killed the already-live `@allegiance info` chat-text output the moment a caller supplied the new self-gated Runtime callback. Both behaviors are folded into the ONE existing registration instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1bb707e248
commit
1c40104896
3 changed files with 437 additions and 1 deletions
|
|
@ -96,7 +96,28 @@ public static class GameEventWiring
|
|||
// WeenieErrorMessages table, just without the SpewBox split) so
|
||||
// every existing caller compiles and behaves unchanged.
|
||||
Action<string, RetailLogTextType>? onInterfaceText = null,
|
||||
Func<bool>? accepting = null)
|
||||
Func<bool>? accepting = null,
|
||||
// Campaign FA slice FA2 (2026-08-12): fellowship + allegiance
|
||||
// delegate holes. RuntimeFellowshipState/RuntimeAllegianceState are
|
||||
// AcDream.Runtime types — Core.Net cannot reference AcDream.Runtime
|
||||
// directly, so these are delegate holes exactly like every other
|
||||
// Runtime-owned sink above (docs/research/2026-08-11-fa-acdream-seams.md
|
||||
// §2.2). All optional/nullable so every existing caller compiles
|
||||
// unchanged.
|
||||
Action<GameEvents.FellowshipFullUpdate>? onFellowshipFullUpdate = null,
|
||||
Action<GameEvents.FellowshipUpdateFellow>? onFellowshipUpdateFellow = null,
|
||||
Action<uint /*quitterGuid*/>? onFellowshipQuit = null,
|
||||
Action<uint /*dismissedGuid*/>? onFellowshipDismiss = null,
|
||||
Action? onFellowshipDisband = null,
|
||||
Action<ClientCommandResponses.AllegianceUpdate>? onAllegianceUpdate = null,
|
||||
// Self-gated: fires only when the response's TargetGuid is the
|
||||
// local player's own guid (see the registration below) — a
|
||||
// by-name @allegiance info query on ANOTHER player must not
|
||||
// overwrite the Runtime allegiance owner's own-tree snapshot.
|
||||
Action<ClientCommandResponses.AllegianceInfoResponse>? onAllegianceInfoResponseSelf = null,
|
||||
Action<uint /*weenieError*/>? onAllegianceUpdateDone = null,
|
||||
Action<uint /*weenieError*/>? onAllegianceUpdateAborted = null,
|
||||
Action<GameEvents.AllegianceLoginNotification>? onAllegianceLoginNotification = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dispatcher);
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
|
@ -189,14 +210,114 @@ public static class GameEventWiring
|
|||
foreach (string line in ClientCommandResponses.FormatAvailableHousesLines(houses.Value))
|
||||
chat.OnSystemMessage(line, chatType: 0u);
|
||||
});
|
||||
// Campaign FA slice FA2 (2026-08-12) correction: GameEventDispatcher.
|
||||
// Dispatch invokes ONLY the single most-recently-registered handler
|
||||
// per GameEventType (GameEventDispatcher.cs:95-117) — a second
|
||||
// registrar.Register(GameEventType.AllegianceInfoResponse, ...)
|
||||
// call does NOT chain-invoke the first; it REPLACES it (the
|
||||
// superseded handler only comes back if the newer registration's
|
||||
// token is later disposed). The seam doc's "the dispatcher supports
|
||||
// multiple owned handlers per type — both fire" claim
|
||||
// (docs/research/2026-08-11-fa-acdream-seams.md §2.3) does not hold
|
||||
// against the actual dispatcher; a literal second Register call
|
||||
// here would have silently killed the already-live `@allegiance
|
||||
// info` chat-text output the moment a caller supplied
|
||||
// onAllegianceInfoResponseSelf. Both behaviors are folded into this
|
||||
// ONE registration instead. Self-gated on TargetGuid == playerGuid()
|
||||
// so a by-name query against ANOTHER player's allegiance never
|
||||
// overwrites the Runtime owner's own-tree snapshot; skipped
|
||||
// entirely when no playerGuid resolver was supplied (matches every
|
||||
// other playerGuid-gated site above).
|
||||
registrar.Register(GameEventType.AllegianceInfoResponse, e =>
|
||||
{
|
||||
var info = ClientCommandResponses.ParseAllegianceInfoResponse(e.Payload.Span);
|
||||
if (info is null) return;
|
||||
foreach (string line in ClientCommandResponses.FormatAllegianceInfoLines(info.Value))
|
||||
chat.OnSystemMessage(line, chatType: 0u);
|
||||
if (onAllegianceInfoResponseSelf is not null
|
||||
&& playerGuid is not null
|
||||
&& info.Value.TargetGuid == playerGuid())
|
||||
{
|
||||
onAllegianceInfoResponseSelf(info.Value);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Fellowship (Campaign FA slice FA2, 2026-08-12) ──────────────
|
||||
if (onFellowshipFullUpdate is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.FellowshipFullUpdate, e =>
|
||||
{
|
||||
var update = GameEvents.ParseFellowshipFullUpdate(e.Payload.Span);
|
||||
if (update is not null) onFellowshipFullUpdate(update.Value);
|
||||
});
|
||||
}
|
||||
if (onFellowshipUpdateFellow is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.FellowshipUpdateFellow, e =>
|
||||
{
|
||||
var update = GameEvents.ParseFellowshipUpdateFellow(e.Payload.Span);
|
||||
if (update is not null) onFellowshipUpdateFellow(update.Value);
|
||||
});
|
||||
}
|
||||
if (onFellowshipQuit is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.FellowshipQuit, e =>
|
||||
{
|
||||
var quit = GameEvents.ParseFellowshipQuit(e.Payload.Span);
|
||||
if (quit is not null) onFellowshipQuit(quit.Value.QuitterGuid);
|
||||
});
|
||||
}
|
||||
if (onFellowshipDismiss is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.FellowshipDismiss, e =>
|
||||
{
|
||||
var dismiss = GameEvents.ParseFellowshipDismiss(e.Payload.Span);
|
||||
if (dismiss is not null) onFellowshipDismiss(dismiss.Value.DismissedGuid);
|
||||
});
|
||||
}
|
||||
if (onFellowshipDisband is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.FellowshipDisband, e =>
|
||||
{
|
||||
if (GameEvents.ParseFellowshipDisband(e.Payload.Span))
|
||||
onFellowshipDisband();
|
||||
});
|
||||
}
|
||||
|
||||
// ── Allegiance (Campaign FA slice FA2, 2026-08-12) ──────────────
|
||||
if (onAllegianceUpdate is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.AllegianceUpdate, e =>
|
||||
{
|
||||
var update = ClientCommandResponses.ParseAllegianceUpdate(e.Payload.Span);
|
||||
if (update is not null) onAllegianceUpdate(update.Value);
|
||||
});
|
||||
}
|
||||
if (onAllegianceUpdateDone is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.AllegianceUpdateDone, e =>
|
||||
{
|
||||
var code = GameEvents.ParseAllegianceUpdateDone(e.Payload.Span);
|
||||
if (code is not null) onAllegianceUpdateDone(code.Value);
|
||||
});
|
||||
}
|
||||
if (onAllegianceUpdateAborted is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.AllegianceUpdateAborted, e =>
|
||||
{
|
||||
var code = GameEvents.ParseAllegianceUpdateAborted(e.Payload.Span);
|
||||
if (code is not null) onAllegianceUpdateAborted(code.Value);
|
||||
});
|
||||
}
|
||||
if (onAllegianceLoginNotification is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.AllegianceLoginNotification, e =>
|
||||
{
|
||||
var notice = GameEvents.ParseAllegianceLoginNotification(e.Payload.Span);
|
||||
if (notice is not null) onAllegianceLoginNotification(notice.Value);
|
||||
});
|
||||
}
|
||||
|
||||
if (onConfirmationRequest is not null)
|
||||
{
|
||||
registrar.Register(GameEventType.CharacterConfirmationRequest, e =>
|
||||
|
|
|
|||
|
|
@ -2315,6 +2315,94 @@ public sealed class WorldSession : IDisposable
|
|||
SendGameAction(ClientCommandRequests.BuildAllegianceInfoRequest(seq, playerName));
|
||||
}
|
||||
|
||||
// ── Campaign FA slice FA2 (2026-08-12): fellowship + allegiance
|
||||
// outbound wrappers. SocialActions/AllegianceRequests ship the byte
|
||||
// builders (repaired/added in FA1); this is the missing
|
||||
// NextGameActionSequence() + SendGameAction() link every other
|
||||
// outbound family already has (docs/research/2026-08-11-fa-acdream-seams.md
|
||||
// §3.2).
|
||||
|
||||
/// <summary>Send retail fellowship create (0x00A2).</summary>
|
||||
public void SendFellowshipCreate(string fellowshipName, bool shareXp)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipCreate(seq, fellowshipName, shareXp));
|
||||
}
|
||||
|
||||
/// <summary>Send retail fellowship quit / disband (0x00A3).</summary>
|
||||
public void SendFellowshipQuit(bool disband)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipQuit(seq, disband));
|
||||
}
|
||||
|
||||
/// <summary>Send retail fellowship dismiss (0x00A4).</summary>
|
||||
public void SendFellowshipDismiss(uint targetGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipDismiss(seq, targetGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail fellowship recruit (0x00A5).</summary>
|
||||
public void SendFellowshipRecruit(uint targetGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipRecruit(seq, targetGuid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send retail fellowship-panel visibility declaration (0x00A6) — D4:
|
||||
/// gates ACE's <c>0x02C0</c> member-vitals stream (docs/research/
|
||||
/// 2026-08-11-fa-fellowship-wire.md §4.5).
|
||||
/// </summary>
|
||||
public void SendFellowshipUpdateRequest(bool panelOpen)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipUpdateRequest(seq, panelOpen));
|
||||
}
|
||||
|
||||
/// <summary>Send retail fellowship leadership transfer (0x0290).</summary>
|
||||
public void SendFellowshipAssignNewLeader(uint newLeaderGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipAssignNewLeader(seq, newLeaderGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail fellowship openness toggle (0x0291).</summary>
|
||||
public void SendFellowshipChangeOpenness(bool isOpen)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildFellowshipChangeOpenness(seq, isOpen));
|
||||
}
|
||||
|
||||
/// <summary>Send retail allegiance swear (0x001D).</summary>
|
||||
public void SendAllegianceSwear(uint patronGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(AllegianceRequests.BuildSwear(seq, patronGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail allegiance break (0x001E) — targets your own patron.</summary>
|
||||
public void SendAllegianceBreak(uint targetGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(AllegianceRequests.BuildBreak(seq, targetGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail allegiance kick (0x001E) — targets a vassal.</summary>
|
||||
public void SendAllegianceKick(uint vassalGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(AllegianceRequests.BuildKick(seq, vassalGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail allegiance-panel subscribe/unsubscribe (0x001F).</summary>
|
||||
public void SendAllegianceUpdateRequest(bool on)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(AllegianceRequests.BuildAllegianceUpdateRequest(seq, on));
|
||||
}
|
||||
|
||||
/// <summary>Send retail @hslist <type> (0x0270).</summary>
|
||||
public void SendListAvailableHouses(uint houseType)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue