feat(app): Campaign CC slice CC4 — chargen screen shell + Heritage/Profession/Skills/Town pages
Mounts gmCharGenMainUI (enum 0x10000039, root 0x100003CC) via CharacterCreationUiController/CharacterCreationUiMountCoordinator, cloning CharacterManagementUiController's recipe. Master shell ports SetProgressState @0x004e7a10 (Olthoi tab-hide + redirect) and ListenToElementMessage @0x004e9450 (Back/Next/Finish/Help/Exit/Random nav) verbatim, with free tab navigation over all six pages. Heritage, Profession, Skills, and Town pages bind to CC3's RuntimeCharacterCreationState commands; Appearance and Summary mount as content-inert placeholders for CC6b/CC5. Live-DAT probing (CharacterCreationLiveDatTests) found two widget- mapping surprises the decomp's DynamicCast hints don't predict: the Profession slider's value field imports as an editable UiField (wired for direct numeric entry), and the avail/health/stamina/mana/credits displays author as UIElement_Button hosts whose Type-12 value child is swallowed by UiButton.ConsumesDatChildren — substituted with the button's own Label. No new DatWidgetFactory widget types were needed. Threads the installed DAT's real ChargenOptions into Runtime via the new RuntimeCharacterCreationState.InstallOptions, called from ContentEffectsAudioCompositionPhase.Compose (mirrors InstallSpellMetadata's pattern); headless keeps ChargenOptions.Empty unchanged. Wires CC3's F14 status-hook gap (ApplyCharacterCreated/ ApplyCreationFailed) to SessionStatusWriter for both graphical and headless hosts, and adds the CharacterCreation view/command seam through CurrentGameRuntimeAdapter and DeferredGameRuntimeStateCommands alongside CharacterSelection's existing shape. Register: AD-101/102/103, AP-212/213, TS-82 filed for the auto-gender- select interim default, the omitted ToD-account gate, the button-Label widget substitution, the Random-button approximation, the flat-listbox Skills simplification, and the Appearance/Summary placeholders. Runtime 1713/0 (was 1707), App 5117/13 skips (was 5101/6), Headless 165/0 unaffected, full solution Release build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3a6b7e3115
commit
0e71d3b829
27 changed files with 3344 additions and 12 deletions
|
|
@ -533,6 +533,8 @@ public sealed class GameRuntime
|
|||
public IRuntimeChatView Chat => CommunicationOwner.View;
|
||||
public IRuntimeCharacterSelectionView CharacterSelection =>
|
||||
Session.CharacterSelection;
|
||||
public IRuntimeCharacterCreationView CharacterCreation =>
|
||||
Session.CharacterCreation;
|
||||
public IRuntimeFellowshipView Fellowship => FellowshipOwner.View;
|
||||
public IRuntimeAllegianceView Allegiance => AllegianceOwner.View;
|
||||
|
||||
|
|
|
|||
|
|
@ -280,6 +280,12 @@ public interface IGameRuntimeView
|
|||
throw new NotSupportedException(
|
||||
"This runtime view does not project character selection.");
|
||||
|
||||
/// <summary>Campaign CC slice CC4: mirrors <see cref="CharacterSelection"/>'s
|
||||
/// default-throw shape.</summary>
|
||||
IRuntimeCharacterCreationView CharacterCreation =>
|
||||
throw new NotSupportedException(
|
||||
"This runtime view does not project character creation.");
|
||||
|
||||
IRuntimeFellowshipView Fellowship { get; }
|
||||
|
||||
IRuntimeAllegianceView Allegiance { get; }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,16 @@ public sealed record LiveSessionHostBindings(
|
|||
/// fan-out, this exists so a status writer can emit the
|
||||
/// <c>enteredWorld</c> event's <c>characterId</c> field.</summary>
|
||||
Action<LiveSessionCharacterSelection> CharacterEntered,
|
||||
LoginCommandSequence? LoginCommands = null);
|
||||
LoginCommandSequence? LoginCommands = null,
|
||||
/// <summary>Campaign CC slice CC4: forwards
|
||||
/// <see cref="ILiveSessionLifecycleHost.ApplyCharacterCreated"/> —
|
||||
/// hosts wire this to <c>SessionStatusWriter.CharacterCreated</c>.
|
||||
/// Default no-op.</summary>
|
||||
Action<RuntimeCharacterCreationIdentity>? CharacterCreated = null,
|
||||
/// <summary>Campaign CC slice CC4: forwards
|
||||
/// <see cref="ILiveSessionLifecycleHost.ApplyCreationFailed"/> — hosts
|
||||
/// wire this to <c>SessionStatusWriter.CreationFailed</c>.</summary>
|
||||
Action<RuntimeCharacterCreationRejection>? CreationFailed = null);
|
||||
|
||||
/// <summary>
|
||||
/// Runtime host for the one canonical <see cref="LiveSessionController"/>.
|
||||
|
|
@ -136,7 +145,9 @@ public sealed class LiveSessionHost
|
|||
Connected: bindings.Connected,
|
||||
Roster: bindings.Roster,
|
||||
Selected: ApplySelection,
|
||||
Entered: ApplyEnteredWorld));
|
||||
Entered: ApplyEnteredWorld,
|
||||
CharacterCreated: bindings.CharacterCreated,
|
||||
CreationFailed: bindings.CreationFailed));
|
||||
}
|
||||
|
||||
public WorldSession? CurrentSession => _controller.CurrentSession;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,15 @@ public sealed record LiveSessionLifecycleBindings(
|
|||
Action Connected,
|
||||
Action<LiveSessionRosterReport> Roster,
|
||||
Action<LiveSessionCharacterSelection> Selected,
|
||||
Action<LiveSessionCharacterSelection> Entered);
|
||||
Action<LiveSessionCharacterSelection> Entered,
|
||||
/// <summary>Campaign CC slice CC4: forwards
|
||||
/// <see cref="ILiveSessionLifecycleHost.ApplyCharacterCreated"/>. Default
|
||||
/// no-op preserves every existing positional/named construction site
|
||||
/// that predates this field.</summary>
|
||||
Action<RuntimeCharacterCreationIdentity>? CharacterCreated = null,
|
||||
/// <summary>Campaign CC slice CC4: forwards
|
||||
/// <see cref="ILiveSessionLifecycleHost.ApplyCreationFailed"/>.</summary>
|
||||
Action<RuntimeCharacterCreationRejection>? CreationFailed = null);
|
||||
|
||||
/// <summary>
|
||||
/// Focused adapter between the session lifetime owner and its composition
|
||||
|
|
@ -62,6 +70,12 @@ public sealed class LiveSessionLifecycleHost : ILiveSessionLifecycleHost
|
|||
public void ApplyEnteredWorld(LiveSessionCharacterSelection selection) =>
|
||||
_bindings.Entered(selection);
|
||||
|
||||
public void ApplyCharacterCreated(RuntimeCharacterCreationIdentity identity) =>
|
||||
_bindings.CharacterCreated?.Invoke(identity);
|
||||
|
||||
public void ApplyCreationFailed(RuntimeCharacterCreationRejection rejection) =>
|
||||
_bindings.CreationFailed?.Invoke(rejection);
|
||||
|
||||
public void DetachSession(WorldSession session)
|
||||
{
|
||||
if (!ReferenceEquals(_boundSession, session))
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ public sealed class RuntimeCharacterCreationState : IDisposable
|
|||
private readonly object _gate = new();
|
||||
private readonly CharacterCreationEventStream _events = new();
|
||||
private readonly ViewProjection _view;
|
||||
private readonly ChargenOptions _options;
|
||||
private ChargenOptions _options;
|
||||
private readonly Random _random;
|
||||
private RuntimeGenerationToken _generation;
|
||||
private bool _active;
|
||||
|
|
@ -321,6 +321,39 @@ public sealed class RuntimeCharacterCreationState : IDisposable
|
|||
|
||||
public ChargenOptions Options => _options;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign CC slice CC4: installs the real chargen options loaded from
|
||||
/// the installed DAT (<c>AcDream.Content.CharGen.ChargenTableReader.Load</c>)
|
||||
/// once the content host's DAT collection opens, mirroring the
|
||||
/// established "install immutable DAT metadata after construction"
|
||||
/// pattern (<c>RuntimeCharacterState.InstallSpellMetadata</c> ->
|
||||
/// <c>Spellbook.InstallMetadata</c>). GameWindow constructs the
|
||||
/// <see cref="AcDream.Runtime.GameRuntime"/> (and therefore this state,
|
||||
/// defaulted to <see cref="ChargenOptions.Empty"/>) before portal.dat is
|
||||
/// open; <c>ContentEffectsAudioCompositionPhase.Compose</c> calls this
|
||||
/// once DATs are published, always well before
|
||||
/// <see cref="Begin"/> — no character-selection/creation session can be
|
||||
/// active yet at that point in the composition sequence, so there is no
|
||||
/// concurrent read to race. Throws if called while a session is already
|
||||
/// active — a second install after chargen has started reading the
|
||||
/// first one would be a genuine caller bug, not a case to silently
|
||||
/// tolerate.
|
||||
/// </summary>
|
||||
public void InstallOptions(ChargenOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
lock (_gate)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (_active)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Chargen options cannot be installed while a character-creation session is active.");
|
||||
}
|
||||
_options = options;
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimeCharacterCreationSnapshot Snapshot
|
||||
{
|
||||
get
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue