mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
item: match decorated (suffixed) name via shared MatchesArg predicate
IsEquippedItem(name) and the bag find-by-name (FindByArgInBags, behind
UseItemByName/EquipItemByName/UseAtCursor) compared the caller's string
against the base ItemStats m_name[0], so a random-suffix item ("Foo of
the Owl") only matched when queried by its base name ("Foo"). The modern
client does the opposite -- verified in-game on both the modern client
and ours:
IsEquippedItem("Iridium Chain") -> false
IsEquippedItem("Iridium Chain of the Owl") -> true
IsEquippedItem("iridium chain of the owl") -> true (case-insensitive)
IsEquippedItem(12022) -> true (itemID)
Match the decorated display name instead, via the engine name builder
(Item::Link::NameFromCGItem), which falls back to the base name for
unsuffixed items so plain items still match.
The match predicate was duplicated inline in both call sites -- which is
why this took two edits. Consolidate it into a single
Item::Location::MatchesArg(cgItem, resolvedArg): itemID-compare when
arg.itemID > 0, else case-insensitive decorated-name compare. Both
IsEquippedItem and FindByArgInBags now delegate to it, so the full chain
is single-sourced (Item::Arg parses input -> MatchesArg decides the match
-> NameFromCGItem / ID::FromCGItem supply the comparands). Also drop a
now-unused <string.h> from Equipment.cpp and fix the stale m_name[0] doc
on FindByArgInBags.
This commit is contained in:
+4
-23
@@ -19,7 +19,6 @@
|
||||
#include "item/Swap.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string.h>
|
||||
|
||||
namespace Item::Equipment {
|
||||
|
||||
@@ -132,31 +131,13 @@ int __fastcall Script_C_Item_IsEquippedItem(void *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Equipment-first walk; itemID or decorated-name match via the shared
|
||||
// `Item::Location::MatchesArg` predicate (same logic the bag find-by-
|
||||
// name uses, so match semantics live in one place).
|
||||
for (int slot = Offsets::EQUIPMENT_SLOT_FIRST; slot <= Offsets::EQUIPMENT_SLOT_LAST; ++slot) {
|
||||
auto *item = Item::Location::ResolveEquipmentSlot(slot);
|
||||
if (item == nullptr) continue;
|
||||
|
||||
const int id = Item::ID::FromCGItem(item);
|
||||
if (id == 0) continue;
|
||||
|
||||
if (arg.itemID > 0) {
|
||||
if (id == arg.itemID) {
|
||||
Game::Lua::PushBoolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Name-match path — peek the item-cache record and compare
|
||||
// `m_name[0]`. Uncached items short-circuit to "no match"
|
||||
// rather than firing a load; matches modern API semantics
|
||||
// (sync call, possibly stale, never blocks).
|
||||
auto *record = PeekItemRecord(static_cast<uint32_t>(id));
|
||||
if (record == nullptr) continue;
|
||||
const char *name = *reinterpret_cast<const char *const *>(
|
||||
record + Offsets::OFF_ITEMSTATS_NAME);
|
||||
if (name == nullptr) continue;
|
||||
if (_stricmp(name, arg.name) == 0) {
|
||||
if (Item::Location::MatchesArg(item, arg)) {
|
||||
Game::Lua::PushBoolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
+21
-16
@@ -18,6 +18,7 @@
|
||||
#include "../guid/Guid.h"
|
||||
#include "Arg.h"
|
||||
#include "ID.h"
|
||||
#include "Link.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -186,6 +187,25 @@ bool FindByItemID(void *L, int itemID, ByGUIDResult *out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MatchesArg(const uint8_t *cgItem, const Item::Arg::Resolved &arg) {
|
||||
const int id = Item::ID::FromCGItem(cgItem);
|
||||
if (id == 0)
|
||||
return false;
|
||||
if (arg.itemID > 0)
|
||||
return id == arg.itemID;
|
||||
if (arg.name == nullptr)
|
||||
return false;
|
||||
// Compare against the item's *decorated* display name (random suffix
|
||||
// included) — modern IsEquippedItem / find-by-name match the full name
|
||||
// only ("Foo of the Owl", not the base "Foo"; verified on the modern
|
||||
// client). NameFromCGItem falls back to the base name for unsuffixed
|
||||
// items, so plain items still match; uncached/unnameable → no match.
|
||||
char name[128];
|
||||
if (!Item::Link::NameFromCGItem(cgItem, name, sizeof(name)))
|
||||
return false;
|
||||
return _stricmp(name, arg.name) == 0;
|
||||
}
|
||||
|
||||
bool FindByArgInBags(void *L, const Item::Arg::Resolved &arg, ByGUIDResult *out) {
|
||||
if (arg.itemID <= 0 && arg.name == nullptr)
|
||||
return false;
|
||||
@@ -196,24 +216,9 @@ bool FindByArgInBags(void *L, const Item::Arg::Resolved &arg, ByGUIDResult *out)
|
||||
auto *item = ResolveBag(L, bagID, slotIndex);
|
||||
if (item == nullptr)
|
||||
continue;
|
||||
|
||||
const int id = Item::ID::FromCGItem(item);
|
||||
if (id == 0)
|
||||
if (!MatchesArg(item, arg))
|
||||
continue;
|
||||
|
||||
if (arg.itemID > 0) {
|
||||
if (id != arg.itemID)
|
||||
continue;
|
||||
} else {
|
||||
auto *record = PeekItemRecord(static_cast<uint32_t>(id));
|
||||
if (record == nullptr)
|
||||
continue;
|
||||
const char *name = *reinterpret_cast<const char *const *>(
|
||||
record + Offsets::OFF_ITEMSTATS_NAME);
|
||||
if (name == nullptr || _stricmp(name, arg.name) != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
out->equipmentSlotIndex = 0;
|
||||
out->bagID = bagID;
|
||||
out->slotIndex = slotIndex;
|
||||
|
||||
+12
-3
@@ -103,10 +103,19 @@ namespace Item::Arg { struct Resolved; }
|
||||
|
||||
namespace Item::Location {
|
||||
|
||||
// The single item name/id match predicate: true if `cgItem` satisfies
|
||||
// the resolved arg. `arg.itemID > 0` ⇒ exact itemID compare; otherwise
|
||||
// `arg.name` is matched case-insensitively against the item's *decorated*
|
||||
// display name (random suffix included — via `Item::Link::NameFromCGItem`,
|
||||
// which falls back to the base name for unsuffixed items). Matches modern
|
||||
// `IsEquippedItem` / by-name semantics: "Foo of the Owl" matches, the base
|
||||
// "Foo" does not. Null / uncached / unnameable items → false. Shared by
|
||||
// `FindByArgInBags` and `C_Item.IsEquippedItem` so the match logic lives
|
||||
// in exactly one place.
|
||||
bool MatchesArg(const uint8_t *cgItem, const Item::Arg::Resolved &arg);
|
||||
|
||||
// Walks the player's bags (0..4 only — no equipment) looking for the
|
||||
// first item matching `arg`. `arg.itemID > 0` ⇒ direct ID compare;
|
||||
// otherwise `arg.name` is matched case-insensitively against the
|
||||
// cached `ItemStats_C.m_name[0]` of each candidate. On hit, populates
|
||||
// first item matching `arg` (via `MatchesArg`). On hit, populates
|
||||
// `out->bagID` / `out->slotIndex` / `out->item` and sets
|
||||
// `equipmentSlotIndex = 0`. Stomps the Lua stack.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user