Harden retail character selection recovery
This commit is contained in:
parent
6cfab727f1
commit
aeac874dab
8 changed files with 736 additions and 81 deletions
|
|
@ -45,6 +45,7 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
private uint _enterWaitContext;
|
||||
private uint _errorDialogContext;
|
||||
private bool _active;
|
||||
private bool _restoreCommandInFlight;
|
||||
private bool _suppressDialogCallbacks;
|
||||
private bool _disposed;
|
||||
|
||||
|
|
@ -197,7 +198,7 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
if (TryCaptureRoster(view, snapshot, out RuntimeCharacterSelectionEntry[] roster))
|
||||
{
|
||||
bool rowsReady;
|
||||
if (RowsMatchRoster(roster))
|
||||
if (RowsMatchRoster(roster, snapshot.SlotCount))
|
||||
{
|
||||
ApplyHighlight(snapshot.HighlightedCharacterId);
|
||||
rowsReady = true;
|
||||
|
|
@ -206,6 +207,7 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
{
|
||||
rowsReady = RebuildRows(
|
||||
roster,
|
||||
snapshot.SlotCount,
|
||||
snapshot.HighlightedCharacterId);
|
||||
}
|
||||
|
||||
|
|
@ -280,11 +282,16 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
}
|
||||
|
||||
private bool RowsMatchRoster(
|
||||
IReadOnlyList<RuntimeCharacterSelectionEntry> roster)
|
||||
IReadOnlyList<RuntimeCharacterSelectionEntry> roster,
|
||||
int allowedSlotCount)
|
||||
{
|
||||
if (_rows.Count != roster.Count)
|
||||
return false;
|
||||
|
||||
int rowHeight = ComputeRowHeight(
|
||||
_list.Height,
|
||||
roster.Count,
|
||||
allowedSlotCount);
|
||||
for (int i = 0; i < roster.Count; i++)
|
||||
{
|
||||
UiButton row = _rows[i];
|
||||
|
|
@ -292,6 +299,7 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
if (!_rowIds.TryGetValue(row, out uint characterId)
|
||||
|| characterId != character.CharacterId
|
||||
|| !string.Equals(row.Label, character.Name, StringComparison.Ordinal)
|
||||
|| (int)row.Height != rowHeight
|
||||
|| row.LabelColor != (character.IsPendingDelete
|
||||
? new Vector4(1f, 0f, 0f, 1f)
|
||||
: Vector4.One))
|
||||
|
|
@ -305,6 +313,7 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
|
||||
private bool RebuildRows(
|
||||
IReadOnlyList<RuntimeCharacterSelectionEntry> roster,
|
||||
int allowedSlotCount,
|
||||
uint highlightedCharacterId)
|
||||
{
|
||||
foreach (UiButton row in _rows)
|
||||
|
|
@ -316,15 +325,34 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
_rowIds.Clear();
|
||||
_list.Flush();
|
||||
|
||||
bool complete = true;
|
||||
int rowHeight = ComputeRowHeight(
|
||||
_list.Height,
|
||||
roster.Count,
|
||||
allowedSlotCount);
|
||||
_list.LineHeight = rowHeight;
|
||||
bool complete = _list.Templates.Count > 0
|
||||
&& _list.TemplateResolver is not null;
|
||||
foreach (RuntimeCharacterSelectionEntry character in roster)
|
||||
{
|
||||
if (_list.AddItemFromTemplateList(0) is not UiButton row)
|
||||
if (!complete)
|
||||
break;
|
||||
|
||||
UiTemplateListEntry template = _list.Templates[0];
|
||||
if (_list.TemplateResolver!(
|
||||
template.TemplateLayoutId,
|
||||
template.TemplateElementId) is not UiButton row)
|
||||
{
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// AddItemFromTemplateList creates the same template, but its
|
||||
// retained viewport stacks at the template's authored 16px
|
||||
// height. Retail establishes the computed size on every row; our
|
||||
// list fixes Top during insertion, so build and resize first to
|
||||
// make every subsequent Top exact.
|
||||
row.Height = rowHeight;
|
||||
_list.AddPrebuiltRow(row);
|
||||
uint characterId = character.CharacterId;
|
||||
row.Label = character.Name;
|
||||
row.LabelColor = character.IsPendingDelete
|
||||
|
|
@ -354,6 +382,21 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
internal static int ComputeRowHeight(
|
||||
float listHeight,
|
||||
int rosterCount,
|
||||
int allowedSlotCount)
|
||||
{
|
||||
// RebuildCharacterList @ 0x004EC3A0 uses integer UIRegion height and
|
||||
// signed integer division for both terms. The 0x66666667 multiply/
|
||||
// shift sequence is compiler output for height / 10.
|
||||
int height = (int)MathF.Truncate(listHeight);
|
||||
int denominator = Math.Max(rosterCount, allowedSlotCount);
|
||||
if (denominator <= 0)
|
||||
return height / 10;
|
||||
return Math.Max(height / denominator, height / 10);
|
||||
}
|
||||
|
||||
private void ApplyHighlight(uint highlightedCharacterId)
|
||||
{
|
||||
foreach (UiButton row in _rows)
|
||||
|
|
@ -407,9 +450,39 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
RuntimeCommandResult result = _bindings.Restore();
|
||||
if (result.Accepted)
|
||||
EnsureOperationWait();
|
||||
|
||||
// ListenToElementMessage @ 0x004ED5A0 opens Please Wait before it
|
||||
// calls CPlayerSystem::RestoreCharacter. Keep it modal even if a
|
||||
// synchronous command callback re-enters Tick before Runtime has
|
||||
// returned its accepted projection.
|
||||
EnsureOperationWait();
|
||||
RuntimeCommandResult result = default;
|
||||
Exception? failure = null;
|
||||
_restoreCommandInFlight = true;
|
||||
try
|
||||
{
|
||||
result = _bindings.Restore();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
failure = error;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_restoreCommandInFlight = false;
|
||||
}
|
||||
|
||||
if (failure is not null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[UI] character restore command failed: {failure.Message}");
|
||||
CloseContext(ref _operationWaitContext, suppressCallback: true);
|
||||
InvalidateAndTick();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.Accepted)
|
||||
CloseContext(ref _operationWaitContext, suppressCallback: true);
|
||||
InvalidateAndTick();
|
||||
}
|
||||
|
||||
|
|
@ -446,7 +519,8 @@ internal sealed class CharacterManagementUiController : IDisposable
|
|||
CloseContext(ref _deleteDialogContext, suppressCallback: true);
|
||||
}
|
||||
|
||||
if (snapshot.Operation is RuntimeCharacterSelectionOperation.DeleteRequested
|
||||
if (_restoreCommandInFlight
|
||||
|| snapshot.Operation is RuntimeCharacterSelectionOperation.DeleteRequested
|
||||
or RuntimeCharacterSelectionOperation.DeleteAcknowledged
|
||||
or RuntimeCharacterSelectionOperation.RestoreRequested)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue