feat(chargen): Campaign CC slice CC5 — Summary page, Finish flow, RandomizeCharacter port
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>
This commit is contained in:
parent
6114b2dda2
commit
34e3a534be
22 changed files with 2217 additions and 79 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.CharGen;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Session;
|
||||
|
||||
|
|
@ -43,6 +44,23 @@ public sealed record CharacterCreationRuntimeBindings(
|
|||
/// <see langword="null"/> degrades to the heritage's own DAT
|
||||
/// <c>Name</c> field instead of the full composed copy.</summary>
|
||||
Func<string, string?>? ResolveText = null,
|
||||
/// <summary>Campaign CC slice CC5: the Summary page's name field
|
||||
/// commit (<c>gmCGSummaryPage::ListenToElementMessage</c>'s
|
||||
/// <c>CharGenState::SetName</c> call).</summary>
|
||||
Func<string, RuntimeCommandResult>? SetName = null,
|
||||
/// <summary>CC5: dismisses a surfaced <c>0xF643</c> rejection after its
|
||||
/// dialog closes (<c>RuntimeCharacterCreationState.TryAcknowledgeRejection</c>).</summary>
|
||||
Func<RuntimeCommandResult>? AcknowledgeRejection = null,
|
||||
/// <summary>CC5: the screen-open roll
|
||||
/// (<c>gmCharGenMainUI</c>'s ctor-time <c>RandomizeCharacter</c> call)
|
||||
/// and the Summary page's Random button.</summary>
|
||||
Func<RuntimeCommandResult>? RandomizeCharacter = null,
|
||||
/// <summary>CC5: the Appearance page's Random button on its Face
|
||||
/// sub-tab.</summary>
|
||||
Func<RuntimeCommandResult>? RandomizeAppearance = null,
|
||||
/// <summary>CC5: the Appearance page's Random button on its Clothes
|
||||
/// sub-tab.</summary>
|
||||
Func<RuntimeCommandResult>? RandomizeClothing = null,
|
||||
bool OpenOnStart = false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,7 +124,23 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
Summary = 6,
|
||||
}
|
||||
|
||||
internal sealed record DialogStrings(string ExitWarning);
|
||||
internal sealed record DialogStrings(
|
||||
string ExitWarning,
|
||||
/// <summary>Campaign CC slice CC5: <c>ID_CharGen_NoNameWarning</c> —
|
||||
/// <c>DoFinish</c>'s empty-name refusal dialog.</summary>
|
||||
string NoNameWarning,
|
||||
/// <summary>CC5: <c>ID_CharGen_CreditWarning</c> —
|
||||
/// <c>MakeCreditWarningDialog</c>'s unspent-attribute-credits
|
||||
/// confirmation.</summary>
|
||||
string CreditWarning,
|
||||
/// <summary>CC5: <c>ID_CharGen_RandomizeWarning</c> —
|
||||
/// <c>MakeRandomizeWarningDialog</c>'s Summary-page Random
|
||||
/// confirmation.</summary>
|
||||
string RandomizeWarning,
|
||||
/// <summary>CC5: <c>ID_CharGen_NameTooLong</c> —
|
||||
/// <c>gmCGSummaryPage::DoNameLimitDialog</c>'s name-field-too-long
|
||||
/// notice.</summary>
|
||||
string NameTooLong);
|
||||
|
||||
private readonly UiRoot _host;
|
||||
private readonly ImportedLayout _layout;
|
||||
|
|
@ -138,6 +172,7 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
private readonly CharacterCreationSkillsPage _skillsPage;
|
||||
private readonly CharacterCreationTownPage _townPage;
|
||||
private readonly CharacterCreationAppearancePage _appearancePage;
|
||||
private readonly CharacterCreationSummaryPage _summaryPage;
|
||||
|
||||
private Vector2 _authoredCanvas;
|
||||
private RuntimeGenerationToken _lastGeneration;
|
||||
|
|
@ -147,6 +182,13 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
private bool _isOpen;
|
||||
private bool _openOnStartConsumed;
|
||||
private uint _exitDialogContext;
|
||||
// Campaign CC slice CC5: gmCharGenMainUI's own m_uiCreditWarningContext/
|
||||
// m_uiRandomizeWarningContext (0x004e8870/0x004e8a90) — same
|
||||
// one-outstanding-dialog-at-a-time guard shape as _exitDialogContext.
|
||||
private uint _creditWarningDialogContext;
|
||||
private uint _randomizeWarningDialogContext;
|
||||
private uint _noNameWarningDialogContext;
|
||||
private RuntimeCharacterCreationRejection? _lastShownRejection;
|
||||
private bool _suppressDialogCallbacks;
|
||||
private bool _disposed;
|
||||
|
||||
|
|
@ -227,18 +269,18 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
_skillsPage = new CharacterCreationSkillsPage(skillsPageRoot, bindings, templateResolver);
|
||||
_townPage = new CharacterCreationTownPage(townPageRoot, bindings);
|
||||
_appearancePage = new CharacterCreationAppearancePage(appearancePageRoot, bindings);
|
||||
_summaryPage = new CharacterCreationSummaryPage(
|
||||
summaryPageRoot, bindings, dialogs, strings.NameTooLong);
|
||||
|
||||
// gmCharGenMainUI::ListenToElementMessage @ 0x004e9450.
|
||||
_back.OnClick = OnBack;
|
||||
_next.OnClick = OnNext;
|
||||
// Finish (0x100003c8) stays ghosted this round: Summary
|
||||
// (0x100003d6) is CC5's placeholder, and DoFinish's real gate
|
||||
// sequence lives in RuntimeCharacterCreationState.TryBeginFinish —
|
||||
// wiring the button here without a Summary page to confirm/collect
|
||||
// the name would let a click reach the wire with an empty name and
|
||||
// silently refuse. No OnClick handler; _finish.Enabled stays false
|
||||
// (see ApplyProgressState).
|
||||
_finish.OnClick = null;
|
||||
// Finish (0x100003c8): retail enables it on Summary only
|
||||
// (ListenToElementMessage's case 0x100003c8 no-ops unless
|
||||
// m_eProgressState == ECG_SUMMARY @ 0x004e956f) — ApplyProgressState
|
||||
// gates _finish.Enabled the same way. OnFinish itself re-checks the
|
||||
// current page defensively (mirroring that same retail guard).
|
||||
_finish.OnClick = OnFinish;
|
||||
// Help (0x100003c9) is not handled in gmCharGenMainUI's own
|
||||
// ListenToElementMessage switch (case 0x100003c9 falls straight
|
||||
// through to the base UIFramework handler) — retail has no custom
|
||||
|
|
@ -280,6 +322,26 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
/// paperdoll's own outer-inventory-frame gate.</summary>
|
||||
internal bool IsAppearancePageVisible => Root.Visible && _appearancePageRoot.Visible;
|
||||
|
||||
/// <summary>Campaign CC slice CC5: the authored Summary-page viewport
|
||||
/// (<c>0x10000406</c>) — its OWN <c>gmCG3DView</c> instance, distinct
|
||||
/// from the Appearance page's (see this class's own class doc on the
|
||||
/// decomp citation).</summary>
|
||||
internal UiViewport? SummaryViewport => _summaryPage.Viewport;
|
||||
|
||||
/// <summary>CC5: the Summary preview's late-bound control surface. No
|
||||
/// zoom/rotate buttons bind against it — see
|
||||
/// <see cref="AcDream.App.Rendering.RetailSummaryPreviewPageVisibility"/>'s
|
||||
/// doc comment.</summary>
|
||||
internal AcDream.App.Rendering.IChargenPreviewControl? SummaryPreviewControl
|
||||
{
|
||||
get => _summaryPage.PreviewControl;
|
||||
set => _summaryPage.PreviewControl = value;
|
||||
}
|
||||
|
||||
/// <summary>CC5: same shape as <see cref="IsAppearancePageVisible"/>,
|
||||
/// for the Summary page.</summary>
|
||||
internal bool IsSummaryPageVisible => Root.Visible && _summaryPageRoot.Visible;
|
||||
|
||||
internal static CharacterCreationUiController? CreateDetached(
|
||||
UiRoot host,
|
||||
ImportedLayout layout,
|
||||
|
|
@ -404,6 +466,7 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
_skillsPage.Refresh(view, snapshot);
|
||||
_townPage.Refresh(view, snapshot);
|
||||
_appearancePage.Refresh(view, snapshot);
|
||||
_summaryPage.Refresh(view, snapshot);
|
||||
_lastGeneration = snapshot.Generation;
|
||||
_lastRevision = snapshot.Revision;
|
||||
}
|
||||
|
|
@ -426,9 +489,40 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
return;
|
||||
_isOpen = true;
|
||||
_host.DeclareFixedCanvas(this, _authoredCanvas);
|
||||
RollOpeningCharacter();
|
||||
ApplyProgressState(Page.Heritage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign CC slice CC5: ports <c>gmCharGenMainUI</c>'s ctor-time roll
|
||||
/// (<c>~0x004e81f5-0x004e8218</c>) — <c>CharGenState::RandomizeCharacter
|
||||
/// (state, hasToD) @ 0x005c6d80</c> runs BEFORE any page constructs,
|
||||
/// retiring AP-214's honest-blank deviation (retail's chargen screen is
|
||||
/// never actually blank on open). Then reproduces
|
||||
/// <c>gmCGAppearancePage::InitializePage</c>'s own gender-read-then-FLIP
|
||||
/// (<c>~0x004802da-0x00480303</c>, decomp-confirmed:
|
||||
/// <c>mGender==1 -> SetGender(2)</c>, <c>mGender==2 -> SetGender(1)</c>) —
|
||||
/// a genuine, always-firing retail quirk that runs immediately AFTER
|
||||
/// <c>RandomizeCharacter</c> already assigned a real (non-zero) gender.
|
||||
/// Retail's whole UI tree (every page, including Appearance) is
|
||||
/// reconstructed fresh each time the chargen screen opens, so the flip
|
||||
/// fires once per visit there; acdream's pages are built once at mount
|
||||
/// time and only toggle visibility, so <see cref="Open"/> — the closest
|
||||
/// analogue to "runs once per screen-open" this architecture has — is
|
||||
/// where both the roll and the flip belong.
|
||||
/// </summary>
|
||||
private void RollOpeningCharacter()
|
||||
{
|
||||
if (_bindings.RandomizeCharacter?.Invoke().Status != RuntimeCommandStatus.Accepted)
|
||||
return;
|
||||
|
||||
uint gender = _bindings.View()?.Snapshot.GenderKey ?? 0u;
|
||||
if (gender == 1u)
|
||||
_bindings.SelectGender(2u);
|
||||
else if (gender == 2u)
|
||||
_bindings.SelectGender(1u);
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
if (!_isOpen)
|
||||
|
|
@ -472,6 +566,7 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
_skillsPage.Dispose();
|
||||
_townPage.Dispose();
|
||||
_appearancePage.Dispose();
|
||||
_summaryPage.Dispose();
|
||||
_host.RemoveChild(Root);
|
||||
}
|
||||
}
|
||||
|
|
@ -535,10 +630,16 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
return;
|
||||
|
||||
// gmCharGenMainUI::DoRandom @ 0x004e7d70. Heritage/Profession/Town
|
||||
// are ported below; Skills' CharGenState::RandomizeSkills and the
|
||||
// Summary randomize-warning dialog have no CC3 primitive/page yet
|
||||
// this round — register AP-212 covers both gaps, and _random.Enabled
|
||||
// already keeps the control ghosted on those pages (ApplyProgressState).
|
||||
// still use the AP-212 uniform-pick approximation (unchanged this
|
||||
// slice); Appearance now delegates to the page's own real
|
||||
// RandomizeAppearance/RandomizeClothing primitives (CC5); Skills'
|
||||
// CharGenState::RandomizeSkills remains unported (AP-212, narrowed) —
|
||||
// _random.Enabled already keeps the control ghosted there
|
||||
// (ApplyProgressState). Summary goes through
|
||||
// gmCharGenMainUI::MakeRandomizeWarningDialog @ 0x004e8a90 first —
|
||||
// that dialog + its confirm-triggered RandomizeCharacter call are
|
||||
// gmCharGenMainUI's OWN methods in retail (not gmCGSummaryPage's),
|
||||
// so they live here on the master controller.
|
||||
IRuntimeCharacterCreationView? view = _bindings.View();
|
||||
if (view is null)
|
||||
return;
|
||||
|
|
@ -552,12 +653,44 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
case Page.Profession:
|
||||
_professionPage.Randomize(snapshot);
|
||||
break;
|
||||
case Page.Appearance:
|
||||
_appearancePage.Randomize();
|
||||
break;
|
||||
case Page.Town:
|
||||
_townPage.Randomize(view);
|
||||
break;
|
||||
case Page.Summary:
|
||||
ShowRandomizeWarningDialog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ports <c>gmCharGenMainUI::MakeRandomizeWarningDialog @
|
||||
/// 0x004e8a90</c> (<c>ID_CharGen_RandomizeWarning</c>) +
|
||||
/// <c>CloseRandomizeWarningDialog @ 0x004e8400</c>'s own confirm arm
|
||||
/// (<c>arg2 != 0 -> DoRandom(this)</c>, which on THIS second call
|
||||
/// takes <c>DoRandom</c>'s Summary case directly — no re-entrant
|
||||
/// warning, since the gate lives in the button-click dispatcher above,
|
||||
/// not inside <c>DoRandom</c> itself).</summary>
|
||||
private void ShowRandomizeWarningDialog()
|
||||
{
|
||||
// MakeRandomizeWarningDialog's own guard: a second click while the
|
||||
// dialog is already open is a no-op.
|
||||
if (_randomizeWarningDialogContext != 0u)
|
||||
return;
|
||||
|
||||
_randomizeWarningDialogContext = _dialogs.MakeConfirmation(
|
||||
_strings.RandomizeWarning,
|
||||
data =>
|
||||
{
|
||||
_randomizeWarningDialogContext = 0u;
|
||||
if (_disposed || _suppressDialogCallbacks)
|
||||
return;
|
||||
if (data.GetBoolean(RetailDialogProperty.ConfirmationResult))
|
||||
_bindings.RandomizeCharacter?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
// ── Page switching (gmCharGenMainUI::SetProgressState @ 0x004e7a10) ────
|
||||
|
||||
private void ApplyProgressState(Page target)
|
||||
|
|
@ -641,19 +774,14 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
break;
|
||||
}
|
||||
|
||||
// Random (0x100003cb): fix round F5 — retail's DoRandom @0x004e7d70
|
||||
// case 3 fully ENABLES Random on Appearance (RandomizeClothing when
|
||||
// m_eCurType == ECG_CHOICE_CLOTHES, else RandomizeAppearance); this
|
||||
// is NOT a placeholder gap the way the old comment claimed. The
|
||||
// disable here rests on the SAME unported-primitive gap AP-212
|
||||
// tracks for Skills (no RandomizeSkills) and Summary (no
|
||||
// RandomizeCharacter) — RandomizeAppearance/RandomizeClothing are
|
||||
// two more of AP-212's six named-but-unported primitives.
|
||||
_random.Enabled = _currentPage
|
||||
is not (Page.Skills or Page.Appearance or Page.Summary);
|
||||
// Finish stays ghosted regardless of page — Summary is a
|
||||
// placeholder this round (see the ctor comment on _finish.OnClick).
|
||||
_finish.Enabled = false;
|
||||
// Random (0x100003cb): CC5 ports RandomizeAppearance/RandomizeClothing
|
||||
// (Appearance) and RandomizeCharacter (Summary), retiring both gaps
|
||||
// AP-212 used to track for those two pages — only Skills'
|
||||
// RandomizeSkills remains unported (AP-212, narrowed).
|
||||
_random.Enabled = _currentPage is not Page.Skills;
|
||||
// Finish (0x100003c8): retail enables it on Summary only
|
||||
// (ListenToElementMessage's case 0x100003c8 no-ops off Summary).
|
||||
_finish.Enabled = _currentPage == Page.Summary;
|
||||
|
||||
_lastRevision = long.MinValue;
|
||||
Tick();
|
||||
|
|
@ -719,12 +847,130 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
// Else (including Lugian, 0x100005f1): no-op, matching retail.
|
||||
}
|
||||
|
||||
// ── Finish (gmCharGenMainUI::DoFinish @ 0x004E9170) ─────────────────
|
||||
|
||||
/// <summary>The Finish button's ordinary click — retail's <c>arg2 = 1</c>
|
||||
/// call site (<c>0x004E9579</c>). Re-checks the current page defensively,
|
||||
/// mirroring <c>ListenToElementMessage</c>'s own
|
||||
/// <c>m_eProgressState != ECG_SUMMARY</c> no-op guard.</summary>
|
||||
private void OnFinish()
|
||||
{
|
||||
if (_disposed || _currentPage != Page.Summary)
|
||||
return;
|
||||
TryFinish(confirmedUnspentCredits: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends via <see cref="CharacterCreationRuntimeBindings.Finish"/>
|
||||
/// (which itself calls <c>RuntimeCharacterCreationState.TryBeginFinish</c>);
|
||||
/// on a LOCAL refusal, surfaces retail's own dialog for the two refusal
|
||||
/// reasons retail dialogs at all (<c>NoName</c> ->
|
||||
/// <c>ID_CharGen_NoNameWarning</c>; <c>AttributeCreditsUnspent</c> ->
|
||||
/// the credit-warning confirm, whose OWN confirm re-invokes this method
|
||||
/// with <paramref name="confirmedUnspentCredits"/> — retail's
|
||||
/// <c>arg2 == 0</c> call site, <c>0x004E98BB</c>). The remaining local
|
||||
/// refusals (<c>HeritageOrGenderUnset</c>, <c>AlreadyPending</c>,
|
||||
/// <c>RosterFull</c>) have no retail dialog citation — retail's own
|
||||
/// <c>DoFinish</c> silently falls through to its final <c>return 0</c>
|
||||
/// for an already-Pending double-click, and the other two are
|
||||
/// acdream-only additions (register AP-223, AP-211) with the same
|
||||
/// silent-refusal shape.
|
||||
/// </summary>
|
||||
private void TryFinish(bool confirmedUnspentCredits)
|
||||
{
|
||||
if (_bindings.Finish(confirmedUnspentCredits).Status != RuntimeCommandStatus.Rejected)
|
||||
return;
|
||||
|
||||
RuntimeCharacterCreationLocalRefusal refusal =
|
||||
_bindings.View()?.Snapshot.LastLocalRefusal ?? default;
|
||||
if (refusal.NoName)
|
||||
ShowNoNameWarningDialog();
|
||||
else if (refusal.AttributeCreditsUnspent)
|
||||
ShowCreditWarningDialog();
|
||||
}
|
||||
|
||||
/// <summary>Ports the empty-name half of <c>DoFinish</c>
|
||||
/// (<c>ID_CharGen_NoNameWarning</c>, <c>@0x004e91dd</c>) — a plain
|
||||
/// informational dialog, no confirm/cancel semantics.</summary>
|
||||
private void ShowNoNameWarningDialog()
|
||||
{
|
||||
if (_noNameWarningDialogContext != 0u)
|
||||
return;
|
||||
_noNameWarningDialogContext = _dialogs.MakeMessage(
|
||||
_strings.NoNameWarning,
|
||||
data =>
|
||||
{
|
||||
_ = data;
|
||||
_noNameWarningDialogContext = 0u;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Ports <c>gmCharGenMainUI::MakeCreditWarningDialog @
|
||||
/// 0x004e8870</c> (<c>ID_CharGen_CreditWarning</c>) — on confirm,
|
||||
/// re-invokes <see cref="TryFinish"/> with
|
||||
/// <c>confirmedUnspentCredits: true</c>, retail's <c>DoFinish(this, 0)</c>
|
||||
/// call at <c>RecvNotice_CloseDialog @0x004e98bb</c>.</summary>
|
||||
private void ShowCreditWarningDialog()
|
||||
{
|
||||
if (_creditWarningDialogContext != 0u)
|
||||
return;
|
||||
_creditWarningDialogContext = _dialogs.MakeConfirmation(
|
||||
_strings.CreditWarning,
|
||||
data =>
|
||||
{
|
||||
_creditWarningDialogContext = 0u;
|
||||
if (_disposed || _suppressDialogCallbacks)
|
||||
return;
|
||||
if (data.GetBoolean(RetailDialogProperty.ConfirmationResult))
|
||||
TryFinish(confirmedUnspentCredits: true);
|
||||
});
|
||||
}
|
||||
|
||||
// ── 0xF643 rejection dialogs (Handle_CharGenVerificationResponse @ ──
|
||||
// ── 0x0055E8B0) ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Ports the four rejection-dialog mappings from
|
||||
/// <c>Handle_CharGenVerificationResponse</c>'s per-case switch (restated
|
||||
/// on <see cref="RuntimeCharacterCreationRejection"/>'s own doc
|
||||
/// comment); Pending/Undef never reach this method (CC3's
|
||||
/// <c>ApplyCreationResponse</c> treats them as a silent state reset with
|
||||
/// no <see cref="RuntimeCharacterCreationRejection"/> produced at all).
|
||||
/// Dedups against the LAST rejection instance already shown so a
|
||||
/// same-value re-check on a later <see cref="Tick"/> (this method runs
|
||||
/// every tick, not just on revision change) doesn't reopen the dialog
|
||||
/// the player already dismissed.</summary>
|
||||
private void ReconcileDialogs(RuntimeCharacterCreationSnapshot snapshot)
|
||||
{
|
||||
// Local-refusal / rejection surfacing is CC5's Summary-page job
|
||||
// (the Finish gate only fires from that page). This round only
|
||||
// needs the exit-confirmation dialog reconciled against disposal.
|
||||
_ = snapshot;
|
||||
RuntimeCharacterCreationRejection? rejection = snapshot.LastRejection;
|
||||
if (rejection is null)
|
||||
{
|
||||
_lastShownRejection = null;
|
||||
return;
|
||||
}
|
||||
if (_lastShownRejection == rejection)
|
||||
return;
|
||||
_lastShownRejection = rejection;
|
||||
|
||||
string? key = rejection.Value.Code switch
|
||||
{
|
||||
CharGenVerificationResponse.Code.NameInUse => "ID_Character_Err_NameReserved",
|
||||
CharGenVerificationResponse.Code.NameBanned => "ID_Character_Err_NameBanned",
|
||||
CharGenVerificationResponse.Code.Corrupt
|
||||
or CharGenVerificationResponse.Code.DatabaseDown => "ID_Character_Err_NameDBDown",
|
||||
CharGenVerificationResponse.Code.AdminPrivilegeDenied => "ID_Character_Err_NameAdminDenied",
|
||||
_ => null,
|
||||
};
|
||||
if (key is null)
|
||||
return;
|
||||
string? message = _bindings.ResolveText?.Invoke(key);
|
||||
if (message is null)
|
||||
return;
|
||||
|
||||
_dialogs.MakeMessage(message, data =>
|
||||
{
|
||||
_ = data;
|
||||
_bindings.AcknowledgeRejection?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
|
|
@ -750,6 +996,24 @@ internal sealed class CharacterCreationUiController : IDisposable
|
|||
_exitDialogContext = 0u;
|
||||
_dialogs.CloseDialog(closing);
|
||||
}
|
||||
if (_creditWarningDialogContext != 0u)
|
||||
{
|
||||
uint closing = _creditWarningDialogContext;
|
||||
_creditWarningDialogContext = 0u;
|
||||
_dialogs.CloseDialog(closing);
|
||||
}
|
||||
if (_randomizeWarningDialogContext != 0u)
|
||||
{
|
||||
uint closing = _randomizeWarningDialogContext;
|
||||
_randomizeWarningDialogContext = 0u;
|
||||
_dialogs.CloseDialog(closing);
|
||||
}
|
||||
if (_noNameWarningDialogContext != 0u)
|
||||
{
|
||||
uint closing = _noNameWarningDialogContext;
|
||||
_noNameWarningDialogContext = 0u;
|
||||
_dialogs.CloseDialog(closing);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue