mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-11 12:27:35 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1242f1dd9f | ||
|
|
de0c41dd1d | ||
|
|
3e22e2c087 | ||
|
|
beb9dec6e1 |
@@ -110,7 +110,7 @@ PLAYER_FACTION_COLORS =
|
||||
{
|
||||
[0] = PLAYER_FACTION_COLOR_HORDE,
|
||||
[1] = PLAYER_FACTION_COLOR_ALLIANCE,
|
||||
};-- Quest
|
||||
};
|
||||
|
||||
WOW_PROJECT_MAINLINE = 1;
|
||||
WOW_PROJECT_CLASSIC = 2;
|
||||
|
||||
+6
-1
@@ -27,7 +27,12 @@ set(TINYCBOR_SOURCES
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS} ${TINYCBOR_SOURCES})
|
||||
|
||||
set(CLASSICAPI_VERSION_VALUE 1)
|
||||
# Dev-build sentinel — represents "newer than every plausible
|
||||
# release". Encoded as v9999.99.99 = 9999 * 10000 + 99 * 100 + 99 =
|
||||
# 99,999,999. Picked so addon-side feature gates of the form
|
||||
# `CLASSIC_API_VERSION >= X` always pass during local development;
|
||||
# CI release builds overwrite it via `-DCLASSICAPI_TAG=vX.Y.Z`.
|
||||
set(CLASSICAPI_VERSION_VALUE 99999999)
|
||||
|
||||
set(CLASSICAPI_TAG
|
||||
""
|
||||
|
||||
@@ -23,7 +23,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [Class](docs/API.md#class) | `FillLocalizedClassList` |
|
||||
| [Combat](docs/API.md#combat) | `InCombatLockdown` |
|
||||
| [CVar](docs/API.md#cvar) | `C_CVar.GetCVarBool` |
|
||||
| [Container](docs/API.md#container) | `C_Container.GetContainerItemCharges`, `C_Container.GetContainerItemDurability`, `C_Container.GetContainerItemID`, `C_Container.GetContainerItemRepairCost`, `C_Container.GetContainerNumFreeSlots`, `C_Container.GetItemCooldown`, `C_Container.MoveItem`, `C_Container.PlayerHasHearthstone`, `C_Container.SwapItems`, `C_Container.UseHearthstone`, `GetItemCooldown` |
|
||||
| [Container](docs/API.md#container) | `C_Container.GetContainerItemCharges`, `C_Container.GetContainerItemDurability`, `C_Container.GetContainerItemID`, `C_Container.GetContainerItemRepairCost`, `C_Container.GetContainerNumFreeSlots`, `C_Container.GetItemCooldown`, `C_Container.IsContainerItemOpenable`, `C_Container.MoveItem`, `C_Container.PlayerHasHearthstone`, `C_Container.SwapItems`, `C_Container.UseHearthstone`, `GetItemCooldown` |
|
||||
| EncodingUtil | `C_EncodingUtil.CompressString`, `C_EncodingUtil.DecompressString`, `C_EncodingUtil.EncodeBase64`, `C_EncodingUtil.DecodeBase64`, `C_EncodingUtil.EncodeHex`, `C_EncodingUtil.DecodeHex`, `C_EncodingUtil.SerializeJSON`, `C_EncodingUtil.DeserializeJSON`, `C_EncodingUtil.SerializeCBOR`, `C_EncodingUtil.DeserializeCBOR` |
|
||||
| [EquipmentSet](docs/API.md#equipmentset) | `C_EquipmentSet.CanUseEquipmentSets`, `C_EquipmentSet.ClearIgnoredSlotsForSave`, `C_EquipmentSet.CreateEquipmentSet`, `C_EquipmentSet.DeleteEquipmentSet`, `C_EquipmentSet.EquipmentSetContainsLockedItems`, `C_EquipmentSet.GetEquipmentSetID`, `C_EquipmentSet.GetEquipmentSetIDs`, `C_EquipmentSet.GetEquipmentSetInfo`, `C_EquipmentSet.GetIgnoredSlots`, `C_EquipmentSet.GetItemIDs`, `C_EquipmentSet.GetItemLocations`, `C_EquipmentSet.GetNumEquipmentSets`, `C_EquipmentSet.IgnoreSlotForSave`, `C_EquipmentSet.IsSlotIgnoredForSave`, `C_EquipmentSet.ModifyEquipmentSet`, `C_EquipmentSet.SaveEquipmentSet`, `C_EquipmentSet.UnignoreSlotForSave`, `C_EquipmentSet.UseEquipmentSet` (sets persist to `WTF\Account\...\ClassicAPI_EquipmentSets.txt`) |
|
||||
| [Events](docs/API.md#events) | `C_EventUtils.IsEventValid` |
|
||||
@@ -36,7 +36,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [Hooks](docs/API.md#hooks) | `hooksecurefunc` |
|
||||
| [Input](docs/API.md#input) | `IsLeftAltKeyDown`, `IsLeftControlKeyDown`, `IsLeftShiftKeyDown`, `IsModifierKeyDown`, `IsRightAltKeyDown`, `IsRightControlKeyDown`, `IsRightShiftKeyDown` |
|
||||
| [Instance](docs/API.md#instance) | `GetInstanceInfo` |
|
||||
| [Item](docs/API.md#item) | `C_Item.DoesItemExist`, `C_Item.DoesItemExistByID`, `C_Item.EquipItemByName`, `C_Item.GetCurrentItemLevel`, `C_Item.GetDetailedItemLevelInfo`, `C_Item.GetItemCount`, `C_Item.GetItemFamily`, `C_Item.GetItemGUID`, `C_Item.GetItemIcon`, `C_Item.GetItemIconByID`, `C_Item.GetItemID`, `C_Item.GetItemInfoInstant`, `C_Item.GetItemInventoryType`, `C_Item.GetItemInventoryTypeByID`, `C_Item.GetItemLink`, `C_Item.GetItemLocation`, `C_Item.GetItemMaxStackSize`, `C_Item.GetItemMaxStackSizeByID`, `C_Item.GetItemName`, `C_Item.GetItemNameByID`, `C_Item.GetItemQuality`, `C_Item.GetItemQualityByID`, `C_Item.GetItemSellPrice`, `C_Item.GetItemSellPriceByID`, `C_Item.GetItemSetID`, `C_Item.GetItemSetIDByID`, `C_Item.GetItemSetInfo`, `C_Item.GetItemSpell`, `C_Item.GetItemUniqueness`, `C_Item.GetItemUniquenessByID`, `C_Item.GetStackCount`, `C_Item.GetWeaponEnchantInfo`, `C_Item.IsBound`, `C_Item.IsEquippableItem`, `C_Item.IsEquippedItem`, `C_Item.IsItemDataCached`, `C_Item.IsItemDataCachedByID`, `C_Item.IsLocked`, `C_Item.LockItem`, `C_Item.LockItemByGUID`, `C_Item.UnlockAllItems`, `C_Item.UnlockItem`, `C_Item.RequestLoadItemData`, `C_Item.RequestLoadItemDataByID`, `C_Item.UseItemByName`, `GetAuctionItemID`, `GetAuctionSellItemID`, `GetAverageItemLevel`, `GetCraftReagentItemID`, `GetInboxItemID`, `GetInventoryItemDurability`, `GetInventoryItemID`, `GetInventoryItemsForSlot`, `GetInventoryItemRepairCost`, `GetItemIcon`, `GetLootRollItemID`, `GetLootSlotItemID`, `GetMerchantItemID`, `GetQuestItemID`, `GetQuestLogItemID`, `GetTradePlayerItemID`, `GetTradeSkillItemID`, `GetTradeSkillReagentItemID`, `GetTradeTargetItemID`, `OffhandHasWeapon` |
|
||||
| [Item](docs/API.md#item) | `C_Item.DoesItemExist`, `C_Item.DoesItemExistByID`, `C_Item.EquipItemByName`, `C_Item.GetCurrentItemLevel`, `C_Item.GetDetailedItemLevelInfo`, `C_Item.GetItemCount`, `C_Item.GetItemFamily`, `C_Item.GetItemGUID`, `C_Item.GetItemIcon`, `C_Item.GetItemIconByID`, `C_Item.GetItemID`, `C_Item.GetItemInfoInstant`, `C_Item.GetItemInventoryType`, `C_Item.GetItemInventoryTypeByID`, `C_Item.GetItemLink`, `C_Item.GetItemLocation`, `C_Item.GetItemMaxStackSize`, `C_Item.GetItemMaxStackSizeByID`, `C_Item.GetItemName`, `C_Item.GetItemNameByID`, `C_Item.GetItemQuality`, `C_Item.GetItemQualityByID`, `C_Item.GetItemSellPrice`, `C_Item.GetItemSellPriceByID`, `C_Item.GetItemSetID`, `C_Item.GetItemSetIDByID`, `C_Item.GetItemSetInfo`, `C_Item.GetItemSpell`, `C_Item.GetItemUniqueness`, `C_Item.GetItemUniquenessByID`, `C_Item.GetStackCount`, `C_Item.GetWeaponEnchantInfo`, `C_Item.IsBound`, `C_Item.IsEquippableItem`, `C_Item.IsEquippedItem`, `C_Item.IsItemDataCached`, `C_Item.IsItemDataCachedByID`, `C_Item.IsItemOpenable`, `C_Item.IsItemOpenableByID`, `C_Item.IsLocked`, `C_Item.LockItem`, `C_Item.LockItemByGUID`, `C_Item.UnlockAllItems`, `C_Item.UnlockItem`, `C_Item.RequestLoadItemData`, `C_Item.RequestLoadItemDataByID`, `C_Item.UseItemByName`, `GetAuctionItemID`, `GetAuctionSellItemID`, `GetAverageItemLevel`, `GetCraftReagentItemID`, `GetInboxItemID`, `GetInventoryItemDurability`, `GetInventoryItemID`, `GetInventoryItemsForSlot`, `GetInventoryItemRepairCost`, `GetItemIcon`, `GetLootRollItemID`, `GetLootSlotItemID`, `GetMerchantItemID`, `GetQuestItemID`, `GetQuestLogItemID`, `GetTradePlayerItemID`, `GetTradeSkillItemID`, `GetTradeSkillReagentItemID`, `GetTradeTargetItemID`, `OffhandHasWeapon` |
|
||||
| [Macros](docs/API.md#macros) | `GetMacroSpell` |
|
||||
| [Map](docs/API.md#map) | `C_Map.GetBestMapForUnit` |
|
||||
| [MerchantFrame](docs/API.md#merchantframe) | `C_MerchantFrame.GetBuybackItemID`, `C_MerchantFrame.GetItemInfo`, `C_MerchantFrame.GetNumJunkItems`, `C_MerchantFrame.IsMerchantItemRefundable`, `C_MerchantFrame.IsSellAllJunkEnabled`, `C_MerchantFrame.SellAllJunkItems` |
|
||||
|
||||
+78
-5
@@ -36,6 +36,7 @@ build instructions.
|
||||
- [`C_Container.GetContainerItemRepairCost(containerIndex, slotIndex)`](#c_containergetcontaineritemrepaircostcontainerindex-slotindex)
|
||||
- [`C_Container.GetContainerItemCharges(containerIndex, slotIndex)`](#c_containergetcontaineritemchargescontainerindex-slotindex)
|
||||
- [`C_Container.GetContainerNumFreeSlots(bagID)`](#c_containergetcontainernumfreeslotsbagid)
|
||||
- [`C_Container.IsContainerItemOpenable(containerIndex, slotIndex)`](#c_containeriscontaineritemopenablecontainerindex-slotindex)
|
||||
- [`C_Container.PlayerHasHearthstone()`](#c_containerplayerhashearthstone)
|
||||
- [`C_Container.UseHearthstone()`](#c_containerusehearthstone)
|
||||
- [`C_Container.SwapItems(srcBag, srcSlot, dstBag, dstSlot)`](#c_containerswapitemssrcbag-srcslot-dstbag-dstslot)
|
||||
@@ -178,6 +179,7 @@ build instructions.
|
||||
- [`C_Item.GetItemUniqueness(itemLocation)` / `C_Item.GetItemUniquenessByID(item)`](#c_itemgetitemuniquenessitemlocation--c_itemgetitemuniquenessbyiditem)
|
||||
- [`C_Item.GetItemLink(itemLocation)`](#c_itemgetitemlinkitemlocation)
|
||||
- [`C_Item.GetItemInventoryType(itemLocation)` / `C_Item.GetItemInventoryTypeByID(item)`](#c_itemgetiteminventorytypeitemlocation--c_itemgetiteminventorytypebyiditem)
|
||||
- [`C_Item.IsItemOpenable(itemLocation)` / `C_Item.IsItemOpenableByID(item)`](#c_itemisitemopenableitemlocation--c_itemisitemopenablebyiditem)
|
||||
- [`C_Item.IsLocked(itemLocation)`](#c_itemislockeditemlocation)
|
||||
- [`C_Item.LockItem(itemLocation)`](#c_itemlockitemitemlocation)
|
||||
- [`C_Item.LockItemByGUID(itemGUID)`](#c_itemlockitembyguiditemguid)
|
||||
@@ -880,6 +882,28 @@ internally and counts slots that resolve to a null `CGItem *` (i.e.,
|
||||
empty). Cross-checked in-game against a manual
|
||||
`C_Container.GetContainerItemID` walk; counts match.
|
||||
|
||||
### `C_Container.IsContainerItemOpenable(containerIndex, slotIndex)`
|
||||
|
||||
Positional-arg wrapper for [`C_Item.IsItemOpenable`](#c_itemisitemopenableitemlocation--c_itemisitemopenablebyiditem)
|
||||
against a bag/slot pair. Returns `true` if the slot holds a right-
|
||||
click-openable item (sack, clam, lockbox, etc.), `false` for non-
|
||||
openable items, and `nil` for empty slots or items whose data hasn't
|
||||
been cached yet.
|
||||
|
||||
```lua
|
||||
-- Sweep main backpack for openables
|
||||
for slot = 1, GetContainerNumSlots(0) do
|
||||
if C_Container.IsContainerItemOpenable(0, slot) then
|
||||
-- ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Same cache-warming caveat as the underlying `C_Item.IsItemOpenable` —
|
||||
a freshly-seen item may report `false` for one call while the
|
||||
`SMSG_ITEM_QUERY_SINGLE` round-trips. Bag-resident items are almost
|
||||
always already cached.
|
||||
|
||||
### `C_Container.PlayerHasHearthstone()`
|
||||
|
||||
Returns the itemID of the hearthstone if one is in the player's bags,
|
||||
@@ -2718,14 +2742,28 @@ lines; re-saving a set repopulates itemIDs.
|
||||
|
||||
### `CLASSIC_API_VERSION`
|
||||
|
||||
Defined once FrameScript has booted. Addons can use this to detect that
|
||||
the DLL is loaded and which version is in use. The value is
|
||||
`X*10000 + Y*100 + Z` for a tag of `vX.Y.Z` passed to CMake at configure
|
||||
time via `-DCLASSICAPI_TAG=vX.Y.Z`. Unset versions resolve to `0`.
|
||||
Defined once FrameScript has booted. Addons can use this to detect
|
||||
that the DLL is loaded and which version is in use. The value is
|
||||
`X*10000 + Y*100 + Z` for a tag of `vX.Y.Z` passed to CMake at
|
||||
configure time via `-DCLASSICAPI_TAG=vX.Y.Z`.
|
||||
|
||||
Untagged builds (local dev, CI without a release tag) get a
|
||||
**sentinel value of `99999999`** — encoded as `v9999.99.99`, chosen
|
||||
to be higher than every plausible real release so addon-side
|
||||
feature gates like `CLASSIC_API_VERSION >= 10200` don't reject the
|
||||
dev build.
|
||||
|
||||
```lua
|
||||
if CLASSIC_API_VERSION and CLASSIC_API_VERSION >= 10200 then
|
||||
-- ClassicAPI v1.2.0 or newer is loaded
|
||||
-- ClassicAPI v1.2.0 or newer is loaded (or untagged dev build)
|
||||
end
|
||||
```
|
||||
|
||||
If you specifically want to detect a dev build:
|
||||
|
||||
```lua
|
||||
if CLASSIC_API_VERSION == 99999999 then
|
||||
-- running against a locally-built, untagged DLL
|
||||
end
|
||||
```
|
||||
|
||||
@@ -4056,6 +4094,41 @@ if t == 1 then -- head
|
||||
end
|
||||
```
|
||||
|
||||
### `C_Item.IsItemOpenable(itemLocation)` / `C_Item.IsItemOpenableByID(item)`
|
||||
|
||||
Returns `true` if the item is right-click-openable — a sack, clam,
|
||||
simple chest, quest box, lockbox, etc. (anything whose tooltip would
|
||||
show `<Right Click to Open>`). Intrinsic to the item type, not
|
||||
contextual: a locked lockbox you can't currently open still reports
|
||||
`true` here.
|
||||
|
||||
```lua
|
||||
-- Loop a bag and gather every openable
|
||||
for slot = 1, GetContainerNumSlots(0) do
|
||||
local itemID = C_Container.GetContainerItemID(0, slot)
|
||||
if itemID and C_Item.IsItemOpenableByID(itemID) then
|
||||
-- pickup + use
|
||||
end
|
||||
end
|
||||
|
||||
-- Single-slot check via itemLocation
|
||||
if C_Item.IsItemOpenable({bagID = 0, slotIndex = 1}) then
|
||||
-- ...
|
||||
end
|
||||
```
|
||||
|
||||
Reads the openable bit (`Flags & 0x4`) from the client-side ItemSparse
|
||||
cache. Same bit the engine's tooltip builder uses to gate the
|
||||
`ITEM_OPENABLE` string. Returns `nil` for uncached items (so callers
|
||||
can distinguish "data unknown" from "definitely not openable") and
|
||||
fires a background `SMSG_ITEM_QUERY_SINGLE` so a follow-up call after
|
||||
`GET_ITEM_INFO_RECEIVED` resolves to a real `true` / `false`; in
|
||||
practice bag items are cached on bag-update and resolve instantly.
|
||||
|
||||
The "ByID" variant accepts a numeric itemID, an item link, or an
|
||||
`"item:NNN..."` string — same input shape as
|
||||
`C_Item.GetItemInfoInstant`.
|
||||
|
||||
### `C_Item.IsLocked(itemLocation)`
|
||||
|
||||
Returns `true` if the item is in the client-side "in-transaction"
|
||||
|
||||
@@ -1060,6 +1060,24 @@ enum Offsets {
|
||||
OFF_ITEMSTATS_NAME = 0x08,
|
||||
OFF_ITEMSTATS_DISPLAY_INFO_ID = 0x18,
|
||||
OFF_ITEMSTATS_QUALITY = 0x1C, // u32 — 0=Poor … 5=Legendary
|
||||
// `m_flags` — item-class flags bitfield. Bits we read:
|
||||
// `ITEMSTATS_FLAG_OPENABLE` (`0x4`, bit 2): freely right-click-
|
||||
// openable container (sack, clam, simple chest, quest box).
|
||||
// The tooltip line `<Right Click to Open>` (`ITEM_OPENABLE`
|
||||
// string) renders for items with this bit set, gated on the
|
||||
// player's lock skill when the item also carries a non-zero
|
||||
// LockID. Same bit modern WoW's `C_Item.IsItemOpenable`
|
||||
// surfaces.
|
||||
// Other documented bits (`0x1` = BIND_ON_PICKUP, `0x2` = CONJURED,
|
||||
// `0x10` = LOOTABLE, `0x200` = WRAPPER, etc.) live in the same
|
||||
// field but we don't read them here. Add named constants as
|
||||
// consumers materialize.
|
||||
// Reader at `0x0052E323` (the tooltip-builder's openable branch in
|
||||
// `FUN_0052B650`) does `(ItemStats[+0x20] & 0x4)` against this
|
||||
// field; verified by checking the field offset matches the
|
||||
// 5-DWORD-per-row layout `puVar4[8]` resolves to in the decompile.
|
||||
OFF_ITEMSTATS_FLAGS = 0x20,
|
||||
ITEMSTATS_FLAG_OPENABLE = 0x4,
|
||||
OFF_ITEMSTATS_SELL_PRICE = 0x28,
|
||||
OFF_ITEMSTATS_INVENTORY_TYPE = 0x2C,
|
||||
OFF_ITEMSTATS_ITEM_LEVEL = 0x38, // u32 — base ilvl from ItemSparse
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `C_Container.IsContainerItemOpenable(bag, slot)` — positional-arg
|
||||
// wrapper around `Item::Openable::IsItemIDOpenable`. Same chain
|
||||
// `C_Container.GetContainerItemDurability` and friends use:
|
||||
// `ResolveBag` → `GetItemBySlot` → instance-block itemID → cache flag
|
||||
// read.
|
||||
|
||||
#include "Game.h"
|
||||
#include "item/ID.h"
|
||||
#include "item/Location.h"
|
||||
#include "item/Openable.h"
|
||||
|
||||
namespace Container::Openable {
|
||||
|
||||
namespace {
|
||||
|
||||
int __fastcall Script_C_Container_IsContainerItemOpenable(void *L) {
|
||||
if (!Game::Lua::IsNumber(L, 1) || !Game::Lua::IsNumber(L, 2)) {
|
||||
Game::Lua::Error(L,
|
||||
"Usage: C_Container.IsContainerItemOpenable(containerIndex, slotIndex)");
|
||||
return 0;
|
||||
}
|
||||
const int bagID = static_cast<int>(Game::Lua::ToNumber(L, 1));
|
||||
const int slotIndex = static_cast<int>(Game::Lua::ToNumber(L, 2));
|
||||
const int itemID = Item::ID::FromCGItem(
|
||||
Item::Location::ResolveBag(L, bagID, slotIndex));
|
||||
return Item::Openable::PushIsItemOpenable(L, static_cast<uint32_t>(itemID));
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_Container", "IsContainerItemOpenable",
|
||||
&Script_C_Container_IsContainerItemOpenable);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Container::Openable
|
||||
@@ -0,0 +1,94 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `C_Item.IsItemOpenable` / `C_Item.IsItemOpenableByID` — backport of
|
||||
// the modern WoW accessor that says "right-clicking this item triggers
|
||||
// an open/loot interaction" (clams, sacks, simple chests, quest
|
||||
// boxes). The flag is intrinsic to the item type, read from the
|
||||
// client-side ItemSparse cache.
|
||||
//
|
||||
// Vanilla 1.12's tooltip builder gates the `<Right Click to Open>`
|
||||
// string (`ITEM_OPENABLE`) on `ItemStats_C.Flags & 0x4` (see
|
||||
// `Offsets::ITEMSTATS_FLAG_OPENABLE`). We surface the same bit.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
#include "item/Arg.h"
|
||||
#include "item/Data.h"
|
||||
#include "item/ID.h"
|
||||
#include "item/Location.h"
|
||||
#include "item/Openable.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Item::Openable {
|
||||
|
||||
namespace {
|
||||
|
||||
using GetItemRecord_t = const uint8_t *(__thiscall *)(void *cache, uint32_t itemID,
|
||||
const uint64_t *guid, void *callback,
|
||||
void *userData, int unused);
|
||||
|
||||
const uint8_t *PeekItemRecord(uint32_t itemID) {
|
||||
auto fn = reinterpret_cast<GetItemRecord_t>(Offsets::FUN_DBCACHE_ITEMSTATS_GET_RECORD);
|
||||
auto *cache = reinterpret_cast<void *>(Offsets::VAR_ITEMDB_CACHE);
|
||||
const uint64_t zeroGuid = 0;
|
||||
return fn(cache, itemID, &zeroGuid, nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int PushIsItemOpenable(void *L, uint32_t itemID) {
|
||||
if (itemID == 0)
|
||||
return 0;
|
||||
const uint8_t *record = PeekItemRecord(itemID);
|
||||
if (record == nullptr) {
|
||||
// Background fill so a follow-up call (after the cache warms)
|
||||
// returns the real value. Same pattern Quality / Info use.
|
||||
Item::Data::WarmCache(itemID);
|
||||
return 0;
|
||||
}
|
||||
const uint32_t flags = *reinterpret_cast<const uint32_t *>(
|
||||
record + Offsets::OFF_ITEMSTATS_FLAGS);
|
||||
Game::Lua::PushBool(L, (flags & Offsets::ITEMSTATS_FLAG_OPENABLE) != 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int __fastcall Script_C_Item_IsItemOpenable(void *L) {
|
||||
if (!Item::Location::IsLocationArg(L, 1)) {
|
||||
Game::Lua::Error(L, "Usage: C_Item.IsItemOpenable(itemLocation)");
|
||||
return 0;
|
||||
}
|
||||
const int itemID = Item::ID::FromCGItem(Item::Location::Resolve(L, 1));
|
||||
return PushIsItemOpenable(L, static_cast<uint32_t>(itemID));
|
||||
}
|
||||
|
||||
int __fastcall Script_C_Item_IsItemOpenableByID(void *L) {
|
||||
const int itemID = Item::Arg::ResolveItemID(L, 1);
|
||||
return PushIsItemOpenable(L, static_cast<uint32_t>(itemID));
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_Item", "IsItemOpenable",
|
||||
&Script_C_Item_IsItemOpenable);
|
||||
Game::Lua::RegisterTableFunction("C_Item", "IsItemOpenableByID",
|
||||
&Script_C_Item_IsItemOpenableByID);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Item::Openable
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU Lesser General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Item::Openable {
|
||||
|
||||
// Pushes the openable bool for `itemID` and returns 1, OR pushes
|
||||
// nothing and returns 0 when the item is uncached / itemID is
|
||||
// invalid. Caller can `return PushIsItemOpenable(...)` from a Lua
|
||||
// C function; pushing 0 surfaces as `nil` to the Lua caller, which
|
||||
// distinguishes "data unknown" from "definitely not openable".
|
||||
//
|
||||
// Reads `ItemStats_C.Flags & 0x4` from the client-side item cache.
|
||||
// Fires a background `SMSG_ITEM_QUERY_SINGLE` for uncached items so
|
||||
// a follow-up call after `GET_ITEM_INFO_RECEIVED` resolves correctly.
|
||||
int PushIsItemOpenable(void *L, uint32_t itemID);
|
||||
|
||||
} // namespace Item::Openable
|
||||
Reference in New Issue
Block a user