fix(ui): restore radar, retail wield switching, and protection meshes
Late-bind radar snapshots to the canonical LiveEntityRuntime maps so the retained radar survives bootstrap and session replacement instead of capturing empty sentinels. Route paperdoll drops through the retail AutoWield blocker transaction. Move conflicting held weapons, shields, explicit jewelry destinations, and mismatched ammo to the backpack one authoritative confirmation at a time; preserve compatible arrows when switching to melee. Render mode-1 and no-degrade particle GfxObjs as their authored modern-pipeline meshes, retain Always2D billboards, interleave both paths back-to-front, balance emitter mesh ownership, and fail safely on corrupt DAT material metadata. This restores the closed apex on Armor Self/protection effects. Retain Studio fixture controller lifetimes, add installed-DAT and adversarial regression coverage, synchronize retail research/divergence bookkeeping, and pass all three review tracks plus the full Release suite. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
8d63e5c28a
commit
b26f84cc69
26 changed files with 1361 additions and 141 deletions
|
|
@ -62,6 +62,7 @@ internal sealed class AutoWieldController : IDisposable
|
|||
private readonly Action<uint, uint>? _sendWield;
|
||||
private readonly Action<uint, uint, int>? _sendPutItemInContainer;
|
||||
private readonly Action<string>? _toast;
|
||||
private readonly Action<string>? _systemMessage;
|
||||
|
||||
private PendingSwitch? _pendingSwitch;
|
||||
private bool _disposed;
|
||||
|
|
@ -73,13 +74,15 @@ internal sealed class AutoWieldController : IDisposable
|
|||
Func<uint> playerGuid,
|
||||
Action<uint, uint>? sendWield,
|
||||
Action<uint, uint, int>? sendPutItemInContainer,
|
||||
Action<string>? toast)
|
||||
Action<string>? toast,
|
||||
Action<string>? systemMessage = null)
|
||||
{
|
||||
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
|
||||
_playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid));
|
||||
_sendWield = sendWield;
|
||||
_sendPutItemInContainer = sendPutItemInContainer;
|
||||
_toast = toast;
|
||||
_systemMessage = systemMessage;
|
||||
|
||||
_objects.ObjectMoved += OnObjectMoved;
|
||||
_objects.ObjectRemoved += OnObjectRemoved;
|
||||
|
|
@ -94,22 +97,53 @@ internal sealed class AutoWieldController : IDisposable
|
|||
/// as retail's inventory-ready gate prevents a second concurrent request.
|
||||
/// </summary>
|
||||
public bool TryWield(ClientObject item)
|
||||
=> TryWield(item, EquipMask.None);
|
||||
|
||||
/// <summary>
|
||||
/// Execute AutoWield while retaining the paperdoll's resolved target side.
|
||||
/// Retail <c>gmPaperDollUI::AcceptDragObject @ 0x004A3B10</c> supplies this
|
||||
/// location to <c>CPlayerSystem::AutoWield @ 0x00560A60</c> rather than
|
||||
/// bypassing the transaction with a direct wire request.
|
||||
/// </summary>
|
||||
public bool TryWield(ClientObject item, EquipMask requestedMask)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
if (_pendingSwitch is not null)
|
||||
return true;
|
||||
if (item.ValidLocations == EquipMask.None
|
||||
|| item.CurrentlyEquippedLocation != EquipMask.None)
|
||||
if (item.ValidLocations == EquipMask.None)
|
||||
return false;
|
||||
if (requestedMask != EquipMask.None
|
||||
&& (requestedMask & item.ValidLocations) != requestedMask)
|
||||
return false;
|
||||
|
||||
EquipMask weaponLocation = item.ValidLocations & WeaponReadyMask;
|
||||
// gmPaperDollUI::AcceptDragObject can ask AutoWield to move an item
|
||||
// that is already worn (the classic left-ring -> right-ring drop).
|
||||
// It is not a blocker transaction: the object itself is the requested
|
||||
// item, so preserve the paperdoll's explicit destination and let the
|
||||
// ordinary GetAndWieldItem acknowledgement relocate it.
|
||||
if (item.CurrentlyEquippedLocation != EquipMask.None)
|
||||
{
|
||||
if (requestedMask == EquipMask.None
|
||||
|| requestedMask == item.CurrentlyEquippedLocation)
|
||||
return false;
|
||||
if (GetEquippedObjectAtLocation(
|
||||
requestedMask, priority: 0, item.ObjectId) is { } blocker)
|
||||
{
|
||||
return BeginWeaponReplacement(item.ObjectId, blocker, requestedMask);
|
||||
}
|
||||
return SendWield(item, requestedMask);
|
||||
}
|
||||
|
||||
EquipMask weaponLocation = requestedMask == EquipMask.None
|
||||
? item.ValidLocations & WeaponReadyMask
|
||||
: requestedMask & WeaponReadyMask;
|
||||
if (weaponLocation != EquipMask.None)
|
||||
{
|
||||
ClientObject? blocker = GetEquippedObjectAtLocation(
|
||||
WeaponReadyMask, priority: 0, item.ObjectId);
|
||||
if (blocker is not null)
|
||||
return BeginWeaponReplacement(item.ObjectId, blocker.ObjectId);
|
||||
return BeginWeaponReplacement(item.ObjectId, blocker, weaponLocation);
|
||||
|
||||
// Retail checks secondary blockers only after the primary weapon
|
||||
// slot is vacant, then re-enters AutoWield after each confirmed
|
||||
|
|
@ -117,18 +151,20 @@ internal sealed class AutoWieldController : IDisposable
|
|||
if (BlocksUseOfShield(item)
|
||||
&& GetEquippedObjectAtLocation(
|
||||
EquipMask.Shield, priority: 0, item.ObjectId) is { } shield)
|
||||
return BeginWeaponReplacement(item.ObjectId, shield.ObjectId);
|
||||
return BeginWeaponReplacement(item.ObjectId, shield, weaponLocation);
|
||||
|
||||
if (item.AmmoType is > 0
|
||||
&& GetEquippedObjectAtLocation(
|
||||
EquipMask.MissileAmmo, priority: 0, item.ObjectId) is { } ammo
|
||||
&& ammo.AmmoType != item.AmmoType)
|
||||
return BeginWeaponReplacement(item.ObjectId, ammo.ObjectId);
|
||||
return BeginWeaponReplacement(item.ObjectId, ammo, weaponLocation);
|
||||
|
||||
return SendWield(item, weaponLocation);
|
||||
}
|
||||
|
||||
EquipMask mask = BestAvailableEquipMask(item);
|
||||
EquipMask mask = requestedMask != EquipMask.None
|
||||
? requestedMask
|
||||
: BestAvailableEquipMask(item);
|
||||
if (mask == EquipMask.None)
|
||||
{
|
||||
_toast?.Invoke("That slot is already in use");
|
||||
|
|
@ -138,7 +174,10 @@ internal sealed class AutoWieldController : IDisposable
|
|||
return SendWield(item, mask);
|
||||
}
|
||||
|
||||
private bool BeginWeaponReplacement(uint requestedItemId, uint blockingItemId)
|
||||
private bool BeginWeaponReplacement(
|
||||
uint requestedItemId,
|
||||
ClientObject blockingItem,
|
||||
EquipMask requestedMask)
|
||||
{
|
||||
if (_sendPutItemInContainer is null)
|
||||
{
|
||||
|
|
@ -150,12 +189,17 @@ internal sealed class AutoWieldController : IDisposable
|
|||
if (player == 0)
|
||||
return false;
|
||||
|
||||
_pendingSwitch = new PendingSwitch(requestedItemId, blockingItemId);
|
||||
_pendingSwitch = new PendingSwitch(
|
||||
requestedItemId,
|
||||
blockingItem.ObjectId,
|
||||
requestedMask);
|
||||
|
||||
// Retail AttemptToPlaceInContainer(blockingID, playerID, 0, 1, 0).
|
||||
// Do not change the local equip projection yet: the server's move event
|
||||
// is the transaction boundary and preserves its stance-specific motion.
|
||||
_sendPutItemInContainer(blockingItemId, player, 0);
|
||||
_systemMessage?.Invoke(
|
||||
$"Moving {blockingItem.GetAppropriateName()} to your backpack");
|
||||
_sendPutItemInContainer(blockingItem.ObjectId, player, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +207,10 @@ internal sealed class AutoWieldController : IDisposable
|
|||
{
|
||||
if (_sendWield is null)
|
||||
return false;
|
||||
_pendingSwitch = new PendingSwitch(item.ObjectId, BlockingItemId: 0);
|
||||
_pendingSwitch = new PendingSwitch(
|
||||
item.ObjectId,
|
||||
BlockingItemId: 0,
|
||||
RequestedMask: mask);
|
||||
if (!_objects.WieldItemOptimistic(item.ObjectId, _playerGuid(), mask))
|
||||
{
|
||||
_pendingSwitch = null;
|
||||
|
|
@ -188,7 +235,7 @@ internal sealed class AutoWieldController : IDisposable
|
|||
// vacant ready slot and send GetAndWieldItem for the requested weapon.
|
||||
_pendingSwitch = null;
|
||||
if (_objects.Get(pending.RequestedItemId) is { } requested)
|
||||
TryWield(requested);
|
||||
TryWield(requested, pending.RequestedMask);
|
||||
}
|
||||
|
||||
private void OnWieldConfirmed(ClientObject item)
|
||||
|
|
@ -325,5 +372,6 @@ internal sealed class AutoWieldController : IDisposable
|
|||
|
||||
private readonly record struct PendingSwitch(
|
||||
uint RequestedItemId,
|
||||
uint BlockingItemId);
|
||||
uint BlockingItemId,
|
||||
EquipMask RequestedMask);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
private readonly Func<uint> _selectedObjectId;
|
||||
private readonly StackSplitQuantityState? _stackSplitQuantity;
|
||||
private readonly Func<bool> _dragOnPlayerOpensSecureTrade;
|
||||
private readonly Action<string>? _systemMessage;
|
||||
private readonly AutoWieldController _autoWield;
|
||||
|
||||
private long _lastUseMs = long.MinValue / 2;
|
||||
|
|
@ -76,7 +77,8 @@ public sealed class ItemInteractionController : IDisposable
|
|||
StackSplitQuantityState? stackSplitQuantity = null,
|
||||
Action<uint, uint, int>? sendPutItemInContainer = null,
|
||||
Action<uint, uint, uint>? sendGive = null,
|
||||
Func<bool>? dragOnPlayerOpensSecureTrade = null)
|
||||
Func<bool>? dragOnPlayerOpensSecureTrade = null,
|
||||
Action<string>? systemMessage = null)
|
||||
{
|
||||
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
|
||||
_playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid));
|
||||
|
|
@ -100,6 +102,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
_selectedObjectId = selectedObjectId ?? (() => 0u);
|
||||
_stackSplitQuantity = stackSplitQuantity;
|
||||
_dragOnPlayerOpensSecureTrade = dragOnPlayerOpensSecureTrade ?? (() => true);
|
||||
_systemMessage = systemMessage;
|
||||
_interactionState = interactionState ?? new InteractionState();
|
||||
_interactionState.Changed += OnInteractionModeChanged;
|
||||
_autoWield = new AutoWieldController(
|
||||
|
|
@ -107,7 +110,8 @@ public sealed class ItemInteractionController : IDisposable
|
|||
_playerGuid,
|
||||
_sendWield,
|
||||
sendPutItemInContainer,
|
||||
_toast);
|
||||
_toast,
|
||||
_systemMessage);
|
||||
}
|
||||
|
||||
public event Action? StateChanged;
|
||||
|
|
@ -264,6 +268,20 @@ public sealed class ItemInteractionController : IDisposable
|
|||
return ExecuteUseActions(decision.Actions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail paperdoll drop entry point. The paperdoll resolves the exact
|
||||
/// target side/location; this shared interaction owner performs AutoWield's
|
||||
/// confirmed blocker transaction before sending GetAndWieldItem.
|
||||
/// </summary>
|
||||
public bool WieldFromPaperdoll(uint itemGuid, EquipMask targetMask)
|
||||
{
|
||||
if (itemGuid == 0u || targetMask == EquipMask.None)
|
||||
return false;
|
||||
if (_objects.Get(itemGuid) is not { } item)
|
||||
return false;
|
||||
return _autoWield.TryWield(item, targetMask);
|
||||
}
|
||||
|
||||
public bool AcquireTarget(uint targetGuid)
|
||||
{
|
||||
if (!IsTargetModeActive || targetGuid == 0) return false;
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
private readonly Func<uint> _playerGuid;
|
||||
private readonly Func<ItemType, uint, uint, uint, uint, uint> _iconIds;
|
||||
private readonly Func<ItemType, uint, uint, uint, uint, uint>? _dragIconIds;
|
||||
private readonly Action<uint, uint>? _sendWield; // (itemGuid, equipMask) → GetAndWieldItem 0x001A
|
||||
private readonly ItemInteractionController? _itemInteraction;
|
||||
private readonly ItemInteractionController _itemInteraction;
|
||||
private readonly bool _ownsItemInteraction;
|
||||
private readonly SelectionState _selection;
|
||||
private readonly PaperdollClickMap? _clickMap;
|
||||
private readonly List<(EquipMask Mask, UiItemList List)> _slots = new();
|
||||
|
|
@ -65,15 +65,17 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
private PaperdollController(
|
||||
ImportedLayout layout, ClientObjectTable objects, Func<uint> playerGuid,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds, SelectionState selection,
|
||||
Action<uint, uint>? sendWield,
|
||||
uint emptySlotSprite, UiDatFont? datFont, ItemInteractionController? itemInteraction,
|
||||
ItemInteractionController itemInteraction,
|
||||
uint emptySlotSprite, UiDatFont? datFont,
|
||||
PaperdollClickMap? clickMap,
|
||||
Func<ItemType, uint, uint, uint, uint, uint>? dragIconIds,
|
||||
IReadOnlyDictionary<uint, uint>? emptySlotSprites)
|
||||
IReadOnlyDictionary<uint, uint>? emptySlotSprites,
|
||||
bool ownsItemInteraction)
|
||||
{
|
||||
_objects = objects; _playerGuid = playerGuid; _iconIds = iconIds; _sendWield = sendWield;
|
||||
_objects = objects; _playerGuid = playerGuid; _iconIds = iconIds;
|
||||
_dragIconIds = dragIconIds;
|
||||
_itemInteraction = itemInteraction;
|
||||
_itemInteraction = itemInteraction ?? throw new ArgumentNullException(nameof(itemInteraction));
|
||||
_ownsItemInteraction = ownsItemInteraction;
|
||||
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
|
||||
_clickMap = clickMap;
|
||||
|
||||
|
|
@ -172,15 +174,15 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
public static PaperdollController Bind(
|
||||
ImportedLayout layout, ClientObjectTable objects, Func<uint> playerGuid,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds, SelectionState selection,
|
||||
Action<uint, uint>? sendWield = null,
|
||||
ItemInteractionController itemInteraction,
|
||||
uint emptySlotSprite = 0u, UiDatFont? datFont = null,
|
||||
ItemInteractionController? itemInteraction = null,
|
||||
PaperdollClickMap? clickMap = null,
|
||||
Func<ItemType, uint, uint, uint, uint, uint>? dragIconIds = null,
|
||||
IReadOnlyDictionary<uint, uint>? emptySlotSprites = null)
|
||||
IReadOnlyDictionary<uint, uint>? emptySlotSprites = null,
|
||||
bool ownsItemInteraction = false)
|
||||
=> new PaperdollController(
|
||||
layout, objects, playerGuid, iconIds, selection, sendWield, emptySlotSprite,
|
||||
datFont, itemInteraction, clickMap, dragIconIds, emptySlotSprites);
|
||||
layout, objects, playerGuid, iconIds, selection, itemInteraction, emptySlotSprite,
|
||||
datFont, clickMap, dragIconIds, emptySlotSprites, ownsItemInteraction);
|
||||
|
||||
private void HandleDollClick(int x, int y)
|
||||
{
|
||||
|
|
@ -311,9 +313,10 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
: ItemDragAcceptance.Reject;
|
||||
}
|
||||
|
||||
/// <summary>Wield the dropped item: optimistic local equip (instant) + GetAndWieldItem 0x001A.
|
||||
/// <summary>Wield the dropped item through the shared retail AutoWield transaction.
|
||||
/// wieldMask = ValidLocations & slotMask resolves the specific bit (the composite weapon slot, the
|
||||
/// chosen finger). Retail: gmPaperDollUI HandleDropRelease → GetAndWieldItem.</summary>
|
||||
/// chosen finger). Retail: gmPaperDollUI::HandleDropRelease @ 0x004A4D80 to
|
||||
/// AcceptDragObject @ 0x004A3B10 to CPlayerSystem::AutoWield @ 0x00560A60.</summary>
|
||||
public void HandleDropRelease(UiItemList targetList, UiItemSlot targetCell, ItemDragPayload payload)
|
||||
{
|
||||
// gmPaperDollUI::HandleDropRelease @ 0x004A4D80 applies the same flag
|
||||
|
|
@ -324,8 +327,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
if (item is null) return;
|
||||
EquipMask wieldMask = ItemEquipRules.ResolvePaperdollDropWieldMask(item, MaskFor(targetList));
|
||||
if (wieldMask == EquipMask.None) return; // not wieldable here (defensive; OnDragOver already rejected)
|
||||
_objects.WieldItemOptimistic(payload.ObjId, _playerGuid(), wieldMask);
|
||||
_sendWield?.Invoke(payload.ObjId, (uint)wieldMask);
|
||||
_itemInteraction.WieldFromPaperdoll(payload.ObjId, wieldMask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -362,5 +364,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
element.OnClickAt = null;
|
||||
break;
|
||||
}
|
||||
if (_ownsItemInteraction)
|
||||
_itemInteraction.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ public sealed class RadarSnapshotProvider
|
|||
private static readonly Vector2 ProductionCenter = new(60f, 60f);
|
||||
|
||||
private readonly ClientObjectTable _objects;
|
||||
private readonly IReadOnlyDictionary<uint, WorldEntity> _worldEntities;
|
||||
private readonly IReadOnlyDictionary<uint, WorldEntity> _playerEntities;
|
||||
private readonly IReadOnlyDictionary<uint, WorldSession.EntitySpawn> _spawns;
|
||||
private readonly Func<IReadOnlyDictionary<uint, WorldEntity>> _worldEntities;
|
||||
private readonly Func<IReadOnlyDictionary<uint, WorldEntity>> _playerEntities;
|
||||
private readonly Func<IReadOnlyDictionary<uint, WorldSession.EntitySpawn>> _spawns;
|
||||
private readonly Func<uint> _playerGuid;
|
||||
private readonly Func<float> _playerYawRadians;
|
||||
private readonly Func<uint> _playerCellId;
|
||||
|
|
@ -32,8 +32,8 @@ public sealed class RadarSnapshotProvider
|
|||
|
||||
public RadarSnapshotProvider(
|
||||
ClientObjectTable objects,
|
||||
IReadOnlyDictionary<uint, WorldEntity> worldEntities,
|
||||
IReadOnlyDictionary<uint, WorldSession.EntitySpawn> spawns,
|
||||
Func<IReadOnlyDictionary<uint, WorldEntity>> worldEntities,
|
||||
Func<IReadOnlyDictionary<uint, WorldSession.EntitySpawn>> spawns,
|
||||
Func<uint> playerGuid,
|
||||
Func<float> playerYawRadians,
|
||||
Func<uint> playerCellId,
|
||||
|
|
@ -41,7 +41,7 @@ public sealed class RadarSnapshotProvider
|
|||
Func<bool> coordinatesOnRadar,
|
||||
Func<bool> uiLocked,
|
||||
Func<uint, RadarRelationshipTraits>? relationshipFor = null,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? playerEntities = null)
|
||||
Func<IReadOnlyDictionary<uint, WorldEntity>>? playerEntities = null)
|
||||
{
|
||||
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
|
||||
_worldEntities = worldEntities ?? throw new ArgumentNullException(nameof(worldEntities));
|
||||
|
|
@ -58,9 +58,17 @@ public sealed class RadarSnapshotProvider
|
|||
|
||||
public UiRadarSnapshot BuildSnapshot()
|
||||
{
|
||||
// The retained UI mounts before LiveEntityRuntime during GameWindow
|
||||
// startup. Resolve these canonical projections per snapshot so the
|
||||
// provider observes that runtime once it is installed, rather than
|
||||
// retaining the bootstrap empty-map sentinels forever.
|
||||
IReadOnlyDictionary<uint, WorldEntity> worldEntities = _worldEntities();
|
||||
IReadOnlyDictionary<uint, WorldEntity> playerEntities = _playerEntities();
|
||||
IReadOnlyDictionary<uint, WorldSession.EntitySpawn> spawns = _spawns();
|
||||
|
||||
bool uiLocked = _uiLocked();
|
||||
uint playerGuid = _playerGuid();
|
||||
if (playerGuid == 0u || !_playerEntities.TryGetValue(playerGuid, out var playerEntity))
|
||||
if (playerGuid == 0u || !playerEntities.TryGetValue(playerGuid, out var playerEntity))
|
||||
return UiRadarSnapshot.Empty with { UiLocked = uiLocked };
|
||||
|
||||
float heading = MoveToMath.HeadingFromYaw(_playerYawRadians());
|
||||
|
|
@ -70,15 +78,15 @@ public sealed class RadarSnapshotProvider
|
|||
? playerCoordinates.CombinedText
|
||||
: null;
|
||||
|
||||
uint playerPwd = _spawns.TryGetValue(playerGuid, out var playerSpawn)
|
||||
uint playerPwd = spawns.TryGetValue(playerGuid, out var playerSpawn)
|
||||
? playerSpawn.ObjectDescriptionFlags ?? 0u
|
||||
: 0u;
|
||||
var playerTraits = RadarObjectTraits.FromPublicWeenieDescription(
|
||||
(uint)(_objects.Get(playerGuid)?.Type ?? ItemType.None), playerPwd);
|
||||
float range = RetailRadar.GetRangeMeters(isOutside);
|
||||
|
||||
var blips = new List<UiRadarBlip>(_worldEntities.Count);
|
||||
foreach (var pair in _worldEntities)
|
||||
var blips = new List<UiRadarBlip>(worldEntities.Count);
|
||||
foreach (var pair in worldEntities)
|
||||
{
|
||||
uint guid = pair.Key;
|
||||
if (guid == playerGuid)
|
||||
|
|
@ -86,7 +94,7 @@ public sealed class RadarSnapshotProvider
|
|||
|
||||
var entity = pair.Value;
|
||||
var clientObject = _objects.Get(guid);
|
||||
_spawns.TryGetValue(guid, out var spawn);
|
||||
spawns.TryGetValue(guid, out var spawn);
|
||||
|
||||
byte? rawBehavior = clientObject?.RadarBehavior ?? spawn.RadarBehavior;
|
||||
if (rawBehavior is null
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ public sealed record InventoryRuntimeBindings(
|
|||
Action<uint, uint, int>? SendPutItemInContainer,
|
||||
Action<uint, uint, uint, uint>? SendStackableSplitToContainer,
|
||||
Action<uint, uint, uint>? SendStackableMerge,
|
||||
Action<uint, uint>? SendWield,
|
||||
ItemInteractionController ItemInteraction,
|
||||
SelectionState Selection);
|
||||
|
||||
|
|
@ -934,8 +933,8 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
StackSplitQuantity,
|
||||
b.ResolveDragIcon);
|
||||
PaperdollController paperdoll = PaperdollController.Bind(
|
||||
layout, b.Objects, b.PlayerGuid, b.ResolveIcon, b.Selection, b.SendWield,
|
||||
contents, _bindings.Assets.DefaultFont, b.ItemInteraction, paperdollClickMap,
|
||||
layout, b.Objects, b.PlayerGuid, b.ResolveIcon, b.Selection, b.ItemInteraction,
|
||||
contents, _bindings.Assets.DefaultFont, paperdollClickMap,
|
||||
b.ResolveDragIcon, paperdollEmptySprites);
|
||||
Host.WindowManager.AttachController(
|
||||
WindowNames.Inventory,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue