fix(client): restore retail interaction parity
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
parent
0c699240e0
commit
f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions
|
|
@ -96,7 +96,8 @@ internal sealed record InteractionRetainedUiDependencies(
|
|||
Func<AcDream.Core.World.DerethDateTime.Calendar> CurrentCalendar,
|
||||
AcDream.App.Rendering.Packs.RenderPackCatalogSource? RenderPackCatalog = null,
|
||||
Func<AcDream.App.Rendering.Packs.RenderPackDiagnosticsSnapshot>?
|
||||
RenderPackDiagnostics = null)
|
||||
RenderPackDiagnostics = null,
|
||||
string? ScreenshotsDirectory = null)
|
||||
{
|
||||
public RuntimeActionState Actions => Runtime.ActionOwner;
|
||||
|
||||
|
|
@ -388,9 +389,15 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
container,
|
||||
placement,
|
||||
amount),
|
||||
sendStackableMerge: (source, target, amount) =>
|
||||
session.CurrentSession?.SendStackableMerge(source, target, amount),
|
||||
requestExternalContainer: guid =>
|
||||
{
|
||||
d.Inventory.ExternalContainers.RequestOpen(guid);
|
||||
ClientObject? container = d.Inventory.Objects.Get(guid);
|
||||
bool isCorpse = container is not null
|
||||
&& ((PublicWeenieFlags)(container.PublicWeenieBitfield ?? 0u)
|
||||
& PublicWeenieFlags.Corpse) != 0;
|
||||
d.Inventory.ExternalContainers.RequestOpen(guid, isCorpse);
|
||||
},
|
||||
requestUse: selection.RequestUse,
|
||||
// Slice 6.3: ItemInteractionController.TryBuy owns the
|
||||
|
|
@ -663,16 +670,20 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
CharacterKey: () => d.Settings.ActiveToonKey,
|
||||
ScreenSize: () => (d.Window.Size.X, d.Window.Size.Y));
|
||||
void ProbeLog(string message) => d.Log("[UI-PROBE] " + message);
|
||||
FrameScreenshotController? screenshots = null;
|
||||
if (d.Options.UiProbeEnabled
|
||||
&& d.Options.AutomationArtifactDirectory is { } artifactDirectory)
|
||||
{
|
||||
screenshots = new FrameScreenshotController(
|
||||
d.BackbufferReader,
|
||||
Path.Combine(artifactDirectory, "screenshots"),
|
||||
ProbeLog,
|
||||
d.RenderPackDiagnostics);
|
||||
}
|
||||
string screenshotDirectory =
|
||||
d.Options.UiProbeEnabled
|
||||
&& d.Options.AutomationArtifactDirectory is { } artifactDirectory
|
||||
? Path.Combine(artifactDirectory, "screenshots")
|
||||
: !string.IsNullOrWhiteSpace(d.ScreenshotsDirectory)
|
||||
? d.ScreenshotsDirectory
|
||||
: Path.Combine(
|
||||
Path.GetDirectoryName(d.KeyBindingsFilePath)!,
|
||||
"screenshots");
|
||||
var screenshots = new FrameScreenshotController(
|
||||
d.BackbufferReader,
|
||||
screenshotDirectory,
|
||||
ProbeLog,
|
||||
d.RenderPackDiagnostics);
|
||||
checkpoint(InteractionRetainedUiCompositionPoint.UiProbeCreated);
|
||||
|
||||
var assets = new RetailUiAssets(
|
||||
|
|
@ -1157,11 +1168,10 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
late.GameRuntime.CharacterSelectionConfirmDelete,
|
||||
late.GameRuntime.CharacterSelectionRestore,
|
||||
late.GameRuntime.CharacterSelectionCancel,
|
||||
// Campaign LA gate round 2 finding 1: the SAME
|
||||
// window-close path GameplayInputCommandController's
|
||||
// Escape fallback uses (IGameplayWindowCommands.Close
|
||||
// /GameplayWindowCommands wrap this same d.Window.Close
|
||||
// delegate) — no separate exit path.
|
||||
// Campaign LA gate round 2 finding 1: the character
|
||||
// selection screen's Exit button uses the ordinary host
|
||||
// close path. Gameplay Escape is independent: retail
|
||||
// clears selection or toggles the Gameplay Options page.
|
||||
d.Window.Close),
|
||||
// Campaign CC slice CC4: same late-bound generation-capturing
|
||||
// seam as CharacterSelection above. RequestExit here is a
|
||||
|
|
@ -1203,7 +1213,24 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
RandomizeAppearance: late.GameRuntime.CharacterCreationRandomizeAppearance,
|
||||
RandomizeClothing: late.GameRuntime.CharacterCreationRandomizeClothing,
|
||||
GetSkillScore: chargenSkillScoreResolver.Resolve,
|
||||
OpenOnStart: d.Options.OpenCharacterCreationOnStart));
|
||||
OpenOnStart: d.Options.OpenCharacterCreationOnStart),
|
||||
CaptureScreenshot: () =>
|
||||
{
|
||||
if (screenshots.TryRequestRetailScreenshot(
|
||||
out string path,
|
||||
out string error))
|
||||
{
|
||||
d.Communication.AddText(
|
||||
$"Screenshot saved to {path}",
|
||||
RetailLogTextType.ClientLocal);
|
||||
}
|
||||
else
|
||||
{
|
||||
d.Communication.AddText(
|
||||
$"Screenshot failed: {error}",
|
||||
RetailLogTextType.ClientLocal);
|
||||
}
|
||||
});
|
||||
RetailUiRuntime runtime = lease.Mount(
|
||||
() => RetailUiRuntime.CreateUninitialized(bindings));
|
||||
checkpoint(InteractionRetainedUiCompositionPoint.UiRuntimeMounted);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ internal sealed record LivePresentationDependencies(
|
|||
CellVisibility CellVisibility,
|
||||
LiveWorldOriginState WorldOrigin,
|
||||
LocalPlayerIdentityState PlayerIdentity,
|
||||
ChaseCameraInputState ChaseCameraInput,
|
||||
PointerPositionState PointerPosition,
|
||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||
GameRenderResourceLifetime RenderResourceLifetime,
|
||||
|
|
@ -808,7 +809,12 @@ internal sealed class LivePresentationCompositionPhase
|
|||
d.RetailAlphaQueue,
|
||||
alphaScratchBudgets.DispatcherBytes,
|
||||
foundation.TerrainAtlas?.BuildingDetailTexture ?? default,
|
||||
() => d.Settings.DisplayPreview.BuildingDetailTextures),
|
||||
() => d.Settings.DisplayPreview.BuildingDetailTextures,
|
||||
serverGuid => serverGuid != 0u
|
||||
&& serverGuid == d.PlayerIdentity.ServerGuid
|
||||
? d.ChaseCameraInput.Retail?.PlayerTranslucency
|
||||
?? (d.ChaseCameraInput.Legacy?.IsInHead == true ? 1f : 0f)
|
||||
: 0f),
|
||||
static value => value.Dispose());
|
||||
var selectionQuery = new WorldSelectionQuery(
|
||||
liveEntities,
|
||||
|
|
@ -845,7 +851,11 @@ internal sealed class LivePresentationCompositionPhase
|
|||
localEntityId =>
|
||||
d.EffectPoses.TryGetRootPose(localEntityId, out Matrix4x4 childRoot)
|
||||
? childRoot
|
||||
: null);
|
||||
: null,
|
||||
hasOpenedCorpse:
|
||||
d.Runtime.InventoryOwner.ExternalContainers.HasCorpseBeenOpened,
|
||||
combatMode: () => d.Runtime.ActionOwner.Combat.CurrentMode,
|
||||
isFellow: guid => d.Runtime.Fellowship.TryGetMember(guid, out _));
|
||||
var radarSnapshotProvider = new RadarSnapshotProvider(
|
||||
d.EntityObjects.Objects,
|
||||
liveEntities,
|
||||
|
|
@ -876,7 +886,12 @@ internal sealed class LivePresentationCompositionPhase
|
|||
() => d.PlayerController.Controller,
|
||||
d.PlayerApproachCompletions),
|
||||
d.Toast,
|
||||
d.PlayerApproachCompletions);
|
||||
d.PlayerApproachCompletions,
|
||||
splitStack: guid =>
|
||||
interaction.RetainedUi?.Runtime.SelectedObjectController?
|
||||
.FocusSplitStackEntry(guid) ?? false,
|
||||
fellowshipMembers: () =>
|
||||
d.Runtime.Fellowship.GetMembers().Select(static member => member.Guid));
|
||||
selectionInteractionSource.Bind(selectionInteractions);
|
||||
bindings.Adopt(
|
||||
"world selection",
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
live.SelectionInteractions),
|
||||
new LiveSessionWorldRuntime(
|
||||
content.Dats,
|
||||
d.DatLock,
|
||||
content.Audio?.Engine is { } sessionAudioEngine
|
||||
? new AcDream.App.Audio.WorldAudioSessionGate(
|
||||
sessionAudioEngine,
|
||||
|
|
@ -1279,14 +1280,10 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
new RetainedGameplayWindowCommands(
|
||||
interaction.RetainedUi?.Runtime),
|
||||
runtimeDiagnostics,
|
||||
new PlayerModeGameplayCommands(
|
||||
d.PlayerMode,
|
||||
playerMode),
|
||||
new PlayerModeGameplayCommands(playerMode),
|
||||
new ItemTargetModeCommands(interaction.ItemInteraction),
|
||||
new GameplayCameraModeCommands(host.CameraController),
|
||||
gameRuntime,
|
||||
gameRuntime.Combat,
|
||||
new GameplayWindowCommands(d.Window.Close),
|
||||
toggleAudioMute: content.Audio?.Engine is { } audioEngine
|
||||
? () =>
|
||||
{
|
||||
|
|
@ -1305,6 +1302,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
gameRuntime,
|
||||
gameRuntime.Selection,
|
||||
gameRuntime.MovementCommands,
|
||||
gameRuntime.CharacterCommands,
|
||||
commands);
|
||||
GameplayInputActionRouter gameplayActions =
|
||||
GameplayInputActionRouter.Create(
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ namespace AcDream.App.Composition;
|
|||
/// <c>UiHost</c>/<c>UiRoot</c> tree — D1) instead of a new
|
||||
/// <c>IPanelRenderer</c> implementation, and its OP9 closeout retired the
|
||||
/// unrendered ImGui-era SettingsPanel/SettingsVM outright. Keybind remapping
|
||||
/// is Campaign OP slice OP8's Configure Keyboard screen, persisting to
|
||||
/// keybinds.json (not retail's <c>.keymap</c> format — register row AP-202).
|
||||
/// is Campaign OP slice OP8's Configure Keyboard screen, persisting retail
|
||||
/// <c>*.keymap</c> profiles with <c>keybinds.json</c> as the host-command mirror.
|
||||
/// </summary>
|
||||
internal sealed record SettingsDevToolsResult(
|
||||
AcDream.UI.Abstractions.Settings.QualitySettings ResolvedQuality)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue