Fills TS-82's Summary placeholder with a faithful port of gmCGSummaryPage (name field with NameInputFilter + the retail commit-on-focus-lost/submit dispatch + the >32-char ID_CharGen_NameTooLong reject-and-revert path, the REAL three-row-template listbox confirmed against the installed EoR dat before writing any page code, and Summary's own independent gmCG3DView preview instance wired through a second ChargenPreviewController pair mirroring the Appearance page's exact composition shape). Ports CharGenState::RandomizeCharacter and its six sub-primitives into RuntimeCharacterCreationState — not approximated: the RandInt/RollDice semantics are independently confirmed from both the decompiled RNG bodies and the CharGenStateVtbl union struct in acclient.h. Three consumers: the chargen screen's open-roll (retiring AP-214's honest-blank deviation and reproducing the Appearance page's gender-flip-on-init quirk), the Summary page's Random button (behind the retail randomize-warning confirm), and the Appearance page's Random button (narrowing AP-212 to just Heritage/Profession/Town's still-approximated rolls and Skills' still-unported RandomizeSkills). Wires the Finish button (previously ghosted) with retail's NoName/ CreditWarning dialog pair, adds the F12 amendment's HeritageOrGenderUnset local refusal to TryBeginFinish (register AP-223) as a defensive backstop now that the screen-open roll normally makes it unreachable, and wires the four ID_Character_Err_* rejection dialogs for the 0xF643 response codes CC3 already parsed but nothing displayed. Register: TS-82 retired, AP-214 retired, AP-212 narrowed, AP-223/224/225 filed (heritage/gender Finish refusal, Summary's two-bucket skill-list narrowing, the 32-vs-33 name-length threshold reconciliation). Runtime 1722/0 (was 1713), App 5240/3 skips (was 5223/3), Headless 166/0 unchanged, full solution Release build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
524 lines
15 KiB
C#
524 lines
15 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,
|
|
/// <summary>
|
|
/// Campaign LA slice LA2: mirrors
|
|
/// <see cref="Session.LiveSessionStartStatus.ProbeComplete"/> — a probe
|
|
/// session connected, reported its roster, and gracefully disconnected
|
|
/// before EnterWorld. A SUCCESS outcome for the headless process host's
|
|
/// exit-code mapping, not a failure.
|
|
/// </summary>
|
|
ProbeComplete,
|
|
AwaitingCharacterSelection,
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// ── Character creation (Campaign CC slice CC3, 2026-08-15) ─────────────────
|
|
|
|
/// <summary>
|
|
/// Generation-gated character-creation (CharGenState) outbound actions —
|
|
/// lands beside <see cref="IGameRuntimeCommands.CharacterSelection"/>, the
|
|
/// same command family shape.
|
|
/// </summary>
|
|
public interface IRuntimeCharacterCreationCommands
|
|
{
|
|
RuntimeCommandResult SelectHeritage(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint heritageId);
|
|
|
|
RuntimeCommandResult SelectGender(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint genderKey);
|
|
|
|
RuntimeCommandResult SelectTemplate(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint templateIndex);
|
|
|
|
RuntimeCommandResult SetAttribute(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
Session.ChargenAttributeId attributeId,
|
|
int value);
|
|
|
|
RuntimeCommandResult SetAttributeLock(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
Session.ChargenAttributeId attributeId,
|
|
bool locked);
|
|
|
|
RuntimeCommandResult TrainSkill(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint skillId);
|
|
|
|
RuntimeCommandResult SpecializeSkill(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint skillId);
|
|
|
|
RuntimeCommandResult UntrainSkill(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint skillId);
|
|
|
|
RuntimeCommandResult SetAppearanceIndex(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
Session.ChargenAppearanceSlot slot,
|
|
uint index);
|
|
|
|
RuntimeCommandResult SetShade(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
Session.ChargenShadeSlot slot,
|
|
double value);
|
|
|
|
RuntimeCommandResult SelectStartArea(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
int startAreaIndex);
|
|
|
|
RuntimeCommandResult SetName(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
string name);
|
|
|
|
RuntimeCommandResult SetSlot(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
uint slot);
|
|
|
|
/// <summary>Retail's Finish button (<c>gmCharGenMainUI::DoFinish @
|
|
/// 0x004E9170</c>). On acceptance the request is already on the wire;
|
|
/// the Ok/rejection reply arrives asynchronously as a status delta —
|
|
/// see <see cref="Session.RuntimeCharacterCreationState"/>.
|
|
/// <paramref name="confirmUnspentCredits"/> is retail's <c>arg2 == 0</c>
|
|
/// — the credit-warning dialog's own confirm click — skipping the
|
|
/// unspent-attribute-credits gate; the ordinary caller passes
|
|
/// <c>false</c> (retail's <c>arg2 = 1</c> button click), which shows
|
|
/// that warning instead of sending when credits remain. See
|
|
/// <see cref="Session.RuntimeCharacterCreationState.TryBeginFinish"/>'s
|
|
/// doc comment for the full citation.</summary>
|
|
RuntimeCommandResult Finish(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
bool confirmUnspentCredits = false);
|
|
|
|
RuntimeCommandResult AcknowledgeRejection(
|
|
RuntimeGenerationToken expectedGeneration);
|
|
|
|
// ── Campaign CC slice CC5: RandomizeCharacter port ──────────────────
|
|
|
|
/// <summary>Retail's ctor-time <c>CharGenState::RandomizeCharacter</c>
|
|
/// roll (mirrored at the App layer's screen-open edge) and the Summary
|
|
/// page's Random button (<c>gmCharGenMainUI::DoRandom</c> case 5, behind
|
|
/// the caller's own <c>ID_CharGen_RandomizeWarning</c> confirmation) —
|
|
/// see <see cref="Session.RuntimeCharacterCreationState.TryRandomizeCharacter"/>.</summary>
|
|
RuntimeCommandResult RandomizeCharacter(
|
|
RuntimeGenerationToken expectedGeneration);
|
|
|
|
/// <summary>The Appearance page's Random button while its Face sub-tab
|
|
/// is showing (<c>gmCharGenMainUI::DoRandom</c> case 3's <c>else</c>
|
|
/// arm) — see
|
|
/// <see cref="Session.RuntimeCharacterCreationState.TryRandomizeAppearance"/>.</summary>
|
|
RuntimeCommandResult RandomizeAppearance(
|
|
RuntimeGenerationToken expectedGeneration);
|
|
|
|
/// <summary>The Appearance page's Random button while its Clothes
|
|
/// sub-tab is showing (<c>gmCharGenMainUI::DoRandom</c> case 3's
|
|
/// <c>RandomizeClothing(state, 1)</c> arm) — see
|
|
/// <see cref="Session.RuntimeCharacterCreationState.TryRandomizeClothing"/>.</summary>
|
|
RuntimeCommandResult RandomizeClothing(
|
|
RuntimeGenerationToken expectedGeneration);
|
|
}
|
|
|
|
public interface IGameRuntimeCommands
|
|
{
|
|
IRuntimeSessionCommands Session { get; }
|
|
|
|
Session.IRuntimeCharacterSelectionCommands CharacterSelection =>
|
|
throw new NotSupportedException(
|
|
"This command adapter does not project character selection.");
|
|
|
|
IRuntimeCharacterCreationCommands CharacterCreation =>
|
|
throw new NotSupportedException(
|
|
"This command adapter does not project character creation.");
|
|
|
|
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; }
|
|
}
|