fix(items): complete retail corpse looting feedback
Route corpse Use through the shared ItemHolder policy so Stuck corpses open instead of being sent as pickup requests. Restore the framed, horizontally resizable external-container strip and use a compact initial width. Port retail's target-list pending item projection so loot is marked in the chosen inventory slot without changing canonical ownership before the server confirms. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
20ce67b625
commit
d51a0fc825
13 changed files with 275 additions and 102 deletions
|
|
@ -13232,50 +13232,11 @@ public sealed class GameWindow : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
// 2026-05-16 (Phase B.6 follow-up) — R is the universal "interact"
|
||||
// key. Retail dispatches by TARGET TYPE first; the useability gate
|
||||
// is enforced by each individual action handler (SendUse checks
|
||||
// IsUseableTarget; SendPickUp checks IsPickupableTarget), not as
|
||||
// a top-level block. Previously the IsUseableTarget gate at the
|
||||
// entry point rejected USEABLE_NO=1 items (spell components,
|
||||
// gems) which retail accepts as pickupable — they just aren't
|
||||
// "useable" in the activate-from-world sense.
|
||||
//
|
||||
// Dispatch order:
|
||||
// 1. Creature → SendUse (talk to NPC, attack monster)
|
||||
// 2. Pickupable → SendPickUp (small items, corpses)
|
||||
// 3. Useable → SendUse (doors, portals, lifestones,
|
||||
// potions / scrolls activated from world)
|
||||
// 4. Else → toast "X cannot be used" (signs, banners,
|
||||
// decorative scenery)
|
||||
//
|
||||
// Retail string at acclient_2013_pseudo_c.txt:1033115
|
||||
// (data_7e2a70): "The %s cannot be used".
|
||||
|
||||
bool isCreature = (LiveItemType(sel) & AcDream.Core.Items.ItemType.Creature) != 0;
|
||||
|
||||
if (isCreature)
|
||||
{
|
||||
SendUse(sel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPickupableTarget(sel))
|
||||
{
|
||||
SendPickUp(sel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsUseableTarget(sel))
|
||||
{
|
||||
SendUse(sel);
|
||||
return;
|
||||
}
|
||||
|
||||
string label = DescribeLiveEntity(sel);
|
||||
_debugVm?.AddToast(AcDream.Core.Ui.RetailMessages.CannotBeUsed(label));
|
||||
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled)
|
||||
Console.WriteLine($"[B.4b] R-key ignored — neither pickupable nor useable guid=0x{sel:X8}");
|
||||
// Retail sends keyboard Use through ItemHolder::UseObject @ 0x00588A80,
|
||||
// the same policy owner used by the toolbar. Keeping a second classifier here
|
||||
// made BF_CORPSE containers look pickupable and sent PutItemInContainer,
|
||||
// which ACE correctly rejected as Stuck (0x0029).
|
||||
_itemInteractionController?.UseSelectedOrEnterMode(sel);
|
||||
}
|
||||
|
||||
private void SendUse(uint guid)
|
||||
|
|
@ -13896,29 +13857,6 @@ public sealed class GameWindow : IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2026-05-16. Retail-faithful gate for F-key PickUp / right-click
|
||||
/// "Pick Up." Distinct from <see cref="IsUseableTarget"/> because
|
||||
/// pickup is more restrictive than Use: the entity must be useable
|
||||
/// FROM THE WORLD (USEABLE_REMOTE bit, 0x20). Signs / banners with
|
||||
/// USEABLE_NO (0x1) lack the REMOTE bit so pickup is blocked
|
||||
/// client-side without a wire packet — matches retail's "The X can't
|
||||
/// be picked up!" client-side toast.
|
||||
///
|
||||
/// <para>
|
||||
/// Useable values that include USEABLE_REMOTE (0x20):
|
||||
/// USEABLE_REMOTE (0x20), USEABLE_REMOTE_NEVER_WALK (0x60),
|
||||
/// USEABLE_VIEWED_REMOTE (0x30), and the SOURCE_*_TARGET_REMOTE
|
||||
/// composites in the 0x200000+ range.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Null-useability fallback: same as <see cref="IsUseableTarget"/>
|
||||
/// — permit pickup for entities with BF_CORPSE bit set, and for
|
||||
/// items with small-item ItemType. This preserves M1 ground-item
|
||||
/// pickup flow for entities where ACE didn't publish useability.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 2026-05-16 — pickup gate is ItemType-based, NOT useability-based.
|
||||
///
|
||||
|
|
@ -13935,7 +13873,7 @@ public sealed class GameWindow : IDisposable
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Now matches retail: small-item ItemType class OR BF_CORPSE bit
|
||||
/// Now matches retail: a non-Stuck small-item ItemType class
|
||||
/// → pickupable. Server validates the request server-side
|
||||
/// (in-range, target-still-exists, container-has-room).
|
||||
/// </para>
|
||||
|
|
@ -13961,11 +13899,9 @@ public sealed class GameWindow : IDisposable
|
|||
if (spawn.ObjectDescriptionFlags is { } odf)
|
||||
{
|
||||
const uint BF_STUCK = 0x0004u;
|
||||
const uint BF_CORPSE = 0x2000u;
|
||||
// Corpses are pickupable (loot) — BF_CORPSE wins over
|
||||
// any BF_STUCK that might be coincidentally set.
|
||||
if ((odf & BF_CORPSE) != 0u) return true;
|
||||
// Anything else with BF_STUCK is immovable scenery.
|
||||
// Corpses are BF_STUCK containers: Use opens their contents; the
|
||||
// corpse object itself is never picked up. ItemHolder::DetermineUseResult
|
||||
// @ 0x00588460 excludes Stuck objects from PlaceInBackpack.
|
||||
if ((odf & BF_STUCK) != 0u) return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue