acdream/src/AcDream.Runtime/GameRuntimeCommands.cs
Erik 369729f06a feat(runtime): FA2 -- RuntimeFellowshipState + RuntimeAllegianceState sibling J-owners
Two new sibling Runtime owners under GameRuntime per the Slice-J
pattern (D2): RuntimeFellowshipState is session-scoped (a new
RuntimeGenerationResetStage.Fellowship clears it at every generation
reset, matching the ExternalContainer precedent); RuntimeAllegianceState
survives reconnect behind a HasServerSeed-style one-way latch and
participates in NO reset stage (its data persists like a real
disconnect does not sever allegiance membership).

Fellowship: full-update REPLACE, incremental-fellow UPSERT, self-vs-
other quit/dismiss removal (self clears the whole snapshot), disband
clear, and retail's leader hand-off rule for the Quit button
(RequiresLeaderHandoffBeforeQuit -- the current leader quitting WITHOUT
disbanding must send 0x0290 AssignNewLeader before 0x00A3, lane B
§2.5/§3.6).

Allegiance: seeded by AllegianceUpdate (0x0020, always self) and,
self-gated on TargetGuid == playerGuid(), by AllegianceInfoResponse
(0x027C); wraps the FA1-assembled flat AllegianceMemberRecord list
directly (AllegianceTree was deleted at FA1 -- nothing left to wrap).

Both apply the full 8-edit J-owner template: construction + fault
points + Owner/View properties + CaptureOwnership + a new
GameRuntimeTeardownStage pair (FellowshipDisposed/AllegianceDisposed,
stage count 11->13) + RuntimeGameplayOwnershipSnapshot inclusion +
RuntimeStateCheckpoint/trace fields. IRuntimeFellowshipCommands/
IRuntimeAllegianceCommands added to IGameRuntimeCommands and
implemented on DirectGameRuntimeCommandAdapter. No IRuntimeEventObserver
member added (D2) -- consumers poll Snapshot.Revision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 01:31:54 +02:00

399 lines
10 KiB
C#

namespace AcDream.Runtime;
public enum RuntimeCommandStatus
{
Accepted,
Inactive,
StaleGeneration,
Unsupported,
Rejected,
}
public readonly record struct RuntimeCommandResult(
RuntimeCommandStatus Status,
RuntimeGenerationToken Generation,
uint ResultObjectId = 0u)
{
public bool Accepted => Status == RuntimeCommandStatus.Accepted;
}
public enum RuntimeSessionStartStatus
{
Disabled,
MissingCredentials,
NoCharacters,
Connected,
Deferred,
Failed,
Inactive,
StaleGeneration,
}
public readonly record struct RuntimeSessionStartResult(
RuntimeSessionStartStatus Status,
RuntimeGenerationToken Generation,
uint CharacterId = 0u,
string CharacterName = "",
Exception? Error = null);
public enum RuntimeSelectionCommand
{
SelectClosestHostile,
SelectPrevious,
ExamineSelected,
UseSelected,
PickUpSelected,
}
public enum RuntimeCombatCommand
{
ToggleMode,
}
public enum RuntimeMovementCommand
{
ToggleRunLock,
Stop,
Ready,
Sit,
Crouch,
Sleep,
}
public enum RuntimeChatChannel
{
Say,
Tell,
Fellowship,
Allegiance,
Vassals,
Patron,
Monarch,
CoVassals,
General,
Trade,
LookingForGroup,
Roleplay,
Society,
Olthoi,
/// <summary>
/// Campaign CH slice CH3 (2026-08-09): the legacy
/// <c>ChatChannel.AllegianceBroadcast (0x02000000)</c> — the monarch/
/// speaker-permission broadcast bound to retail's <c>@ab</c> verb.
/// Distinct from <see cref="Allegiance"/>, which is ALWAYS the Turbine
/// room now (retail's <c>@a</c> binds unconditionally to
/// <c>DoTurbineChat_Allegiance</c> once Turbine chat starts — see the
/// research doc §4.3/§6.7).
/// </summary>
AllegianceBroadcast,
}
public readonly record struct RuntimeChatCommand(
RuntimeChatChannel Channel,
string Text,
string? TargetName = null);
public enum RuntimePortalCommand
{
RecallLifestone,
RecallMarketplace,
RecallHouse,
RecallMansion,
}
public interface IRuntimeSessionCommands
{
RuntimeSessionStartResult Start(RuntimeGenerationToken expectedGeneration);
RuntimeSessionStartResult Reconnect(RuntimeGenerationToken expectedGeneration);
RuntimeTeardownAcknowledgement Stop(RuntimeGenerationToken expectedGeneration);
}
public interface IRuntimeSelectionCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimeSelectionCommand command);
RuntimeCommandResult SelectObject(
RuntimeGenerationToken expectedGeneration,
uint objectId);
RuntimeCommandResult Clear(
RuntimeGenerationToken expectedGeneration);
}
public interface IRuntimeCombatCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimeCombatCommand command);
RuntimeCommandResult ExecuteAttack(
RuntimeGenerationToken expectedGeneration,
in Gameplay.RuntimeCombatAttackInput command);
}
public readonly record struct RuntimeMagicCommand(uint SpellId);
public interface IRuntimeMagicCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeMagicCommand command);
}
public interface IRuntimeMovementCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimeMovementCommand command);
RuntimeCommandResult SetIntent(
RuntimeGenerationToken expectedGeneration,
in Gameplay.MovementInput input);
RuntimeCommandResult ClearIntent(
RuntimeGenerationToken expectedGeneration);
}
public interface IRuntimeChatCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeChatCommand command);
}
public interface IRuntimePortalCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
RuntimePortalCommand command);
}
public readonly record struct RuntimeShortcutCommand(
int Index,
uint ObjectId,
uint SpellId);
public interface IRuntimeInventoryStateCommands
{
RuntimeCommandResult AddShortcut(
RuntimeGenerationToken expectedGeneration,
in RuntimeShortcutCommand command);
RuntimeCommandResult RemoveShortcut(
RuntimeGenerationToken expectedGeneration,
int index);
}
public interface IRuntimeSpellbookCommands
{
RuntimeCommandResult AddFavorite(
RuntimeGenerationToken expectedGeneration,
int tabIndex,
int position,
uint spellId);
RuntimeCommandResult RemoveFavorite(
RuntimeGenerationToken expectedGeneration,
int tabIndex,
uint spellId);
RuntimeCommandResult SetFilter(
RuntimeGenerationToken expectedGeneration,
uint filters);
RuntimeCommandResult ForgetSpell(
RuntimeGenerationToken expectedGeneration,
uint spellId);
RuntimeCommandResult SetDesiredComponent(
RuntimeGenerationToken expectedGeneration,
uint componentId,
uint amount);
RuntimeCommandResult ClearDesiredComponents(
RuntimeGenerationToken expectedGeneration);
}
public enum RuntimeAdvancementKind
{
Attribute,
Vital,
Skill,
TrainSkill,
}
public readonly record struct RuntimeAdvancementCommand(
RuntimeAdvancementKind Kind,
uint StatId,
ulong Cost);
public interface IRuntimeCharacterCommands
{
RuntimeCommandResult Advance(
RuntimeGenerationToken expectedGeneration,
in RuntimeAdvancementCommand command);
/// <summary>
/// Retail <c>SetSingleCharacterOption (GameActionType 0x0005)</c> —
/// toggles one character option. Campaign CH slice CH3 (2026-08-09)
/// replaced the former <c>SetOptions1</c> (the malformed, callerless
/// full-Options1-blob 0x01A1 builder) with this: the only wire message
/// that actually changes Turbine room membership for the six
/// <c>ListenTo*Chat</c> ids.
/// </summary>
RuntimeCommandResult SetSingleOption(
RuntimeGenerationToken expectedGeneration,
uint optionId,
bool value);
/// <summary>
/// Retail <c>CPlayerModule::SaveToServer(force: 0)</c> — the batched-
/// option blob-flush verb (Campaign OP slice OP1, 2026-08-10). Flushes
/// <c>SetCharacterOptions (0x01A1)</c> iff the module is dirty; a clean
/// module sends nothing, matching retail exactly (both its own
/// production call sites — Apply, logout — pass <c>force = 0</c>).
/// </summary>
RuntimeCommandResult SaveOptions(RuntimeGenerationToken expectedGeneration);
}
public enum RuntimeFriendCommandKind
{
Add,
Remove,
Clear,
RequestLegacyList,
}
public readonly record struct RuntimeFriendCommand(
RuntimeFriendCommandKind Kind,
uint CharacterId = 0u,
string? Name = null);
public enum RuntimeSquelchScope
{
Character,
Account,
Global,
}
public readonly record struct RuntimeSquelchCommand(
RuntimeSquelchScope Scope,
bool Add,
uint CharacterId = 0u,
string? Name = null,
uint MessageType = 0u);
public interface IRuntimeSocialCommands
{
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeFriendCommand command);
RuntimeCommandResult Execute(
RuntimeGenerationToken expectedGeneration,
in RuntimeSquelchCommand command);
}
// ── Fellowship / Allegiance (Campaign FA slice FA2, 2026-08-12) ────────────
/// <summary>
/// Generation-gated fellowship outbound actions. <see cref="Quit"/>
/// implements retail's leader hand-off rule itself (lane B §2.5/§3.6): the
/// current leader quitting WITHOUT disbanding sends <c>0x0290</c> (assign
/// a non-leader fellow) BEFORE <c>0x00A3</c> — see
/// <c>RuntimeFellowshipState.RequiresLeaderHandoffBeforeQuit</c>.
/// <see cref="SetPanelOpen"/> is D4's <c>0x00A6</c> panel-visibility
/// declaration, the command the fellowship page's show/hide seam calls
/// (FA3+); without it ACE freezes the roster's vitals stream at join.
/// </summary>
public interface IRuntimeFellowshipCommands
{
RuntimeCommandResult Create(
RuntimeGenerationToken expectedGeneration,
string fellowshipName,
bool shareXp);
RuntimeCommandResult Recruit(
RuntimeGenerationToken expectedGeneration,
uint targetGuid);
RuntimeCommandResult Dismiss(
RuntimeGenerationToken expectedGeneration,
uint targetGuid);
RuntimeCommandResult Quit(
RuntimeGenerationToken expectedGeneration,
bool disband);
RuntimeCommandResult AssignLeader(
RuntimeGenerationToken expectedGeneration,
uint newLeaderGuid);
RuntimeCommandResult SetOpen(
RuntimeGenerationToken expectedGeneration,
bool isOpen);
RuntimeCommandResult SetPanelOpen(
RuntimeGenerationToken expectedGeneration,
bool panelOpen);
}
/// <summary>Generation-gated allegiance outbound actions.</summary>
public interface IRuntimeAllegianceCommands
{
RuntimeCommandResult Swear(
RuntimeGenerationToken expectedGeneration,
uint patronGuid);
RuntimeCommandResult Break(
RuntimeGenerationToken expectedGeneration,
uint targetGuid);
RuntimeCommandResult Kick(
RuntimeGenerationToken expectedGeneration,
uint vassalGuid);
/// <summary><c>0x027B</c> — empty name queries self.</summary>
RuntimeCommandResult RequestInfo(
RuntimeGenerationToken expectedGeneration,
string playerName);
/// <summary><c>0x001F</c> — the allegiance panel's subscribe/unsubscribe toggle.</summary>
RuntimeCommandResult SetUpdateSubscription(
RuntimeGenerationToken expectedGeneration,
bool on);
}
public interface IGameRuntimeCommands
{
IRuntimeSessionCommands Session { get; }
IRuntimeSelectionCommands Selection { get; }
IRuntimeCombatCommands Combat { get; }
IRuntimeMagicCommands Magic { get; }
IRuntimeMovementCommands Movement { get; }
IRuntimeChatCommands Chat { get; }
IRuntimePortalCommands Portal { get; }
IRuntimeInventoryStateCommands InventoryState { get; }
IRuntimeSpellbookCommands Spellbook { get; }
IRuntimeCharacterCommands Character { get; }
IRuntimeSocialCommands Social { get; }
IRuntimeFellowshipCommands Fellowship { get; }
IRuntimeAllegianceCommands Allegiance { get; }
}