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
|
|
@ -1,5 +1,6 @@
|
|||
using AcDream.App.Interaction;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.Core.CharGen;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
|
|
@ -21,6 +22,7 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
private readonly GameRuntime _runtime;
|
||||
private readonly CurrentGameRuntimeCommandAdapter _commands;
|
||||
private readonly CharacterSelectionProjection _characterSelection;
|
||||
private readonly CharacterCreationProjection _characterCreation;
|
||||
private readonly IDisposable _hostLease;
|
||||
private readonly object _subscriptionGate = new();
|
||||
private readonly HashSet<AdapterSubscription> _subscriptions = [];
|
||||
|
|
@ -42,6 +44,7 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
try
|
||||
{
|
||||
_characterSelection = new CharacterSelectionProjection(this);
|
||||
_characterCreation = new CharacterCreationProjection(this);
|
||||
_commands = new CurrentGameRuntimeCommandAdapter(
|
||||
runtime.Session,
|
||||
sessionHost,
|
||||
|
|
@ -93,6 +96,8 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
public IRuntimeChatView Chat => _runtime.Chat;
|
||||
public IRuntimeCharacterSelectionView CharacterSelection =>
|
||||
_characterSelection;
|
||||
public IRuntimeCharacterCreationView CharacterCreation =>
|
||||
_characterCreation;
|
||||
public IRuntimeFellowshipView Fellowship => _runtime.Fellowship;
|
||||
public IRuntimeAllegianceView Allegiance => _runtime.Allegiance;
|
||||
public IRuntimeActionView Actions => _runtime.Actions;
|
||||
|
|
@ -105,6 +110,10 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
_characterSelection;
|
||||
IRuntimeCharacterSelectionCommands IGameRuntimeCommands.CharacterSelection =>
|
||||
_characterSelection;
|
||||
public IRuntimeCharacterCreationCommands CharacterCreationCommands =>
|
||||
_characterCreation;
|
||||
IRuntimeCharacterCreationCommands IGameRuntimeCommands.CharacterCreation =>
|
||||
_characterCreation;
|
||||
public IRuntimeSelectionCommands Selection => _commands;
|
||||
public IRuntimeCombatCommands Combat => _commands;
|
||||
public IRuntimeMagicCommands Magic => _commands;
|
||||
|
|
@ -281,6 +290,83 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
}
|
||||
}
|
||||
|
||||
// ── Campaign CC slice CC4: character creation, same shape as the
|
||||
// character-selection block above. ────────────────────────────────
|
||||
|
||||
private RuntimeCharacterCreationSnapshot CharacterCreationSnapshot()
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
if (IsActive)
|
||||
return _runtime.CharacterCreation.Snapshot;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private ChargenSkillAdvancementClass CharacterCreationSkillLevel(uint skillId)
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
return IsActive
|
||||
? _runtime.CharacterCreation.GetSkillLevel(skillId)
|
||||
: ChargenSkillAdvancementClass.Inactive;
|
||||
}
|
||||
}
|
||||
|
||||
private ChargenOptions CharacterCreationOptions()
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
return IsActive
|
||||
? _runtime.CharacterCreation.Options
|
||||
: ChargenOptions.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private IDisposable SubscribeCharacterCreation(
|
||||
IRuntimeCharacterCreationObserver observer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(observer);
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var gated = new AdapterCharacterCreationObserver(this, observer);
|
||||
IDisposable runtimeSubscription =
|
||||
_runtime.CharacterCreation.Subscribe(gated);
|
||||
var subscription = new AdapterSubscription(
|
||||
this,
|
||||
runtimeSubscription);
|
||||
_subscriptions.Add(subscription);
|
||||
return subscription;
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeCommandResult ExecuteCharacterCreation(
|
||||
Func<IRuntimeCharacterCreationCommands, RuntimeCommandResult> execute)
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
if (!IsActive)
|
||||
{
|
||||
return new RuntimeCommandResult(
|
||||
RuntimeCommandStatus.Inactive,
|
||||
_runtime.Generation);
|
||||
}
|
||||
return execute(_runtime.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private void ForwardCharacterCreation(
|
||||
IRuntimeCharacterCreationObserver observer,
|
||||
in RuntimeCharacterCreationDelta delta)
|
||||
{
|
||||
lock (_subscriptionGate)
|
||||
{
|
||||
if (IsActive)
|
||||
observer.OnCharacterCreationChanged(in delta);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CharacterSelectionProjection(
|
||||
CurrentGameRuntimeAdapter owner)
|
||||
: IRuntimeCharacterSelectionView,
|
||||
|
|
@ -350,6 +436,126 @@ internal sealed class CurrentGameRuntimeAdapter
|
|||
owner.ForwardCharacterSelection(observer, in delta);
|
||||
}
|
||||
|
||||
private sealed class CharacterCreationProjection(
|
||||
CurrentGameRuntimeAdapter owner)
|
||||
: IRuntimeCharacterCreationView,
|
||||
IRuntimeCharacterCreationCommands
|
||||
{
|
||||
public RuntimeCharacterCreationSnapshot Snapshot =>
|
||||
owner.CharacterCreationSnapshot();
|
||||
|
||||
public ChargenSkillAdvancementClass GetSkillLevel(uint skillId) =>
|
||||
owner.CharacterCreationSkillLevel(skillId);
|
||||
|
||||
public ChargenOptions Options => owner.CharacterCreationOptions();
|
||||
|
||||
public IDisposable Subscribe(IRuntimeCharacterCreationObserver observer) =>
|
||||
owner.SubscribeCharacterCreation(observer);
|
||||
|
||||
public RuntimeCommandResult SelectHeritage(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint heritageId) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SelectHeritage(expectedGeneration, heritageId));
|
||||
|
||||
public RuntimeCommandResult SelectGender(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint genderKey) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SelectGender(expectedGeneration, genderKey));
|
||||
|
||||
public RuntimeCommandResult SelectTemplate(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint templateIndex) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SelectTemplate(expectedGeneration, templateIndex));
|
||||
|
||||
public RuntimeCommandResult SetAttribute(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
ChargenAttributeId attributeId,
|
||||
int value) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetAttribute(expectedGeneration, attributeId, value));
|
||||
|
||||
public RuntimeCommandResult SetAttributeLock(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
ChargenAttributeId attributeId,
|
||||
bool locked) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetAttributeLock(expectedGeneration, attributeId, locked));
|
||||
|
||||
public RuntimeCommandResult TrainSkill(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint skillId) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.TrainSkill(expectedGeneration, skillId));
|
||||
|
||||
public RuntimeCommandResult SpecializeSkill(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint skillId) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SpecializeSkill(expectedGeneration, skillId));
|
||||
|
||||
public RuntimeCommandResult UntrainSkill(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint skillId) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.UntrainSkill(expectedGeneration, skillId));
|
||||
|
||||
public RuntimeCommandResult SetAppearanceIndex(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
ChargenAppearanceSlot slot,
|
||||
uint index) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetAppearanceIndex(expectedGeneration, slot, index));
|
||||
|
||||
public RuntimeCommandResult SetShade(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
ChargenShadeSlot slot,
|
||||
double value) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetShade(expectedGeneration, slot, value));
|
||||
|
||||
public RuntimeCommandResult SelectStartArea(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
int startAreaIndex) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SelectStartArea(expectedGeneration, startAreaIndex));
|
||||
|
||||
public RuntimeCommandResult SetName(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
string name) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetName(expectedGeneration, name));
|
||||
|
||||
public RuntimeCommandResult SetSlot(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint slot) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.SetSlot(expectedGeneration, slot));
|
||||
|
||||
public RuntimeCommandResult Finish(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
bool confirmUnspentCredits = false) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.Finish(expectedGeneration, confirmUnspentCredits));
|
||||
|
||||
public RuntimeCommandResult AcknowledgeRejection(
|
||||
RuntimeGenerationToken expectedGeneration) =>
|
||||
owner.ExecuteCharacterCreation(
|
||||
commands => commands.AcknowledgeRejection(expectedGeneration));
|
||||
}
|
||||
|
||||
private sealed class AdapterCharacterCreationObserver(
|
||||
CurrentGameRuntimeAdapter owner,
|
||||
IRuntimeCharacterCreationObserver observer)
|
||||
: IRuntimeCharacterCreationObserver
|
||||
{
|
||||
public void OnCharacterCreationChanged(
|
||||
in RuntimeCharacterCreationDelta delta) =>
|
||||
owner.ForwardCharacterCreation(observer, in delta);
|
||||
}
|
||||
|
||||
private sealed class AdapterSubscription(
|
||||
CurrentGameRuntimeAdapter owner,
|
||||
IDisposable runtimeSubscription) : IDisposable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue