fix(items): port retail useability and autowear rejection

Use the exact low USEABLE_NO-bit predicate so reset/zero-valued direct-use items such as Blackmoor's Favor reach the ordinary Use request. Port AutoWear's clothing-priority blocker lookup and exact named system notice through the shared activation owner.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 08:33:31 +02:00
parent 60387668d0
commit f71f947475
13 changed files with 289 additions and 57 deletions

View file

@ -164,6 +164,26 @@ internal sealed class AutoWieldController : IDisposable
return SendWield(item, requestedMask, combatModeAfterWield);
}
if (ItemEquipRules.IsAutoWearItem(item))
{
if (!AutoWearIsLegal(item, out ClientObject? blocker))
{
if (blocker is not null)
{
_systemMessage?.Invoke(
$"You must remove your {blocker.GetAppropriateName()} to wear that");
}
return false;
}
// CPlayerSystem::AutoWear @ 0x005601C0 always supplies the complete
// PublicWeenieDesc valid-locations mask to UIAttemptWield.
return SendWield(
item,
item.ValidLocations,
combatModeAfterWield: null);
}
EquipMask weaponLocation = requestedMask == EquipMask.None
? item.ValidLocations & WeaponReadyMask
: requestedMask & WeaponReadyMask;
@ -424,9 +444,6 @@ internal sealed class AutoWieldController : IDisposable
private EquipMask BestAvailableEquipMask(ClientObject item)
{
if (ItemEquipRules.IsAutoWearItem(item))
return AutoWearIsLegal(item) ? item.ValidLocations : EquipMask.None;
foreach (EquipMask mask in AutoEquipOrder)
{
if ((item.ValidLocations & mask) == EquipMask.None)
@ -437,16 +454,22 @@ internal sealed class AutoWieldController : IDisposable
return EquipMask.None;
}
private bool AutoWearIsLegal(ClientObject item)
private bool AutoWearIsLegal(
ClientObject item,
out ClientObject? blocker)
{
uint priorityMask = EquippedAutoWearPriorityMask(item.ObjectId);
if ((item.Priority & priorityMask) == 0)
{
blocker = null;
return true;
}
EquipMask occupiedLocations = EquippedAutoWearLocationMask(item.ObjectId)
& item.ValidLocations;
return GetEquippedObjectAtLocation(
occupiedLocations, item.Priority, item.ObjectId) is null;
blocker = GetEquippedObjectAtLocation(
occupiedLocations, item.Priority, item.ObjectId);
return false;
}
private bool EquipMaskOccupied(EquipMask mask, uint exceptGuid)
@ -455,11 +478,10 @@ internal sealed class AutoWieldController : IDisposable
private uint EquippedAutoWearPriorityMask(uint exceptGuid)
{
uint mask = 0;
foreach (ClientObject item in _objects.Objects)
foreach (ClientObject item in _objects.GetEquippedBy(_playerGuid()))
{
if (item.ObjectId == exceptGuid
|| (item.CurrentlyEquippedLocation & ItemEquipRules.AutoWearMask) == EquipMask.None
|| !IsEquippedByPlayer(item))
|| (item.CurrentlyEquippedLocation & ItemEquipRules.AutoWearMask) == EquipMask.None)
continue;
mask |= item.Priority;
}
@ -469,11 +491,10 @@ internal sealed class AutoWieldController : IDisposable
private EquipMask EquippedAutoWearLocationMask(uint exceptGuid)
{
EquipMask mask = EquipMask.None;
foreach (ClientObject item in _objects.Objects)
foreach (ClientObject item in _objects.GetEquippedBy(_playerGuid()))
{
if (item.ObjectId == exceptGuid
|| (item.CurrentlyEquippedLocation & ItemEquipRules.AutoWearMask) == EquipMask.None
|| !IsEquippedByPlayer(item))
|| (item.CurrentlyEquippedLocation & ItemEquipRules.AutoWearMask) == EquipMask.None)
continue;
mask |= item.CurrentlyEquippedLocation;
}
@ -488,10 +509,9 @@ internal sealed class AutoWieldController : IDisposable
if (locationMask == EquipMask.None)
return null;
foreach (ClientObject item in _objects.Objects)
foreach (ClientObject item in _objects.GetEquippedBy(_playerGuid()))
{
if (item.ObjectId == exceptGuid
|| !IsEquippedByPlayer(item)
|| (item.CurrentlyEquippedLocation & locationMask) == EquipMask.None)
continue;
if ((item.Priority & priority) != 0 || priority == 0)
@ -500,13 +520,6 @@ internal sealed class AutoWieldController : IDisposable
return null;
}
private bool IsEquippedByPlayer(ClientObject item)
{
uint player = _playerGuid();
return item.CurrentlyEquippedLocation != EquipMask.None
&& (item.WielderId == player || item.ContainerId == player);
}
internal static bool BlocksUseOfShield(ClientObject item)
{
byte combatUse = item.CombatUse ?? 0;