From b381cd551171c64a9a878682f17c0125b24f1600 Mon Sep 17 00:00:00 2001 From: avitasia Date: Mon, 15 Dec 2025 12:05:12 -0800 Subject: [PATCH] add new convenience functions for inventory and queue bugfix --- README.md | 225 ++++++++++ nampower/game.cpp | 12 +- nampower/game.hpp | 884 +++++++++++++++++++++++---------------- nampower/helper.cpp | 41 ++ nampower/helper.hpp | 19 + nampower/items.cpp | 750 ++++++++++++++++++++++++++++++++- nampower/items.hpp | 6 + nampower/main.cpp | 26 +- nampower/main.hpp | 11 +- nampower/offsets.hpp | 5 + nampower/scripts.cpp | 53 +-- nampower/spellcast.cpp | 2 +- nampower/spellevents.cpp | 3 +- 13 files changed, 1613 insertions(+), 424 deletions(-) diff --git a/README.md b/README.md index 307aa61..7780cc4 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,231 @@ local delay = GetItemStatsField(19019, "delay") print("Weapon speed: " .. (delay / 1000) .. " seconds") -- 1.9 seconds ``` +#### FindPlayerItemSlot(itemId or itemName) +Searches the player's inventory for an item by ID or name and returns its location. + +**Parameters:** +- `itemId` (number): The item ID to search for, OR +- `itemName` (string): The item name to search for (case-insensitive) + +**Returns:** +- 1st param (number or nil): Bag index where the item was found + - `nil` = Equipped item (check 2nd param for equipment slot 0-18) + - `0` = Inventory pack + - `1-4` = Regular bags + - `-1` = Bank item slots + - `5-9` = Bank bags + - `-2` = Keyring +- 2nd param (number): Slot number within the bag (or equipment slot if 1st param is nil) + - For equipped items: 0-18 (equipment slots are 0-indexed) + - For bag 0, -1, -2: Returns **relative slot position** (1-indexed, 0-based within bag + 1) + - Bag 0: slots 1-16 (corresponding to absolute slots 23-38) + - Bag -1: slots 1-24 (corresponding to absolute bank slots 39-62) + - Bag -2: slots 1-16 (corresponding to absolute keyring slots 81-96) + - For regular bags (1-4) and bank bags (5-9): Returns 1-indexed slot within the bag +- Returns `nil,nil` if the item is not found + +**Examples:** +```lua +-- Find Thunderfury in player inventory +local bag, slot = FindPlayerItemSlot(19019) +if bag then + print("Found in bag " .. bag .. " slot " .. slot) + if bag == -1 or (bag >= 5 and bag <= 9) then + print("Item is in bank") + end +elseif bag == nil and slot then + print("Item is equipped in slot " .. slot) +else + print("Item not found") +end + +-- Find item by name (uses cache for performance after first lookup) +local bag, slot = FindPlayerItemSlot("Hearthstone") +if slot then + if bag == nil then + print("Hearthstone is equipped in slot " .. slot) + elseif bag == 0 then + print("Hearthstone is in inventory pack slot " .. slot .. " (1-16)") + elseif bag == -1 then + print("Hearthstone is in bank slot " .. slot .. " (1-24)") + elseif bag == -2 then + print("Hearthstone is in keyring slot " .. slot .. " (1-16)") + else + print("Hearthstone is in bag " .. bag .. " slot " .. slot) + end +end +``` + +#### GetEquippedItems(unitToken) +Returns a table containing all equipped items for the specified unit. + +**Parameters:** +- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", etc.) or a GUID string + +**Returns:** +- A Lua table with equipment slot indices as keys (0-18) and item info tables as values +- Returns nil if the unit cannot be found or inspected + +For the player, item info includes: +- `itemId`: The item's ID +- `stackCount`: Number of items in the stack +- `duration`: Item duration in milliseconds +- `spellCharges`: Table of spell charges (indices 1-5) +- `flags`: Item flags +- `permanentEnchantId`: Permanent enchantment ID +- `tempEnchantId`: Temporary enchantment ID +- `tempEnchantmentTimeLeftMs`: Time remaining on temp enchant in milliseconds +- `tempEnchantmentCharges`: Charges remaining on temp enchant +- `durability`: Current durability +- `maxDurability`: Maximum durability + +For other inspected units (limited data): +- `itemId`: The item's ID +- `permanentEnchantId`: Permanent enchantment ID +- `tempEnchantId`: Temporary enchantment ID + +**Examples:** +```lua +-- Get all equipped items for your target +local items = GetEquippedItems("target") +if items then + for slot, itemInfo in pairs(items) do + print("Slot " .. slot .. ": Item ID " .. itemInfo.itemId) + if itemInfo.permanentEnchantId and itemInfo.permanentEnchantId > 0 then + print(" Permanent enchant: " .. itemInfo.permanentEnchantId) + end + end +end + +-- Check player's weapon durability +local items = GetEquippedItems("player") +if items and items[15] then -- slot 15 is main hand + local weapon = items[15] + print("Weapon durability: " .. weapon.durability .. "/" .. weapon.maxDurability) +end +``` + +#### GetEquippedItem(unitToken, slot) +Returns item info for a specific equipment slot on the specified unit. + +**Parameters:** +- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", etc.) or a GUID string +- `slot` (number): Equipment slot number (0-18) + - 1 = Head, 2 = Neck, 3 = Shoulder, 4 = Shirt, 5 = Chest + - 6 = Waist, 7 = Legs, 8 = Feet, 9 = Wrist, 10 = Hands + - 11 = Finger 1, 12 = Finger 2, 13 = Trinket 1, 14 = Trinket 2 + - 15 = Back, 16 = Main Hand, 17 = Off Hand, 18 = Ranged, 19 = Tabard + +**Returns:** +- A Lua table containing the item info (same fields as GetEquippedItems) +- Returns nil if the slot is empty, unit cannot be found, or unit cannot be inspected + +**Examples:** +```lua +-- Check target's main hand weapon +local weapon = GetEquippedItem("target", 16) +if weapon then + print("Target has weapon: " .. weapon.itemId) +else + print("Target has no main hand weapon") +end + +-- Check your own helmet +local helm = GetEquippedItem("player", 1) +if helm and helm.durability then + local durabilityPercent = (helm.durability / helm.maxDurability) * 100 + print("Helmet durability: " .. string.format("%.1f%%", durabilityPercent)) +end +``` + +#### GetBagItems() +Returns a nested table containing all items in all bags (including bank if open). + +**Returns:** +- A Lua table with bag indices as keys and bag contents as values +- Each bag contains **1-indexed** slot numbers as keys and item info tables as values +- Bag indices: + - 0 = Inventory pack (16 slots) + - 1-4 = Regular bags + - -1 = Bank item slots (24 slots, only if bank is open) + - 5-9 = Bank bags (only if bank is open) + - -2 = Keyring + +Item info table fields (same as GetEquippedItems for player): +- `itemId`, `stackCount`, `duration`, `spellCharges`, `flags` +- `permanentEnchantId`, `tempEnchantId`, `tempEnchantmentTimeLeftMs`, `tempEnchantmentCharges` +- `durability`, `maxDurability` + +**Examples:** +```lua +-- Get all items in all bags +local allItems = GetBagItems() +for bagIndex, bagContents in pairs(allItems) do + print("Bag " .. bagIndex .. ":") + for slot, itemInfo in pairs(bagContents) do + print(" Slot " .. slot .. ": " .. itemInfo.itemId .. " (x" .. itemInfo.stackCount .. ")") + end +end + +-- Count total number of a specific item +local function CountItem(itemId) + local total = 0 + local allItems = GetBagItems() + for bagIndex, bagContents in pairs(allItems) do + for slot, itemInfo in pairs(bagContents) do + if itemInfo.itemId == itemId then + total = total + itemInfo.stackCount + end + end + end + return total +end + +local soulShardCount = CountItem(6265) +print("Soul Shards: " .. soulShardCount) +``` + +#### GetBagItem(bagIndex, slot) +Returns item info for a specific slot in a specific bag. + +**Parameters:** +- `bagIndex` (number): The bag to check + - 0 = Inventory pack + - 1-4 = Regular bags + - -1 = Bank item slots or buyback slots + - 5-9 = Bank bags (requires bank to be open) + - -2 = Keyring +- `slot` (number): **1-indexed** slot number within the bag + +**Returns:** +- A Lua table containing the item info (same fields as GetBagItems) +- Returns nil if the slot is empty or invalid + +**Examples:** +```lua +-- Get item in first slot of first bag +local item = GetBagItem(1, 1) +if item then + print("Item ID: " .. item.itemId) + print("Stack count: " .. item.stackCount) +else + print("Slot is empty") +end + +-- Check durability of an item in inventory pack +local item = GetBagItem(0, 1) +if item and item.durability then + print("Durability: " .. item.durability .. "/" .. item.maxDurability) +end + +-- Check if a specific bank slot has an item (bank must be open) +local bankItem = GetBagItem(-1, 1) +if bankItem then + print("Bank slot 1 contains: " .. bankItem.itemId) +end +``` + #### GetSpellRec(spellId) Returns a Lua table containing all fields for the spell's `SpellRec` record (including localized `name` and `rank`). Returns nil if the spell cannot be found. diff --git a/nampower/game.cpp b/nampower/game.cpp index ba1af54..ab2005b 100644 --- a/nampower/game.cpp +++ b/nampower/game.cpp @@ -103,9 +103,17 @@ namespace game { } uint32_t GetItemId(CGItem_C *item) { - uintptr_t *itemInfo = item->m_itemInfo; + return item->object.m_obj->m_entryID; + } - return *reinterpret_cast(itemInfo + 3); // item id offset + uintptr_t *GetObjectVFTable(uintptr_t *unit) { + // Dereference once to get vtable pointer + return *reinterpret_cast(unit); + } + + uintptr_t *GetPlayerInventoryPtr(uintptr_t *playerUnit) { + // Access inventory at byte offset 0x1d38 + return playerUnit + 0x74E; } const char *GetSpellName(uint32_t spellId) { diff --git a/nampower/game.hpp b/nampower/game.hpp index 9269f0b..17f41c2 100644 --- a/nampower/game.hpp +++ b/nampower/game.hpp @@ -186,13 +186,141 @@ namespace game { MAX_ITEM_FLAG = 32768 }; + + typedef enum OBJECT_TYPE_MASK { + TYPE_OBJECT = 1, + TYPE_ITEM = 2, + TYPE_CONTAINER = 4, + TYPE_UNIT = 8, + TYPE_PLAYER = 16, + TYPE_GAMEOBJECT = 32, + TYPE_DYNAMICOBJECT = 64, + TYPE_CORPSE = 128, + TYPE_AIGROUP = 256, + TYPE_AREATRIGGER = 512 + } OBJECT_TYPE_MASK; + + + struct CGObjectData { + uint64_t m_guid; + OBJECT_TYPE_MASK m_type; + int m_entryID; + float m_scale; + uint32_t pad; + }; + + struct ObjectFields { + uint64_t guid; + uint32_t type; + uint32_t entry; + uint32_t scaleX; + uint32_t padding; + }; + + struct ItemEnchantment { + int id; + int duration; + int charges; + }; + + struct CGObject { + void *vftable; + uintptr_t *m_data; + CGObjectData *m_obj; + }; + + struct ItemFields { + uint64_t owner; + uint64_t contained; + uint64_t creator; + uint64_t giftCreator; + uint32_t stackCount; + uint32_t duration; + uint32_t spellCharges[5]; + uint32_t flags; + ItemEnchantment permEnchantmentSlot; + ItemEnchantment tempEnchantmentSlot; + ItemEnchantment maxInspectedEnchantmentSlot; + ItemEnchantment propEnchantmentSlot0; + ItemEnchantment propEnchantmentSlot1; + ItemEnchantment propEnchantmentSlot2; + ItemEnchantment propEnchantmentSlot3; + uint32_t propertySeed; + uint32_t randomPropertiesId; + uint32_t itemTextId; + uint32_t durability; + uint32_t maxDurability; + }; + struct CGItem_C { - uint32_t m_unk; - uint32_t m_flags; - uintptr_t *m_itemInfo; - uint32_t m_expirationTime; - uint32_t m_enchantmentExpiration[5]; - uintptr_t *m_soundsRec; + CGObject object; + int32_t field1; + int32_t field2; + int32_t field3; + int32_t field4; + int32_t field5; + int32_t field6; + int32_t field7; + int32_t field8; + int32_t field9; + int32_t field10; + int32_t field11; + int32_t field12; + int32_t field13; + int32_t field14; + int32_t field15; + int32_t field16; + int32_t field17; + int32_t field18; + int32_t field19; + int32_t field20; + int32_t field21; + int32_t field22; + int32_t field23; + int32_t field24; + int32_t field25; + int32_t field26; + int32_t field27; + int32_t field28; + int32_t field29; + int32_t field30; + int32_t field31; + int32_t field32; + int32_t field33; + int32_t field34; + int32_t field35; + int32_t field36; + int32_t field37; + int32_t field38; + int32_t field39; + int32_t field40; + int32_t field41; + int32_t field42; + int32_t field43; + int32_t field44; + int32_t field45; + int32_t field46; + int32_t field47; + int32_t field48; + int32_t field49; + int32_t field50; + int32_t field51; + int32_t field52; + int32_t field53; + int32_t field54; + int32_t field55; + int32_t field56; + int32_t field57; + int32_t field58; + int32_t field59; + int32_t field60; + int32_t field61; + int32_t field62; + int32_t field63; + int32_t field64; + int32_t field65; + int32_t field66; + ItemFields *itemFields; }; struct __declspec(align(4)) ItemStats_C { @@ -254,6 +382,15 @@ namespace game { }; #pragma pack(pop) + struct CGItem { + uintptr_t *unkPtr; + uint32_t unk; + uint32_t itemId; + uint32_t permanentEnchantId; + uint32_t tempEnchantId; + }; + + enum SpellEffects { SPELL_EFFECT_NONE = 0, SPELL_EFFECT_INSTAKILL = 1, @@ -396,121 +533,121 @@ namespace game { enum SpellCastResult : std::uint8_t { - SPELL_FAILED_AFFECTING_COMBAT = 0, // 0x0 - SPELL_FAILED_ALREADY_AT_FULL_HEALTH = 1, // 0x1 - SPELL_FAILED_ALREADY_AT_FULL_MANA = 2, // 0x2 - SPELL_FAILED_ALREADY_BEING_TAMED = 3, // 0x3 - SPELL_FAILED_ALREADY_HAVE_CHARM = 4, // 0x4 - SPELL_FAILED_ALREADY_HAVE_SUMMON = 5, // 0x5 - SPELL_FAILED_ALREADY_OPEN = 6, // 0x6 + SPELL_FAILED_AFFECTING_COMBAT = 0, // 0x0 + SPELL_FAILED_ALREADY_AT_FULL_HEALTH = 1, // 0x1 + SPELL_FAILED_ALREADY_AT_FULL_MANA = 2, // 0x2 + SPELL_FAILED_ALREADY_BEING_TAMED = 3, // 0x3 + SPELL_FAILED_ALREADY_HAVE_CHARM = 4, // 0x4 + SPELL_FAILED_ALREADY_HAVE_SUMMON = 5, // 0x5 + SPELL_FAILED_ALREADY_OPEN = 6, // 0x6 SPELL_FAILED_MORE_POWERFUL_SPELL_ACTIVE = 7, // 0x7 - SPELL_FAILED_BAD_IMPLICIT_TARGETS = 9, // 0x9 - SPELL_FAILED_BAD_TARGETS = 10, // 0xA - SPELL_FAILED_CANT_BE_CHARMED = 11, // 0xB - SPELL_FAILED_CANT_BE_DISENCHANTED = 12, // 0xC - SPELL_FAILED_CANT_BE_PROSPECTED = 13, // 0xD - SPELL_FAILED_CANT_CAST_ON_TAPPED = 14, // 0xE + SPELL_FAILED_BAD_IMPLICIT_TARGETS = 9, // 0x9 + SPELL_FAILED_BAD_TARGETS = 10, // 0xA + SPELL_FAILED_CANT_BE_CHARMED = 11, // 0xB + SPELL_FAILED_CANT_BE_DISENCHANTED = 12, // 0xC + SPELL_FAILED_CANT_BE_PROSPECTED = 13, // 0xD + SPELL_FAILED_CANT_CAST_ON_TAPPED = 14, // 0xE SPELL_FAILED_CANT_DUEL_WHILE_INVISIBLE = 15, // 0xF SPELL_FAILED_CANT_DUEL_WHILE_STEALTHED = 16, // 0x10 - SPELL_FAILED_CANT_TOO_CLOSE_TO_ENEMY = 17, // 0x11 - SPELL_FAILED_CANT_DO_THAT_YET = 18, // 0x12 - SPELL_FAILED_CASTER_DEAD = 19, // 0x13 - SPELL_FAILED_CHARMED = 20, // 0x14 - SPELL_FAILED_CHEST_IN_USE = 21, // 0x15 - SPELL_FAILED_CONFUSED = 22, // 0x16 - SPELL_FAILED_DONT_REPORT = 23, // 0x17 - SPELL_FAILED_EQUIPPED_ITEM = 24, // 0x18 - SPELL_FAILED_EQUIPPED_ITEM_CLASS = 25, // 0x19 + SPELL_FAILED_CANT_TOO_CLOSE_TO_ENEMY = 17, // 0x11 + SPELL_FAILED_CANT_DO_THAT_YET = 18, // 0x12 + SPELL_FAILED_CASTER_DEAD = 19, // 0x13 + SPELL_FAILED_CHARMED = 20, // 0x14 + SPELL_FAILED_CHEST_IN_USE = 21, // 0x15 + SPELL_FAILED_CONFUSED = 22, // 0x16 + SPELL_FAILED_DONT_REPORT = 23, // 0x17 + SPELL_FAILED_EQUIPPED_ITEM = 24, // 0x18 + SPELL_FAILED_EQUIPPED_ITEM_CLASS = 25, // 0x19 SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND = 26, // 0x1A - SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND = 27, // 0x1B - SPELL_FAILED_ERROR = 28, // 0x1C - SPELL_FAILED_FIZZLE = 29, // 0x1D - SPELL_FAILED_FLEEING = 30, // 0x1E - SPELL_FAILED_FOOD_LOWLEVEL = 31, // 0x1F - SPELL_FAILED_HIGHLEVEL = 32, // 0x20 - SPELL_FAILED_IMMUNE = 34, // 0x22 - SPELL_FAILED_INTERRUPTED = 35, // 0x23 - SPELL_FAILED_INTERRUPTED_COMBAT = 36, // 0x24 - SPELL_FAILED_ITEM_ALREADY_ENCHANTED = 37, // 0x25 - SPELL_FAILED_ITEM_GONE = 38, // 0x26 + SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND = 27, // 0x1B + SPELL_FAILED_ERROR = 28, // 0x1C + SPELL_FAILED_FIZZLE = 29, // 0x1D + SPELL_FAILED_FLEEING = 30, // 0x1E + SPELL_FAILED_FOOD_LOWLEVEL = 31, // 0x1F + SPELL_FAILED_HIGHLEVEL = 32, // 0x20 + SPELL_FAILED_IMMUNE = 34, // 0x22 + SPELL_FAILED_INTERRUPTED = 35, // 0x23 + SPELL_FAILED_INTERRUPTED_COMBAT = 36, // 0x24 + SPELL_FAILED_ITEM_ALREADY_ENCHANTED = 37, // 0x25 + SPELL_FAILED_ITEM_GONE = 38, // 0x26 SPELL_FAILED_ENCHANT_NOT_EXISTING_ITEM = 39, // 0x27 - SPELL_FAILED_ITEM_NOT_READY = 40, // 0x28 - SPELL_FAILED_LEVEL_REQUIREMENT = 41, // 0x29 - SPELL_FAILED_LINE_OF_SIGHT = 42, // 0x2A - SPELL_FAILED_LOWLEVEL = 43, // 0x2B - SPELL_FAILED_SKILL_NOT_HIGH_ENOUGH = 44, // 0x2C - SPELL_FAILED_MAINHAND_EMPTY = 45, // 0x2D - SPELL_FAILED_MOVING = 46, // 0x2E - SPELL_FAILED_NEED_AMMO = 47, // 0x2F - SPELL_FAILED_NEED_REQUIRES_SOMETHING = 48, // 0x30 - SPELL_FAILED_NEED_EXOTIC_AMMO = 49, // 0x31 - SPELL_FAILED_NOPATH = 50, // 0x32 - SPELL_FAILED_NOT_BEHIND = 51, // 0x33 - SPELL_FAILED_NOT_FISHABLE = 52, // 0x34 - SPELL_FAILED_NOT_HERE = 53, // 0x35 - SPELL_FAILED_NOT_INFRONT = 54, // 0x36 - SPELL_FAILED_NOT_IN_CONTROL = 55, // 0x37 - SPELL_FAILED_NOT_KNOWN = 56, // 0x38 - SPELL_FAILED_NOT_MOUNTED = 57, // 0x39 - SPELL_FAILED_NOT_ON_TAXI = 58, // 0x3A - SPELL_FAILED_NOT_ON_TRANSPORT = 59, // 0x3B - SPELL_FAILED_NOT_READY = 60, // 0x3C - SPELL_FAILED_NOT_SHAPESHIFT = 61, // 0x3D - SPELL_FAILED_NOT_STANDING = 62, // 0x3E - SPELL_FAILED_NOT_TRADEABLE = 63, // 0x3F - SPELL_FAILED_NOT_TRADING = 64, // 0x40 - SPELL_FAILED_NOT_UNSHEATHED = 65, // 0x41 - SPELL_FAILED_NOT_WHILE_GHOST = 66, // 0x42 - SPELL_FAILED_NO_AMMO = 67, // 0x43 - SPELL_FAILED_NO_CHARGES_REMAIN = 68, // 0x44 - SPELL_FAILED_NO_CHAMPION = 69, // 0x45 - SPELL_FAILED_NO_COMBO_POINTS = 70, // 0x46 - SPELL_FAILED_NO_DUELING = 71, // 0x47 - SPELL_FAILED_NO_ENDURANCE = 72, // 0x48 - SPELL_FAILED_NO_FISH = 73, // 0x49 + SPELL_FAILED_ITEM_NOT_READY = 40, // 0x28 + SPELL_FAILED_LEVEL_REQUIREMENT = 41, // 0x29 + SPELL_FAILED_LINE_OF_SIGHT = 42, // 0x2A + SPELL_FAILED_LOWLEVEL = 43, // 0x2B + SPELL_FAILED_SKILL_NOT_HIGH_ENOUGH = 44, // 0x2C + SPELL_FAILED_MAINHAND_EMPTY = 45, // 0x2D + SPELL_FAILED_MOVING = 46, // 0x2E + SPELL_FAILED_NEED_AMMO = 47, // 0x2F + SPELL_FAILED_NEED_REQUIRES_SOMETHING = 48, // 0x30 + SPELL_FAILED_NEED_EXOTIC_AMMO = 49, // 0x31 + SPELL_FAILED_NOPATH = 50, // 0x32 + SPELL_FAILED_NOT_BEHIND = 51, // 0x33 + SPELL_FAILED_NOT_FISHABLE = 52, // 0x34 + SPELL_FAILED_NOT_HERE = 53, // 0x35 + SPELL_FAILED_NOT_INFRONT = 54, // 0x36 + SPELL_FAILED_NOT_IN_CONTROL = 55, // 0x37 + SPELL_FAILED_NOT_KNOWN = 56, // 0x38 + SPELL_FAILED_NOT_MOUNTED = 57, // 0x39 + SPELL_FAILED_NOT_ON_TAXI = 58, // 0x3A + SPELL_FAILED_NOT_ON_TRANSPORT = 59, // 0x3B + SPELL_FAILED_NOT_READY = 60, // 0x3C + SPELL_FAILED_NOT_SHAPESHIFT = 61, // 0x3D + SPELL_FAILED_NOT_STANDING = 62, // 0x3E + SPELL_FAILED_NOT_TRADEABLE = 63, // 0x3F + SPELL_FAILED_NOT_TRADING = 64, // 0x40 + SPELL_FAILED_NOT_UNSHEATHED = 65, // 0x41 + SPELL_FAILED_NOT_WHILE_GHOST = 66, // 0x42 + SPELL_FAILED_NO_AMMO = 67, // 0x43 + SPELL_FAILED_NO_CHARGES_REMAIN = 68, // 0x44 + SPELL_FAILED_NO_CHAMPION = 69, // 0x45 + SPELL_FAILED_NO_COMBO_POINTS = 70, // 0x46 + SPELL_FAILED_NO_DUELING = 71, // 0x47 + SPELL_FAILED_NO_ENDURANCE = 72, // 0x48 + SPELL_FAILED_NO_FISH = 73, // 0x49 SPELL_FAILED_NO_ITEMS_WHILE_SHAPESHIFTED = 74, // 0x4A - SPELL_FAILED_NO_MOUNTS_ALLOWED = 75, // 0x4B - SPELL_FAILED_NO_PET = 76, // 0x4C - SPELL_FAILED_NO_POWER = 77, // 0x4D - SPELL_FAILED_NOTHING_TO_DISPEL = 78, // 0x4E - SPELL_FAILED_NOTHING_TO_STEAL = 79, // 0x4F - SPELL_FAILED_ONLY_ABOVEWATER = 80, // 0x50 - SPELL_FAILED_ONLY_DAYTIME = 81, // 0x51 - SPELL_FAILED_ONLY_INDOORS = 82, // 0x52 - SPELL_FAILED_ONLY_MOUNTED = 83, // 0x53 - SPELL_FAILED_ONLY_NIGHTTIME = 84, // 0x54 - SPELL_FAILED_ONLY_OUTDOORS = 85, // 0x55 - SPELL_FAILED_ONLY_SHAPESHIFT = 86, // 0x56 - SPELL_FAILED_ONLY_STEALTHED = 87, // 0x57 - SPELL_FAILED_ONLY_UNDERWATER = 88, // 0x58 - SPELL_FAILED_OUT_OF_RANGE = 89, // 0x59 - SPELL_FAILED_PACIFIED = 90, // 0x5A - SPELL_FAILED_POSSESSED = 91, // 0x5B - SPELL_FAILED_REQUIRES_AREA = 93, // 0x5D - SPELL_FAILED_REQUIRES_SPELL_FOCUS = 94, // 0x5E - SPELL_FAILED_ROOTED = 95, // 0x5F - SPELL_FAILED_SILENCED = 96, // 0x60 - SPELL_FAILED_SPELL_IN_PROGRESS = 97, // 0x61 - SPELL_FAILED_SPELL_LEARNED = 98, // 0x62 - SPELL_FAILED_SPELL_UNAVAILABLE = 99, // 0x63 - SPELL_FAILED_STUNNED = 100, // 0x64 - SPELL_FAILED_TARGETS_DEAD = 101, // 0x65 - SPELL_FAILED_TARGET_AFFECTING_COMBAT = 102, // 0x66 - SPELL_FAILED_TARGET_AURASTATE = 103, // 0x67 - SPELL_FAILED_TARGET_DUELING = 104, // 0x68 - SPELL_FAILED_TARGET_ENEMY = 105, // 0x69 - SPELL_FAILED_TARGET_ENRAGED = 106, // 0x6A - SPELL_FAILED_TARGET_FRIENDLY = 107, // 0x6B - SPELL_FAILED_TARGET_IN_COMBAT = 108, // 0x6C - SPELL_FAILED_TARGET_IS_PLAYER = 109, // 0x6D - SPELL_FAILED_TARGET_NOT_DEAD = 110, // 0x6E - SPELL_FAILED_TARGET_NOT_IN_PARTY = 111, // 0x6F - SPELL_FAILED_TARGET_NOT_LOOTED = 112, // 0x70 - SPELL_FAILED_TARGET_NOT_PLAYER = 113, // 0x71 - SPELL_FAILED_TARGET_NO_POCKETS = 114, // 0x72 - SPELL_FAILED_TARGET_NO_WEAPONS = 115, // 0x73 - SPELL_FAILED_TARGET_UNSKINNABLE = 116, // 0x74 - SPELL_FAILED_THIRST_SATIATED = 117, // 0x75 + SPELL_FAILED_NO_MOUNTS_ALLOWED = 75, // 0x4B + SPELL_FAILED_NO_PET = 76, // 0x4C + SPELL_FAILED_NO_POWER = 77, // 0x4D + SPELL_FAILED_NOTHING_TO_DISPEL = 78, // 0x4E + SPELL_FAILED_NOTHING_TO_STEAL = 79, // 0x4F + SPELL_FAILED_ONLY_ABOVEWATER = 80, // 0x50 + SPELL_FAILED_ONLY_DAYTIME = 81, // 0x51 + SPELL_FAILED_ONLY_INDOORS = 82, // 0x52 + SPELL_FAILED_ONLY_MOUNTED = 83, // 0x53 + SPELL_FAILED_ONLY_NIGHTTIME = 84, // 0x54 + SPELL_FAILED_ONLY_OUTDOORS = 85, // 0x55 + SPELL_FAILED_ONLY_SHAPESHIFT = 86, // 0x56 + SPELL_FAILED_ONLY_STEALTHED = 87, // 0x57 + SPELL_FAILED_ONLY_UNDERWATER = 88, // 0x58 + SPELL_FAILED_OUT_OF_RANGE = 89, // 0x59 + SPELL_FAILED_PACIFIED = 90, // 0x5A + SPELL_FAILED_POSSESSED = 91, // 0x5B + SPELL_FAILED_REQUIRES_AREA = 93, // 0x5D + SPELL_FAILED_REQUIRES_SPELL_FOCUS = 94, // 0x5E + SPELL_FAILED_ROOTED = 95, // 0x5F + SPELL_FAILED_SILENCED = 96, // 0x60 + SPELL_FAILED_SPELL_IN_PROGRESS = 97, // 0x61 + SPELL_FAILED_SPELL_LEARNED = 98, // 0x62 + SPELL_FAILED_SPELL_UNAVAILABLE = 99, // 0x63 + SPELL_FAILED_STUNNED = 100, // 0x64 + SPELL_FAILED_TARGETS_DEAD = 101, // 0x65 + SPELL_FAILED_TARGET_AFFECTING_COMBAT = 102, // 0x66 + SPELL_FAILED_TARGET_AURASTATE = 103, // 0x67 + SPELL_FAILED_TARGET_DUELING = 104, // 0x68 + SPELL_FAILED_TARGET_ENEMY = 105, // 0x69 + SPELL_FAILED_TARGET_ENRAGED = 106, // 0x6A + SPELL_FAILED_TARGET_FRIENDLY = 107, // 0x6B + SPELL_FAILED_TARGET_IN_COMBAT = 108, // 0x6C + SPELL_FAILED_TARGET_IS_PLAYER = 109, // 0x6D + SPELL_FAILED_TARGET_NOT_DEAD = 110, // 0x6E + SPELL_FAILED_TARGET_NOT_IN_PARTY = 111, // 0x6F + SPELL_FAILED_TARGET_NOT_LOOTED = 112, // 0x70 + SPELL_FAILED_TARGET_NOT_PLAYER = 113, // 0x71 + SPELL_FAILED_TARGET_NO_POCKETS = 114, // 0x72 + SPELL_FAILED_TARGET_NO_WEAPONS = 115, // 0x73 + SPELL_FAILED_TARGET_UNSKINNABLE = 116, // 0x74 + SPELL_FAILED_THIRST_SATIATED = 117, // 0x75 SPELL_FAILED_TOO_CLOSE = 118 }; @@ -550,140 +687,161 @@ namespace game { }; enum SpellAttributesEx { - SPELL_ATTR_EX_DISMISS_PET_FIRST = 0x00000001, // 0 For spells without this flag client doesn't allow to summon pet if caster has a pet - SPELL_ATTR_EX_USE_ALL_MANA = 0x00000002, // 1 Use all power (Only paladin Lay of Hands and Bunyanize) - SPELL_ATTR_EX_IS_CHANNELED = 0x00000004, // 2 - SPELL_ATTR_EX_NO_REDIRECTION = 0x00000008, // 3 - SPELL_ATTR_EX_NO_SKILL_INCREASE = 0x00000010, // 4 Only assigned to stealth spells for some reason - SPELL_ATTR_EX_ALLOW_WHILE_STEALTHED = 0x00000020, // 5 Does not break stealth - SPELL_ATTR_EX_IS_SELF_CHANNELED = 0x00000040, // 6 - SPELL_ATTR_EX_NO_REFLECTION = 0x00000080, // 7 - SPELL_ATTR_EX_ONLY_PEACEFUL_TARGETS = 0x00000100, // 8 Target must not be in combat - SPELL_ATTR_EX_INITIATES_COMBAT = 0x00000200, // 9 Enables Auto-Attack - SPELL_ATTR_EX_NO_THREAT = 0x00000400, // 10 - SPELL_ATTR_EX_AURA_UNIQUE = 0x00000800, // 11 - SPELL_ATTR_EX_FAILURE_BREAKS_STEALTH = 0x00001000, // 12 - SPELL_ATTR_EX_TOGGLE_FARSIGHT = 0x00002000, // 13 - SPELL_ATTR_EX_TRACK_TARGET_IN_CHANNEL = 0x00004000, // 14 Client automatically forces player to face target when channeling - SPELL_ATTR_EX_IMMUNITY_PURGES_EFFECT = 0x00008000, // 15 Remove auras on immunity - SPELL_ATTR_EX_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, // 16 Aura that provides immunity prevents positive effects too - SPELL_ATTR_EX_NO_AUTOCAST_AI = 0x00020000, // 17 - SPELL_ATTR_EX_PREVENTS_ANIM = 0x00040000, // 18 Stun, polymorph, daze, sleep - SPELL_ATTR_EX_EXCLUDE_CASTER = 0x00080000, // 19 - SPELL_ATTR_EX_FINISHING_MOVE_DAMAGE = 0x00100000, // 20 Uses combo points - SPELL_ATTR_EX_THREAT_ONLY_ON_MISS = 0x00200000, // 21 - SPELL_ATTR_EX_FINISHING_MOVE_DURATION = 0x00400000, // 22 Uses combo points (in 4.x not required combo point target selected) - SPELL_ATTR_EX_IGNORE_CASTER_AND_TARGET_RESTRICTIONS = 0x00800000, // 23 Skips all cast checks, moved to AttributesEx3 after 1.10 (100% correlation) - SPELL_ATTR_EX_SPECIAL_SKILLUP = 0x01000000, // 24 Only fishing spells - SPELL_ATTR_EX_UNK25 = 0x02000000, // 25 Different in vanilla - SPELL_ATTR_EX_REQUIRE_ALL_TARGETS = 0x04000000, // 26 - SPELL_ATTR_EX_DISCOUNT_POWER_ON_MISS = 0x08000000, // 27 All these spells refund power on parry or deflect - SPELL_ATTR_EX_NO_AURA_ICON = 0x10000000, // 28 Client doesn't display these spells in aura bar - SPELL_ATTR_EX_NAME_IN_CHANNEL_BAR = 0x20000000, // 29 Spell name is displayed in cast bar instead of 'channeling' text - SPELL_ATTR_EX_COMBO_ON_BLOCK = 0x40000000, // 30 Overpower - SPELL_ATTR_EX_CAST_WHEN_LEARNED = 0x80000000 // 31 + SPELL_ATTR_EX_DISMISS_PET_FIRST = 0x00000001, + // 0 For spells without this flag client doesn't allow to summon pet if caster has a pet + SPELL_ATTR_EX_USE_ALL_MANA = 0x00000002, // 1 Use all power (Only paladin Lay of Hands and Bunyanize) + SPELL_ATTR_EX_IS_CHANNELED = 0x00000004, // 2 + SPELL_ATTR_EX_NO_REDIRECTION = 0x00000008, // 3 + SPELL_ATTR_EX_NO_SKILL_INCREASE = 0x00000010, // 4 Only assigned to stealth spells for some reason + SPELL_ATTR_EX_ALLOW_WHILE_STEALTHED = 0x00000020, // 5 Does not break stealth + SPELL_ATTR_EX_IS_SELF_CHANNELED = 0x00000040, // 6 + SPELL_ATTR_EX_NO_REFLECTION = 0x00000080, // 7 + SPELL_ATTR_EX_ONLY_PEACEFUL_TARGETS = 0x00000100, // 8 Target must not be in combat + SPELL_ATTR_EX_INITIATES_COMBAT = 0x00000200, // 9 Enables Auto-Attack + SPELL_ATTR_EX_NO_THREAT = 0x00000400, // 10 + SPELL_ATTR_EX_AURA_UNIQUE = 0x00000800, // 11 + SPELL_ATTR_EX_FAILURE_BREAKS_STEALTH = 0x00001000, // 12 + SPELL_ATTR_EX_TOGGLE_FARSIGHT = 0x00002000, // 13 + SPELL_ATTR_EX_TRACK_TARGET_IN_CHANNEL = 0x00004000, + // 14 Client automatically forces player to face target when channeling + SPELL_ATTR_EX_IMMUNITY_PURGES_EFFECT = 0x00008000, // 15 Remove auras on immunity + SPELL_ATTR_EX_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, + // 16 Aura that provides immunity prevents positive effects too + SPELL_ATTR_EX_NO_AUTOCAST_AI = 0x00020000, // 17 + SPELL_ATTR_EX_PREVENTS_ANIM = 0x00040000, // 18 Stun, polymorph, daze, sleep + SPELL_ATTR_EX_EXCLUDE_CASTER = 0x00080000, // 19 + SPELL_ATTR_EX_FINISHING_MOVE_DAMAGE = 0x00100000, // 20 Uses combo points + SPELL_ATTR_EX_THREAT_ONLY_ON_MISS = 0x00200000, // 21 + SPELL_ATTR_EX_FINISHING_MOVE_DURATION = 0x00400000, + // 22 Uses combo points (in 4.x not required combo point target selected) + SPELL_ATTR_EX_IGNORE_CASTER_AND_TARGET_RESTRICTIONS = 0x00800000, + // 23 Skips all cast checks, moved to AttributesEx3 after 1.10 (100% correlation) + SPELL_ATTR_EX_SPECIAL_SKILLUP = 0x01000000, // 24 Only fishing spells + SPELL_ATTR_EX_UNK25 = 0x02000000, // 25 Different in vanilla + SPELL_ATTR_EX_REQUIRE_ALL_TARGETS = 0x04000000, // 26 + SPELL_ATTR_EX_DISCOUNT_POWER_ON_MISS = 0x08000000, // 27 All these spells refund power on parry or deflect + SPELL_ATTR_EX_NO_AURA_ICON = 0x10000000, // 28 Client doesn't display these spells in aura bar + SPELL_ATTR_EX_NAME_IN_CHANNEL_BAR = 0x20000000, + // 29 Spell name is displayed in cast bar instead of 'channeling' text + SPELL_ATTR_EX_COMBO_ON_BLOCK = 0x40000000, // 30 Overpower + SPELL_ATTR_EX_CAST_WHEN_LEARNED = 0x80000000 // 31 }; enum SpellAttributesEx2 { - SPELL_ATTR_EX2_ALLOW_DEAD_TARGET = 0x00000001, // 0 Can target dead unit or corpse - SPELL_ATTR_EX2_NO_SHAPESHIFT_UI = 0x00000002, // 1 - SPELL_ATTR_EX2_IGNORE_LINE_OF_SIGHT = 0x00000004, // 2 - SPELL_ATTR_EX2_ALLOW_LOW_LEVEL_BUFF = 0x00000008, // 3 - SPELL_ATTR_EX2_USE_SHAPESHIFT_BAR = 0x00000010, // 4 Client displays icon in stance bar when learned, even if not shapeshift - SPELL_ATTR_EX2_AUTO_REPEAT = 0x00000020, // 5 - SPELL_ATTR_EX2_CANNOT_CAST_ON_TAPPED = 0x00000040, // 6 Target must be tapped by caster - SPELL_ATTR_EX2_DO_NOT_REPORT_SPELL_FAILURE = 0x00000080, // 7 - SPELL_ATTR_EX2_UNK8 = 0x00000100, // 8 Unused - SPELL_ATTR_EX2_UNK9 = 0x00000200, // 9 Unused - SPELL_ATTR_EX2_SPECIAL_TAMING_FLAG = 0x00000400, // 10 - SPELL_ATTR_EX2_NO_TARGET_PER_SECOND_COSTS = 0x00000800, // 11 - SPELL_ATTR_EX2_CHAIN_FROM_CASTER = 0x00001000, // 12 - SPELL_ATTR_EX2_ENCHANT_OWN_ITEM_ONLY = 0x00002000, // 13 - SPELL_ATTR_EX2_ALLOW_WHILE_INVISIBLE = 0x00004000, // 14 - SPELL_ATTR_EX2_ENABLE_AFTER_PARRY = 0x00008000, // 15 Deprecated in patch 1.8 and moved to CasterAuraState - SPELL_ATTR_EX2_NO_ACTIVE_PETS = 0x00010000, // 16 - SPELL_ATTR_EX2_DO_NOT_RESET_COMBAT_TIMERS = 0x00020000, // 17 Don't reset timers for melee autoattacks (swings) or ranged autoattacks (autoshoots) - SPELL_ATTR_EX2_REQ_DEAD_PET = 0x00040000, // 18 Only Revive pet has it - SPELL_ATTR_EX2_ALLOW_WHILE_NOT_SHAPESHIFTED = 0x00080000, // 19 Does not necessary need shapeshift (pre-3.x not have passive spells with this attribute) - SPELL_ATTR_EX2_INITIATE_COMBAT_POST_CAST = 0x00100000, // 20 Client will send CMSG_ATTACK_SWING after SMSG_SPELL_GO - SPELL_ATTR_EX2_FAIL_ON_ALL_TARGETS_IMMUNE = 0x00200000, // 21 For ice blocks, pala immunity buffs, priest absorb shields - SPELL_ATTR_EX2_NO_INITIAL_THREAT = 0x00400000, // 22 - SPELL_ATTR_EX2_PROC_COOLDOWN_ON_FAILURE = 0x00800000, // 23 - SPELL_ATTR_EX2_ITEM_CAST_WITH_OWNER_SKILL = 0x01000000, // 24 NYI - SPELL_ATTR_EX2_DONT_BLOCK_MANA_REGEN = 0x02000000, // 25 - SPELL_ATTR_EX2_NO_SCHOOL_IMMUNITIES = 0x04000000, // 26 - SPELL_ATTR_EX2_IGNORE_WEAPONSKILL = 0x08000000, // 27 NYI (only fishing has it) - SPELL_ATTR_EX2_NOT_AN_ACTION = 0x10000000, // 28 - SPELL_ATTR_EX2_CANT_CRIT = 0x20000000, // 29 - SPELL_ATTR_EX2_ACTIVE_THREAT = 0x40000000, // 30 Caster is put in combat for 5.5 seconds on cast at enemy unit - SPELL_ATTR_EX2_RETAIN_ITEM_CAST = 0x80000000 // 31 Food or Drink Buff (like Well Fed) + SPELL_ATTR_EX2_ALLOW_DEAD_TARGET = 0x00000001, // 0 Can target dead unit or corpse + SPELL_ATTR_EX2_NO_SHAPESHIFT_UI = 0x00000002, // 1 + SPELL_ATTR_EX2_IGNORE_LINE_OF_SIGHT = 0x00000004, // 2 + SPELL_ATTR_EX2_ALLOW_LOW_LEVEL_BUFF = 0x00000008, // 3 + SPELL_ATTR_EX2_USE_SHAPESHIFT_BAR = 0x00000010, + // 4 Client displays icon in stance bar when learned, even if not shapeshift + SPELL_ATTR_EX2_AUTO_REPEAT = 0x00000020, // 5 + SPELL_ATTR_EX2_CANNOT_CAST_ON_TAPPED = 0x00000040, // 6 Target must be tapped by caster + SPELL_ATTR_EX2_DO_NOT_REPORT_SPELL_FAILURE = 0x00000080, // 7 + SPELL_ATTR_EX2_UNK8 = 0x00000100, // 8 Unused + SPELL_ATTR_EX2_UNK9 = 0x00000200, // 9 Unused + SPELL_ATTR_EX2_SPECIAL_TAMING_FLAG = 0x00000400, // 10 + SPELL_ATTR_EX2_NO_TARGET_PER_SECOND_COSTS = 0x00000800, // 11 + SPELL_ATTR_EX2_CHAIN_FROM_CASTER = 0x00001000, // 12 + SPELL_ATTR_EX2_ENCHANT_OWN_ITEM_ONLY = 0x00002000, // 13 + SPELL_ATTR_EX2_ALLOW_WHILE_INVISIBLE = 0x00004000, // 14 + SPELL_ATTR_EX2_ENABLE_AFTER_PARRY = 0x00008000, // 15 Deprecated in patch 1.8 and moved to CasterAuraState + SPELL_ATTR_EX2_NO_ACTIVE_PETS = 0x00010000, // 16 + SPELL_ATTR_EX2_DO_NOT_RESET_COMBAT_TIMERS = 0x00020000, + // 17 Don't reset timers for melee autoattacks (swings) or ranged autoattacks (autoshoots) + SPELL_ATTR_EX2_REQ_DEAD_PET = 0x00040000, // 18 Only Revive pet has it + SPELL_ATTR_EX2_ALLOW_WHILE_NOT_SHAPESHIFTED = 0x00080000, + // 19 Does not necessary need shapeshift (pre-3.x not have passive spells with this attribute) + SPELL_ATTR_EX2_INITIATE_COMBAT_POST_CAST = 0x00100000, + // 20 Client will send CMSG_ATTACK_SWING after SMSG_SPELL_GO + SPELL_ATTR_EX2_FAIL_ON_ALL_TARGETS_IMMUNE = 0x00200000, + // 21 For ice blocks, pala immunity buffs, priest absorb shields + SPELL_ATTR_EX2_NO_INITIAL_THREAT = 0x00400000, // 22 + SPELL_ATTR_EX2_PROC_COOLDOWN_ON_FAILURE = 0x00800000, // 23 + SPELL_ATTR_EX2_ITEM_CAST_WITH_OWNER_SKILL = 0x01000000, // 24 NYI + SPELL_ATTR_EX2_DONT_BLOCK_MANA_REGEN = 0x02000000, // 25 + SPELL_ATTR_EX2_NO_SCHOOL_IMMUNITIES = 0x04000000, // 26 + SPELL_ATTR_EX2_IGNORE_WEAPONSKILL = 0x08000000, // 27 NYI (only fishing has it) + SPELL_ATTR_EX2_NOT_AN_ACTION = 0x10000000, // 28 + SPELL_ATTR_EX2_CANT_CRIT = 0x20000000, // 29 + SPELL_ATTR_EX2_ACTIVE_THREAT = 0x40000000, // 30 Caster is put in combat for 5.5 seconds on cast at enemy unit + SPELL_ATTR_EX2_RETAIN_ITEM_CAST = 0x80000000 // 31 Food or Drink Buff (like Well Fed) }; enum SpellAttributesEx3 { - SPELL_ATTR_EX3_PVP_ENABLING = 0x00000001, // 0 Spell landed counts as hostile action against enemy even if it doesn't trigger combat state, propagates PvP flags - SPELL_ATTR_EX3_NO_PROC_EQUIP_REQUIREMENT = 0x00000002, // 1 - SPELL_ATTR_EX3_NO_CASTING_BAR_TEXT = 0x00000004, // 2 - SPELL_ATTR_EX3_COMPLETELY_BLOCKED = 0x00000008, // 3 All effects prevented on block - SPELL_ATTR_EX3_NO_RES_TIMER = 0x00000010, // 4 Corpse reclaim delay does not apply to accepting resurrection (only Rebirth has it) - SPELL_ATTR_EX3_NO_DURABILITY_LOSS = 0x00000020, // 5 - SPELL_ATTR_EX3_NO_AVOIDANCE = 0x00000040, // 6 Persistent Area Aura not removed on leaving radius - SPELL_ATTR_EX3_DOT_STACKING_RULE = 0x00000080, // 7 Create a separate (de)buff stack for each caster - SPELL_ATTR_EX3_ONLY_ON_PLAYER = 0x00000100, // 8 Can target only players - SPELL_ATTR_EX3_NOT_A_PROC = 0x00000200, // 9 Aura periodic trigger is not evaluated as triggered - SPELL_ATTR_EX3_REQUIRES_MAIN_HAND_WEAPON = 0x00000400, // 10 - SPELL_ATTR_EX3_ONLY_BATTLEGROUNDS = 0x00000800, // 11 - SPELL_ATTR_EX3_ONLY_ON_GHOSTS = 0x00001000, // 12 - SPELL_ATTR_EX3_HIDE_CHANNEL_BAR = 0x00002000, // 13 Client will not display channeling bar - SPELL_ATTR_EX3_HIDE_IN_RAID_FILTER = 0x00004000, // 14 Only "Honorless Target" has this flag - SPELL_ATTR_EX3_NORMAL_RANGED_ATTACK = 0x00008000, // 15 Spells with this attribute are processed as ranged attacks in client - SPELL_ATTR_EX3_SUPPRESS_CASTER_PROCS = 0x00010000, // 16 - SPELL_ATTR_EX3_SUPPRESS_TARGET_PROCS = 0x00020000, // 17 - SPELL_ATTR_EX3_ALWAYS_HIT = 0x00040000, // 18 Spell should always hit its target - SPELL_ATTR_EX3_INSTANT_TARGET_PROCS = 0x00080000, // 19 Related to spell batching - SPELL_ATTR_EX3_ALLOW_AURA_WHILE_DEAD = 0x00100000, // 20 Death persistent spells - SPELL_ATTR_EX3_ONLY_PROC_OUTDOORS = 0x00200000, // 21 - SPELL_ATTR_EX3_CASTING_CANCELS_AUTOREPEAT = 0x00400000, // 22 NYI (only Shoot with Wand has it) - SPELL_ATTR_EX3_NO_DAMAGE_HISTORY = 0x00800000, // 23 NYI - SPELL_ATTR_EX3_REQUIRES_OFFHAND_WEAPON = 0x01000000, // 24 - SPELL_ATTR_EX3_TREAT_AS_PERIODIC = 0x02000000, // 25 Does not cause spell pushback - SPELL_ATTR_EX3_CAN_PROC_FROM_PROCS = 0x04000000, // 26 Auras with this attribute can proc off procced spells (periodic triggers etc) - SPELL_ATTR_EX3_ONLY_PROC_ON_CASTER = 0x08000000, // 27 - SPELL_ATTR_EX3_IGNORE_CASTER_AND_TARGET_RESTRICTIONS = 0x10000000, // 28 Skips all cast checks, moved from AttributesEx after 1.10 (100% correlation) - SPELL_ATTR_EX3_IGNORE_CASTER_MODIFIERS = 0x20000000, // 29 - SPELL_ATTR_EX3_DO_NOT_DISPLAY_RANGE = 0x40000000, // 30 - SPELL_ATTR_EX3_NOT_ON_AOE_IMMUNE = 0x80000000 // 31 + SPELL_ATTR_EX3_PVP_ENABLING = 0x00000001, + // 0 Spell landed counts as hostile action against enemy even if it doesn't trigger combat state, propagates PvP flags + SPELL_ATTR_EX3_NO_PROC_EQUIP_REQUIREMENT = 0x00000002, // 1 + SPELL_ATTR_EX3_NO_CASTING_BAR_TEXT = 0x00000004, // 2 + SPELL_ATTR_EX3_COMPLETELY_BLOCKED = 0x00000008, // 3 All effects prevented on block + SPELL_ATTR_EX3_NO_RES_TIMER = 0x00000010, + // 4 Corpse reclaim delay does not apply to accepting resurrection (only Rebirth has it) + SPELL_ATTR_EX3_NO_DURABILITY_LOSS = 0x00000020, // 5 + SPELL_ATTR_EX3_NO_AVOIDANCE = 0x00000040, // 6 Persistent Area Aura not removed on leaving radius + SPELL_ATTR_EX3_DOT_STACKING_RULE = 0x00000080, // 7 Create a separate (de)buff stack for each caster + SPELL_ATTR_EX3_ONLY_ON_PLAYER = 0x00000100, // 8 Can target only players + SPELL_ATTR_EX3_NOT_A_PROC = 0x00000200, // 9 Aura periodic trigger is not evaluated as triggered + SPELL_ATTR_EX3_REQUIRES_MAIN_HAND_WEAPON = 0x00000400, // 10 + SPELL_ATTR_EX3_ONLY_BATTLEGROUNDS = 0x00000800, // 11 + SPELL_ATTR_EX3_ONLY_ON_GHOSTS = 0x00001000, // 12 + SPELL_ATTR_EX3_HIDE_CHANNEL_BAR = 0x00002000, // 13 Client will not display channeling bar + SPELL_ATTR_EX3_HIDE_IN_RAID_FILTER = 0x00004000, // 14 Only "Honorless Target" has this flag + SPELL_ATTR_EX3_NORMAL_RANGED_ATTACK = 0x00008000, + // 15 Spells with this attribute are processed as ranged attacks in client + SPELL_ATTR_EX3_SUPPRESS_CASTER_PROCS = 0x00010000, // 16 + SPELL_ATTR_EX3_SUPPRESS_TARGET_PROCS = 0x00020000, // 17 + SPELL_ATTR_EX3_ALWAYS_HIT = 0x00040000, // 18 Spell should always hit its target + SPELL_ATTR_EX3_INSTANT_TARGET_PROCS = 0x00080000, // 19 Related to spell batching + SPELL_ATTR_EX3_ALLOW_AURA_WHILE_DEAD = 0x00100000, // 20 Death persistent spells + SPELL_ATTR_EX3_ONLY_PROC_OUTDOORS = 0x00200000, // 21 + SPELL_ATTR_EX3_CASTING_CANCELS_AUTOREPEAT = 0x00400000, // 22 NYI (only Shoot with Wand has it) + SPELL_ATTR_EX3_NO_DAMAGE_HISTORY = 0x00800000, // 23 NYI + SPELL_ATTR_EX3_REQUIRES_OFFHAND_WEAPON = 0x01000000, // 24 + SPELL_ATTR_EX3_TREAT_AS_PERIODIC = 0x02000000, // 25 Does not cause spell pushback + SPELL_ATTR_EX3_CAN_PROC_FROM_PROCS = 0x04000000, + // 26 Auras with this attribute can proc off procced spells (periodic triggers etc) + SPELL_ATTR_EX3_ONLY_PROC_ON_CASTER = 0x08000000, // 27 + SPELL_ATTR_EX3_IGNORE_CASTER_AND_TARGET_RESTRICTIONS = 0x10000000, + // 28 Skips all cast checks, moved from AttributesEx after 1.10 (100% correlation) + SPELL_ATTR_EX3_IGNORE_CASTER_MODIFIERS = 0x20000000, // 29 + SPELL_ATTR_EX3_DO_NOT_DISPLAY_RANGE = 0x40000000, // 30 + SPELL_ATTR_EX3_NOT_ON_AOE_IMMUNE = 0x80000000 // 31 }; enum SpellAttributesEx4 { - SPELL_ATTR_EX4_IGNORE_RESISTANCES = 0x00000001, // 0 From TC 3.3.5, but not present in 1.12 native DBCs. Add it with spell_mod to prevent a spell from being resisted. - SPELL_ATTR_EX4_CLASS_TRIGGER_ONLY_ON_TARGET = 0x00000002, // 1 - SPELL_ATTR_EX4_AURA_EXPIRES_OFFLINE = 0x00000004, // 2 Aura continues to expire while player is offline - SPELL_ATTR_EX4_NO_HELPFUL_THREAT = 0x00000008, // 3 - SPELL_ATTR_EX4_NO_HARMFUL_THREAT = 0x00000010, // 4 - SPELL_ATTR_EX4_ALLOW_CLIENT_TARGETING = 0x00000020, // 5 NYI - SPELL_ATTR_EX4_CANNOT_BE_STOLEN = 0x00000040, // 6 Unused - SPELL_ATTR_EX4_CAN_CAST_WHILE_CASTING = 0x00000080, // 7 NYI (does not seem to work client side either) - SPELL_ATTR_EX4_IGNORE_DAMAGE_TAKEN_MODIFIERS = 0x00000100, // 8 - SPELL_ATTR_EX4_COMBAT_FEEDBACK_WHEN_USABLE = 0x00000200, // 9 Initially disabled / Trigger activate from event (Execute, Riposte, Deep Freeze...) + SPELL_ATTR_EX4_IGNORE_RESISTANCES = 0x00000001, + // 0 From TC 3.3.5, but not present in 1.12 native DBCs. Add it with spell_mod to prevent a spell from being resisted. + SPELL_ATTR_EX4_CLASS_TRIGGER_ONLY_ON_TARGET = 0x00000002, // 1 + SPELL_ATTR_EX4_AURA_EXPIRES_OFFLINE = 0x00000004, // 2 Aura continues to expire while player is offline + SPELL_ATTR_EX4_NO_HELPFUL_THREAT = 0x00000008, // 3 + SPELL_ATTR_EX4_NO_HARMFUL_THREAT = 0x00000010, // 4 + SPELL_ATTR_EX4_ALLOW_CLIENT_TARGETING = 0x00000020, // 5 NYI + SPELL_ATTR_EX4_CANNOT_BE_STOLEN = 0x00000040, // 6 Unused + SPELL_ATTR_EX4_CAN_CAST_WHILE_CASTING = 0x00000080, // 7 NYI (does not seem to work client side either) + SPELL_ATTR_EX4_IGNORE_DAMAGE_TAKEN_MODIFIERS = 0x00000100, // 8 + SPELL_ATTR_EX4_COMBAT_FEEDBACK_WHEN_USABLE = 0x00000200, + // 9 Initially disabled / Trigger activate from event (Execute, Riposte, Deep Freeze...) }; -// Custom flags assigned in the db + // Custom flags assigned in the db enum SpellAttributesCustom { SPELL_CUSTOM_NONE = 0x000, - SPELL_CUSTOM_ALLOW_STACK_BETWEEN_CASTER = 0x001, // For example 'Siphon Soul' must be able to stack between the warlocks on a mob + SPELL_CUSTOM_ALLOW_STACK_BETWEEN_CASTER = 0x001, + // For example 'Siphon Soul' must be able to stack between the warlocks on a mob SPELL_CUSTOM_NEGATIVE = 0x002, SPELL_CUSTOM_POSITIVE = 0x004, SPELL_CUSTOM_CHAN_NO_DIST_LIMIT = 0x008, - SPELL_CUSTOM_FIXED_DAMAGE = 0x010, // Not affected by damage/healing done bonus + SPELL_CUSTOM_FIXED_DAMAGE = 0x010, // Not affected by damage/healing done bonus SPELL_CUSTOM_IGNORE_ARMOR = 0x020, - SPELL_CUSTOM_BEHIND_TARGET = 0x040, // For spells that require the caster to be behind the target - SPELL_CUSTOM_FACE_TARGET = 0x080, // For spells that require the target to be in front of the caster - SPELL_CUSTOM_SINGLE_TARGET_AURA = 0x100, // Aura applied by spell can only be on 1 target at a time - SPELL_CUSTOM_AURA_APPLY_BREAKS_STEALTH = 0x200, // Stealth is removed when this aura is applied - SPELL_CUSTOM_NOT_REMOVED_ON_EVADE = 0x400, // Aura persists after creature evades - SPELL_CUSTOM_SEND_CHANNEL_VISUAL = 0x800, // Will periodically send the channeling spell visual kit - SPELL_CUSTOM_SEPARATE_AURA_PER_CASTER = 0x1000, // Each caster has his own aura slot, instead of replacing others + SPELL_CUSTOM_BEHIND_TARGET = 0x040, // For spells that require the caster to be behind the target + SPELL_CUSTOM_FACE_TARGET = 0x080, // For spells that require the target to be in front of the caster + SPELL_CUSTOM_SINGLE_TARGET_AURA = 0x100, // Aura applied by spell can only be on 1 target at a time + SPELL_CUSTOM_AURA_APPLY_BREAKS_STEALTH = 0x200, // Stealth is removed when this aura is applied + SPELL_CUSTOM_NOT_REMOVED_ON_EVADE = 0x400, // Aura persists after creature evades + SPELL_CUSTOM_SEND_CHANNEL_VISUAL = 0x800, // Will periodically send the channeling spell visual kit + SPELL_CUSTOM_SEPARATE_AURA_PER_CASTER = 0x1000, + // Each caster has his own aura slot, instead of replacing others }; + enum SpellTarget { TARGET_NONE = 1, TARGET_UNIT_CASTER = 2, @@ -752,26 +910,25 @@ namespace game { }; // SpellEntry::Targets - enum SpellCastTargetFlags - { - TARGET_FLAG_SELF = 0x00000000, - TARGET_FLAG_UNUSED1 = 0x00000001, // not used in any spells (can be set dynamically) - TARGET_FLAG_UNIT = 0x00000002, // pguid - TARGET_FLAG_UNUSED2 = 0x00000004, // not used in any spells (can be set dynamically) - TARGET_FLAG_UNUSED3 = 0x00000008, // not used in any spells (can be set dynamically) - TARGET_FLAG_ITEM = 0x00000010, // pguid - TARGET_FLAG_SOURCE_LOCATION = 0x00000020, // 3 float - TARGET_FLAG_DEST_LOCATION = 0x00000040, // 3 float - TARGET_FLAG_OBJECT_UNK = 0x00000080, // used in 7 spells only - TARGET_FLAG_UNIT_UNK = 0x00000100, // looks like self target (389 spells) - TARGET_FLAG_PVP_CORPSE = 0x00000200, // pguid - TARGET_FLAG_UNIT_CORPSE = 0x00000400, // 10 spells (gathering professions) - TARGET_FLAG_OBJECT = 0x00000800, // pguid, 0 spells - TARGET_FLAG_TRADE_ITEM = 0x00001000, // pguid, 0 spells - TARGET_FLAG_STRING = 0x00002000, // string, 0 spells - TARGET_FLAG_UNK1 = 0x00004000, // 199 spells, opening object/lock - TARGET_FLAG_CORPSE = 0x00008000, // pguid, resurrection spells - TARGET_FLAG_UNK2 = 0x00010000, // pguid, not used in any spells (can be set dynamically) + enum SpellCastTargetFlags { + TARGET_FLAG_SELF = 0x00000000, + TARGET_FLAG_UNUSED1 = 0x00000001, // not used in any spells (can be set dynamically) + TARGET_FLAG_UNIT = 0x00000002, // pguid + TARGET_FLAG_UNUSED2 = 0x00000004, // not used in any spells (can be set dynamically) + TARGET_FLAG_UNUSED3 = 0x00000008, // not used in any spells (can be set dynamically) + TARGET_FLAG_ITEM = 0x00000010, // pguid + TARGET_FLAG_SOURCE_LOCATION = 0x00000020, // 3 float + TARGET_FLAG_DEST_LOCATION = 0x00000040, // 3 float + TARGET_FLAG_OBJECT_UNK = 0x00000080, // used in 7 spells only + TARGET_FLAG_UNIT_UNK = 0x00000100, // looks like self target (389 spells) + TARGET_FLAG_PVP_CORPSE = 0x00000200, // pguid + TARGET_FLAG_UNIT_CORPSE = 0x00000400, // 10 spells (gathering professions) + TARGET_FLAG_OBJECT = 0x00000800, // pguid, 0 spells + TARGET_FLAG_TRADE_ITEM = 0x00001000, // pguid, 0 spells + TARGET_FLAG_STRING = 0x00002000, // string, 0 spells + TARGET_FLAG_UNK1 = 0x00004000, // 199 spells, opening object/lock + TARGET_FLAG_CORPSE = 0x00008000, // pguid, resurrection spells + TARGET_FLAG_UNK2 = 0x00010000, // pguid, not used in any spells (can be set dynamically) }; enum Events : std::uint32_t { @@ -1188,7 +1345,7 @@ namespace game { SPELLMOD_CRIT_DAMAGE_BONUS = 15, SPELLMOD_RESIST_MISS_CHANCE = 16, SPELLMOD_JUMP_TARGETS = 17, - SPELLMOD_CHANCE_OF_SUCCESS = 18, // Only used with SPELL_AURA_ADD_FLAT_MODIFIER and affects proc spells + SPELLMOD_CHANCE_OF_SUCCESS = 18, // Only used with SPELL_AURA_ADD_FLAT_MODIFIER and affects proc spells SPELLMOD_ACTIVATION_TIME = 19, SPELLMOD_EFFECT_PAST_FIRST = 20, SPELLMOD_CASTING_TIME_OLD = 21, @@ -1218,139 +1375,146 @@ namespace game { class CDuration { public: - char m_DurationIndex; //0x0000 - __int32 m_Duration; //0x0004 - char unknown[4]; //0x0008 - __int32 m_Duration2; //0x000C + char m_DurationIndex; //0x0000 + __int32 m_Duration; //0x0004 + char unknown[4]; //0x0008 + __int32 m_Duration2; //0x000C __int32 GetDuration() { return ((m_Duration / 1000) / 60); } - };//Size=0x0010 + }; //Size=0x0010 class CSpellCastingTime { public: - __int32 m_CastingTimeIndex; //0x0000 - __int32 m_CastTime; //0x0004 - char m_0x0008[4]; //0x0008 - __int32 m_CastTime2; //0x000C - - };//Size=0x0010 + __int32 m_CastingTimeIndex; //0x0000 + __int32 m_CastTime; //0x0004 + char m_0x0008[4]; //0x0008 + __int32 m_CastTime2; //0x000C + }; //Size=0x0010 enum UnitFlags { UNIT_FLAG_NONE = 0x00000000, - UNIT_FLAG_UNK_0 = 0x00000001, // Movement checks disabled, likely paired with loss of client control packet. - UNIT_FLAG_SPAWNING = 0x00000002, // not attackable + UNIT_FLAG_UNK_0 = 0x00000001, // Movement checks disabled, likely paired with loss of client control packet. + UNIT_FLAG_SPAWNING = 0x00000002, // not attackable UNIT_FLAG_DISABLE_MOVE = 0x00000004, - UNIT_FLAG_PLAYER_CONTROLLED = 0x00000008, // players, pets, totems, guardians, companions, charms, any units associated with players - UNIT_FLAG_PET_RENAME = 0x00000010, // Old pet rename: moved to UNIT_FIELD_BYTES_2,2 in TBC+ - UNIT_FLAG_PET_ABANDON = 0x00000020, // Old pet abandon: moved to UNIT_FIELD_BYTES_2,2 in TBC+ + UNIT_FLAG_PLAYER_CONTROLLED = 0x00000008, + // players, pets, totems, guardians, companions, charms, any units associated with players + UNIT_FLAG_PET_RENAME = 0x00000010, // Old pet rename: moved to UNIT_FIELD_BYTES_2,2 in TBC+ + UNIT_FLAG_PET_ABANDON = 0x00000020, // Old pet abandon: moved to UNIT_FIELD_BYTES_2,2 in TBC+ UNIT_FLAG_UNK_6 = 0x00000040, - UNIT_FLAG_IMMUNE_TO_PLAYER = 0x00000100, // Target is immune to players - UNIT_FLAG_IMMUNE_TO_NPC = 0x00000200, // Target is immune to creatures + UNIT_FLAG_IMMUNE_TO_PLAYER = 0x00000100, // Target is immune to players + UNIT_FLAG_IMMUNE_TO_NPC = 0x00000200, // Target is immune to creatures UNIT_FLAG_PVP = 0x00001000, - UNIT_FLAG_SILENCED = 0x00002000, // silenced, 2.1.1 + UNIT_FLAG_SILENCED = 0x00002000, // silenced, 2.1.1 UNIT_FLAG_UNK_14 = 0x00004000, UNIT_FLAG_USE_SWIM_ANIMATION = 0x00008000, - UNIT_FLAG_NON_ATTACKABLE_2 = 0x00010000, // removes attackable icon, if on yourself, cannot assist self but can cast TARGET_UNIT_CASTER spells - added by SPELL_AURA_MOD_UNATTACKABLE + UNIT_FLAG_NON_ATTACKABLE_2 = 0x00010000, + // removes attackable icon, if on yourself, cannot assist self but can cast TARGET_UNIT_CASTER spells - added by SPELL_AURA_MOD_UNATTACKABLE UNIT_FLAG_PACIFIED = 0x00020000, - UNIT_FLAG_STUNNED = 0x00040000, // Unit is a subject to stun, turn and strafe movement disabled + UNIT_FLAG_STUNNED = 0x00040000, // Unit is a subject to stun, turn and strafe movement disabled UNIT_FLAG_IN_COMBAT = 0x00080000, - UNIT_FLAG_TAXI_FLIGHT = 0x00100000, // Unit is on taxi, paired with a duplicate loss of client control packet (likely a legacy serverside hack). Disables any spellcasts not allowed in taxi flight client-side. - UNIT_FLAG_CONFUSED = 0x00400000, // Unit is a subject to confused movement, movement checks disabled, paired with loss of client control packet. - UNIT_FLAG_FLEEING = 0x00800000, // Unit is a subject to fleeing movement, movement checks disabled, paired with loss of client control packet. - UNIT_FLAG_POSSESSED = 0x01000000, // Unit is under remote control by another unit, movement checks disabled, paired with loss of client control packet. New master is allowed to use melee attack and can't select this unit via mouse in the world (as if it was own character). + UNIT_FLAG_TAXI_FLIGHT = 0x00100000, + // Unit is on taxi, paired with a duplicate loss of client control packet (likely a legacy serverside hack). Disables any spellcasts not allowed in taxi flight client-side. + UNIT_FLAG_CONFUSED = 0x00400000, + // Unit is a subject to confused movement, movement checks disabled, paired with loss of client control packet. + UNIT_FLAG_FLEEING = 0x00800000, + // Unit is a subject to fleeing movement, movement checks disabled, paired with loss of client control packet. + UNIT_FLAG_POSSESSED = 0x01000000, + // Unit is under remote control by another unit, movement checks disabled, paired with loss of client control packet. New master is allowed to use melee attack and can't select this unit via mouse in the world (as if it was own character). UNIT_FLAG_NOT_SELECTABLE = 0x02000000, UNIT_FLAG_SKINNABLE = 0x04000000, - UNIT_FLAG_AURAS_VISIBLE = 0x08000000, // magic detect + UNIT_FLAG_AURAS_VISIBLE = 0x08000000, // magic detect UNIT_FLAG_SHEATHE = 0x40000000, - UNIT_FLAG_IMMUNE = 0x80000000, // Immune to damage + UNIT_FLAG_IMMUNE = 0x80000000, // Immune to damage // [-ZERO] TBC enumerations [?] - UNIT_FLAG_NOT_ATTACKABLE_1 = 0x00000080, // ?? (UNIT_FLAG_PLAYER_CONTROLLED | UNIT_FLAG_NOT_ATTACKABLE_1) is NON_PVP_ATTACKABLE - UNIT_FLAG_LOOTING = 0x00000400, // loot animation - UNIT_FLAG_PET_IN_COMBAT = 0x00000800, // in combat?, 2.0.8 - UNIT_FLAG_DISARMED = 0x00200000, // disable melee spells casting..., "Required melee weapon" added to melee spells tooltip. + UNIT_FLAG_NOT_ATTACKABLE_1 = 0x00000080, + // ?? (UNIT_FLAG_PLAYER_CONTROLLED | UNIT_FLAG_NOT_ATTACKABLE_1) is NON_PVP_ATTACKABLE + UNIT_FLAG_LOOTING = 0x00000400, // loot animation + UNIT_FLAG_PET_IN_COMBAT = 0x00000800, // in combat?, 2.0.8 + UNIT_FLAG_DISARMED = 0x00200000, + // disable melee spells casting..., "Required melee weapon" added to melee spells tooltip. UNIT_FLAG_UNK_28 = 0x10000000, - UNIT_FLAG_UNK_29 = 0x20000000, // used in Feing Death spell + UNIT_FLAG_UNK_29 = 0x20000000, // used in Feing Death spell }; typedef struct UnitFields { - uint64_t charm; // Size:2 - uint64_t summon; // Size:2 - uint64_t charmedBy; // Size:2 - uint64_t summonedBy; // Size:2 - uint64_t createdBy; // Size:2 - uint64_t target; // Size:2 - uint64_t persuaded; // Size:2 - uint64_t channelObject; // Size:2 - uint32_t health; // Size:1 - uint32_t power1; // Size:1 - uint32_t power2; // Size:1 - uint32_t power3; // Size:1 - uint32_t power4; // Size:1 - uint32_t power5; // Size:1 - uint32_t maxHealth; // Size:1 - uint32_t maxPower1; // Size:1 - uint32_t maxPower2; // Size:1 - uint32_t maxPower3; // Size:1 - uint32_t maxPower4; // Size:1 - uint32_t maxPower5; // Size:1 - uint32_t level; // Size:1 - uint32_t factionTemplate; // Size:1 - uint32_t bytes0; // Size:1 - uint32_t virtualItemDisplay[3]; // Size:3 - uint32_t virtualItemInfo[6]; // Size:6 - uint32_t flags; // Size:1 - uint32_t aura[48]; // Size:48 - uint32_t auraFlags[6]; // Size:6 - uint8_t auraLevels[48]; // Size:48 - uint8_t auraApplications[48]; // Size:48 - uint32_t auraState; // Size:1 - uint32_t baseAttackTime; // Size:1 - uint32_t offhandAttackTime; // Size:1 - uint32_t rangedAttackTime; // Size:1 - float boundingRadius; // Size:1 - float combatReach; // Size:1 - uint32_t displayId; // Size:1 - uint32_t nativeDisplayId; // Size:1 - uint32_t mountDisplayId; // Size:1 - float minDamage; // Size:1 - float maxDamage; // Size:1 - float minOffhandDamage; // Size:1 - float maxOffhandDamage; // Size:1 - uint32_t bytes1; // Size:1 - uint32_t petNumber; // Size:1 - uint32_t petNameTimestamp; // Size:1 - uint32_t petExperience; // Size:1 - uint32_t petNextLevelExp; // Size:1 - uint32_t dynamicFlags; // Size:1 - uint32_t channelSpell; // Size:1 - float modCastSpeed; // Size:1 (Float in 1.12+) - uint32_t createdBySpell; // Size:1 - uint32_t npcFlags; // Size:1 - uint32_t npcEmoteState; // Size:1 - uint32_t trainingPoints; // Size:1 - uint32_t stat0; // Size:1 - uint32_t stat1; // Size:1 - uint32_t stat2; // Size:1 - uint32_t stat3; // Size:1 - uint32_t stat4; // Size:1 - uint32_t resistances[7]; // Size:7 - uint32_t baseMana; // Size:1 - uint32_t baseHealth; // Size:1 - uint32_t bytes2; // Size:1 - uint32_t attackPower; // Size:1 - uint32_t attackPowerMods; // Size:1 - float attackPowerMultiplier; // Size:1 - uint32_t rangedAttackPower; // Size:1 - uint32_t rangedAttackPowerMods; // Size:1 - float rangedAttackPowerMultiplier; // Size:1 - float minRangedDamage; // Size:1 - float maxRangedDamage; // Size:1 - float powerCostModifier[7]; // Size:7 - float powerCostMultiplier[7]; // Size:7 + uint64_t charm; // Size:2 + uint64_t summon; // Size:2 + uint64_t charmedBy; // Size:2 + uint64_t summonedBy; // Size:2 + uint64_t createdBy; // Size:2 + uint64_t target; // Size:2 + uint64_t persuaded; // Size:2 + uint64_t channelObject; // Size:2 + uint32_t health; // Size:1 + uint32_t power1; // Size:1 + uint32_t power2; // Size:1 + uint32_t power3; // Size:1 + uint32_t power4; // Size:1 + uint32_t power5; // Size:1 + uint32_t maxHealth; // Size:1 + uint32_t maxPower1; // Size:1 + uint32_t maxPower2; // Size:1 + uint32_t maxPower3; // Size:1 + uint32_t maxPower4; // Size:1 + uint32_t maxPower5; // Size:1 + uint32_t level; // Size:1 + uint32_t factionTemplate; // Size:1 + uint32_t bytes0; // Size:1 + uint32_t virtualItemDisplay[3]; // Size:3 + uint32_t virtualItemInfo[6]; // Size:6 + uint32_t flags; // Size:1 + uint32_t aura[48]; // Size:48 + uint32_t auraFlags[6]; // Size:6 + uint8_t auraLevels[48]; // Size:48 + uint8_t auraApplications[48]; // Size:48 + uint32_t auraState; // Size:1 + uint32_t baseAttackTime; // Size:1 + uint32_t offhandAttackTime; // Size:1 + uint32_t rangedAttackTime; // Size:1 + float boundingRadius; // Size:1 + float combatReach; // Size:1 + uint32_t displayId; // Size:1 + uint32_t nativeDisplayId; // Size:1 + uint32_t mountDisplayId; // Size:1 + float minDamage; // Size:1 + float maxDamage; // Size:1 + float minOffhandDamage; // Size:1 + float maxOffhandDamage; // Size:1 + uint32_t bytes1; // Size:1 + uint32_t petNumber; // Size:1 + uint32_t petNameTimestamp; // Size:1 + uint32_t petExperience; // Size:1 + uint32_t petNextLevelExp; // Size:1 + uint32_t dynamicFlags; // Size:1 + uint32_t channelSpell; // Size:1 + float modCastSpeed; // Size:1 (Float in 1.12+) + uint32_t createdBySpell; // Size:1 + uint32_t npcFlags; // Size:1 + uint32_t npcEmoteState; // Size:1 + uint32_t trainingPoints; // Size:1 + uint32_t stat0; // Size:1 + uint32_t stat1; // Size:1 + uint32_t stat2; // Size:1 + uint32_t stat3; // Size:1 + uint32_t stat4; // Size:1 + uint32_t resistances[7]; // Size:7 + uint32_t baseMana; // Size:1 + uint32_t baseHealth; // Size:1 + uint32_t bytes2; // Size:1 + uint32_t attackPower; // Size:1 + uint32_t attackPowerMods; // Size:1 + float attackPowerMultiplier; // Size:1 + uint32_t rangedAttackPower; // Size:1 + uint32_t rangedAttackPowerMods; // Size:1 + float rangedAttackPowerMultiplier; // Size:1 + float minRangedDamage; // Size:1 + float maxRangedDamage; // Size:1 + float powerCostModifier[7]; // Size:7 + float powerCostMultiplier[7]; // Size:7 } UnitFields; uintptr_t *GetObjectPtr(std::uint64_t guid); @@ -1367,6 +1531,10 @@ namespace game { uint32_t GetItemId(CGItem_C *item); + uintptr_t *GetObjectVFTable(uintptr_t *unit); + + uintptr_t *GetPlayerInventoryPtr(uintptr_t *playerUnit); + const char *GetSpellName(uint32_t spellId); std::uint64_t ClntObjMgrGetActivePlayerGuid(); diff --git a/nampower/helper.cpp b/nampower/helper.cpp index 1ae090f..0b7e0a1 100644 --- a/nampower/helper.cpp +++ b/nampower/helper.cpp @@ -13,6 +13,20 @@ #include namespace Nampower { + // Lua function pointers + lua_errorT lua_error = reinterpret_cast(Offsets::lua_error); + lua_gettopT lua_gettop = reinterpret_cast(Offsets::lua_gettop); + lua_isstringT lua_isstring = reinterpret_cast(Offsets::lua_isstring); + lua_isnumberT lua_isnumber = reinterpret_cast(Offsets::lua_isnumber); + lua_tostringT lua_tostring = reinterpret_cast(Offsets::lua_tostring); + lua_tonumberT lua_tonumber = reinterpret_cast(Offsets::lua_tonumber); + lua_pushnumberT lua_pushnumber = reinterpret_cast(Offsets::lua_pushnumber); + lua_pushstringT lua_pushstring = reinterpret_cast(Offsets::lua_pushstring); + lua_pushbooleanT lua_pushboolean = reinterpret_cast(Offsets::lua_pushboolean); + lua_pushnilT lua_pushnil = reinterpret_cast(Offsets::lua_pushnil); + lua_newtableT lua_newtable = reinterpret_cast(Offsets::lua_newtable); + lua_settableT lua_settable = reinterpret_cast(Offsets::lua_settable); + uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) { struct CachedEntry { @@ -263,6 +277,33 @@ namespace Nampower { return guidStr; } + uint64_t GetUnitGuidFromString(const char *unitToken) { + if (!unitToken) { + return 0; + } + + // Check if it's a GUID string (starts with "0x" or "0X") + if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) { + return std::stoull(unitToken, nullptr, 16); + } else { + // Get GUID from unit token + auto const getGUIDFromName = reinterpret_cast(Offsets::GetGUIDFromName); + return getGUIDFromName(unitToken); + } + } + + uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex) { + if (lua_isnumber(luaState, paramIndex)) { + // Parameter is a GUID number + return static_cast(lua_tonumber(luaState, paramIndex)); + } else if (lua_isstring(luaState, paramIndex)) { + // Parameter is a unit token or GUID string + const char *unitToken = lua_tostring(luaState, paramIndex); + return GetUnitGuidFromString(unitToken); + } + return 0; + } + float GetNameplateDistance() { auto const distanceSquared = *reinterpret_cast(Offsets::NameplateDistance); return sqrtf(distanceSquared); diff --git a/nampower/helper.hpp b/nampower/helper.hpp index 9475d7b..a8f1cf2 100644 --- a/nampower/helper.hpp +++ b/nampower/helper.hpp @@ -5,8 +5,23 @@ #pragma once #include "game.hpp" +#include "main.hpp" namespace Nampower { + // Lua function pointers + extern lua_errorT lua_error; + extern lua_gettopT lua_gettop; + extern lua_isstringT lua_isstring; + extern lua_isnumberT lua_isnumber; + extern lua_tostringT lua_tostring; + extern lua_tonumberT lua_tonumber; + extern lua_pushnumberT lua_pushnumber; + extern lua_pushstringT lua_pushstring; + extern lua_pushbooleanT lua_pushboolean; + extern lua_pushnilT lua_pushnil; + extern lua_newtableT lua_newtable; + extern lua_settableT lua_settable; + uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType); uint32_t GetSpellIdFromSpellName(const char *spellName); @@ -35,6 +50,10 @@ namespace Nampower { char *ConvertGuidToString(uint64_t guid); + uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex); + + uint64_t GetUnitGuidFromString(const char *unitToken); + float GetNameplateDistance(); void SetNameplateDistance(float distance); diff --git a/nampower/items.cpp b/nampower/items.cpp index 7e8005b..0a32637 100644 --- a/nampower/items.cpp +++ b/nampower/items.cpp @@ -5,6 +5,7 @@ #include "items.hpp" #include "offsets.hpp" #include "logging.hpp" +#include "helper.hpp" #include #include @@ -12,6 +13,8 @@ #include #include +#include "dbc_fields.hpp" + namespace Nampower { // Global dictionary to store itemId -> ItemStats_C mappings static std::unordered_map itemStatsCache; @@ -19,6 +22,9 @@ namespace Nampower { // Track pending async loads (can have multiple at once) static std::unordered_set pendingItemIds; + // Cache for itemName -> itemId mappings (case-insensitive) + static std::unordered_map itemNameToIdCache; + // Export state tracking static bool isExporting = false; static uint32_t currentExportItemId = 0; @@ -28,6 +34,18 @@ namespace Nampower { auto const getRow = reinterpret_cast(Offsets::DBCache_ItemCacheDBGetRow); auto const itemCache = reinterpret_cast(Offsets::ItemDBCache); + // Define string keys as char arrays + static char itemIdKey[] = "itemId"; + static char permanentEnchantIdKey[] = "permanentEnchantId"; + static char tempEnchantIdKey[] = "tempEnchantId"; + static char stackCountKey[] = "stackCount"; + static char durationKey[] = "duration"; + static char flagsKey[] = "flags"; + static char durabilityKey[] = "durability"; + static char maxDurabilityKey[] = "maxDurability"; + static char tempEnchantmentTimeLeftMsKey[] = "tempEnchantmentTimeLeftMs"; + static char tempEnchantmentChargesKey[] = "tempEnchantmentCharges"; + std::string escapeJsonString(const char *str) { if (!str) return "null"; @@ -91,7 +109,7 @@ namespace Nampower { // Check if already in cache auto it = itemStatsCache.find(itemId); if (it != itemStatsCache.end()) { - return false; // Already loaded, no wait needed + return false; // Already loaded, no wait needed } uint64_t guid; @@ -99,17 +117,17 @@ namespace Nampower { if (itemStats) { // Item was available immediately, store in cache itemStatsCache[itemId] = reinterpret_cast(itemStats); - return false; // Loaded immediately, no wait needed + return false; // Loaded immediately, no wait needed } else { // Item not immediately available, need to wait for callback pendingItemIds.insert(itemId); - return true; // Caller should wait for callback + return true; // Caller should wait for callback } } bool ProcessItemExport() { if (!isExporting) { - return false; // Not currently exporting + return false; // Not currently exporting } // Try to load items until we have 20 pending or run out of items @@ -120,7 +138,7 @@ namespace Nampower { // Log progress every 1000 items if (currentExportItemId % 1000 == 0) { DEBUG_LOG("Progress: " << currentExportItemId << " / " << MAX_EXPORT_ITEM_ID - << " items requested, " << pendingItemIds.size() << " pending callbacks"); + << " items requested, " << pendingItemIds.size() << " pending callbacks"); } currentExportItemId++; @@ -136,7 +154,7 @@ namespace Nampower { if (currentExportItemId > MAX_EXPORT_ITEM_ID && pendingItemIds.empty()) { // Fall through to export completion } else { - return true; // Still exporting, more items to process or waiting for callbacks + return true; // Still exporting, more items to process or waiting for callbacks } // Export complete, write to file @@ -313,10 +331,13 @@ namespace Nampower { } isExporting = false; - return false; // Export complete + return false; // Export complete } game::ItemStats_C *GetItemStats(uint32_t itemId) { + // Load item if not cached + LoadItem(itemId); + auto it = itemStatsCache.find(itemId); if (it != itemStatsCache.end()) { return it->second; @@ -327,7 +348,9 @@ namespace Nampower { // Register a function if you want to use this // Don't want everyone scraping all items. void ExportAllItems() { - DEBUG_LOG("Starting item export - will fetch items 1 to " << MAX_EXPORT_ITEM_ID << " with up to " << ITEMS_PER_FRAME << " concurrent requests"); + DEBUG_LOG( + "Starting item export - will fetch items 1 to " << MAX_EXPORT_ITEM_ID << " with up to " << ITEMS_PER_FRAME + << " concurrent requests"); // Initialize export state isExporting = true; @@ -336,4 +359,715 @@ namespace Nampower { DEBUG_LOG("Export initialized. ProcessItemExport will be called each frame from processQueues."); } + + // Helper function to convert string to lowercase + std::string ToLowerCase(const char *str) { + std::string result; + if (!str) return result; + + while (*str) { + result += static_cast(tolower(*str)); + ++str; + } + return result; + } + + // Helper function to get itemId from itemName using cache + // Returns 0 if not found in cache + uint32_t GetItemIdFromCache(const char *itemName) { + if (!itemName) return 0; + + std::string lowerName = ToLowerCase(itemName); + auto it = itemNameToIdCache.find(lowerName); + if (it != itemNameToIdCache.end()) { + return it->second; + } + return 0; + } + + // Helper function to add itemName -> itemId mapping to cache + void CacheItemNameToId(const char *itemName, uint32_t itemId) { + if (!itemName || itemId == 0) return; + + std::string lowerName = ToLowerCase(itemName); + itemNameToIdCache[lowerName] = itemId; + } + + // Helper function to check if an item matches search criteria + bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) { + if (searchItemId != 0) { + // Searching by ID + return itemId == searchItemId; + } else if (searchItemName) { + // Searching by name + auto itemStats = GetItemStats(itemId); + if (itemStats && itemStats->m_displayName[0]) { + bool matches = _stricmp(itemStats->m_displayName[0], searchItemName) == 0; + // Cache the mapping if we found a match + if (matches) { + CacheItemNameToId(searchItemName, itemId); + } + return matches; + } + } + return false; + } + + // Helper function to push item found result to Lua stack + void PushItemFoundResult(uintptr_t *luaState, int32_t bagIndex, uint32_t slot) { + // Adjust slot for special bags (assembly expects relative positions) + uint32_t adjustedSlot = slot; + if (bagIndex == 0) { + adjustedSlot = slot - 0x17; // subtract 23 + } else if (bagIndex == -1) { + adjustedSlot = slot - 0x27; // subtract 39 + } else if (bagIndex == -2) { + adjustedSlot = slot - 0x51; // subtract 81 + } + + lua_pushnumber(luaState, static_cast(bagIndex)); + lua_pushnumber(luaState, static_cast(adjustedSlot + 1)); // lua is 1 indexed + } + + // Helper function to get bag pointer from container + uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr) { + auto vftable = game::GetObjectVFTable(containerPtr); + using GetBagPtrT = uintptr_t * (__thiscall *)(uintptr_t *); + auto getBagPtrFunc = reinterpret_cast(vftable[4]); + return getBagPtrFunc(containerPtr); + } + + // Helper function to create basic item info table for CGItem (equipped items from other units) + void CreateBasicItemInfoTable(uintptr_t *luaState, game::CGItem *cgItem) { + if (!cgItem) { + lua_pushnil(luaState); + return; + } + + lua_newtable(luaState); + + // Add itemId + lua_pushstring(luaState, itemIdKey); + lua_pushnumber(luaState, static_cast(cgItem->itemId)); + lua_settable(luaState, -3); + + // Add permanentEnchantId + lua_pushstring(luaState, permanentEnchantIdKey); + lua_pushnumber(luaState, static_cast(cgItem->permanentEnchantId)); + lua_settable(luaState, -3); + + // Add tempEnchantId + lua_pushstring(luaState, tempEnchantIdKey); + lua_pushnumber(luaState, static_cast(cgItem->tempEnchantId)); + lua_settable(luaState, -3); + } + + // Helper function to create item info table on Lua stack (leaves table on stack) + void CreateItemInfoTable(uintptr_t *luaState, game::CGItem_C *item) { + if (!item || !item->itemFields) { + lua_pushnil(luaState); + return; + } + + auto itemId = game::GetItemId(item); + auto itemFields = item->itemFields; + + // Create item info table + lua_newtable(luaState); + + // Add itemId + lua_pushstring(luaState, itemIdKey); + lua_pushnumber(luaState, static_cast(itemId)); + lua_settable(luaState, -3); + + // Add stackCount + lua_pushstring(luaState, stackCountKey); + lua_pushnumber(luaState, static_cast(itemFields->stackCount)); + lua_settable(luaState, -3); + + // Add duration + lua_pushstring(luaState, durationKey); + lua_pushnumber(luaState, static_cast(itemFields->duration)); + lua_settable(luaState, -3); + + // Add flags + lua_pushstring(luaState, flagsKey); + lua_pushnumber(luaState, static_cast(itemFields->flags)); + lua_settable(luaState, -3); + + // Add permanent enchantment + lua_pushstring(luaState, permanentEnchantIdKey); + lua_pushnumber(luaState, static_cast(itemFields->permEnchantmentSlot.id)); + lua_settable(luaState, -3); + + // Add temp enchantment + lua_pushstring(luaState, tempEnchantIdKey); + lua_pushnumber(luaState, static_cast(itemFields->tempEnchantmentSlot.id)); + lua_settable(luaState, -3); + + // Add temp enchantment time left + lua_pushstring(luaState, tempEnchantmentTimeLeftMsKey); + lua_pushnumber(luaState, static_cast(itemFields->tempEnchantmentSlot.duration)); + lua_settable(luaState, -3); + + // Add temp enchantment charges + lua_pushstring(luaState, tempEnchantmentChargesKey); + lua_pushnumber(luaState, static_cast(itemFields->tempEnchantmentSlot.charges)); + lua_settable(luaState, -3); + + // Add durability + lua_pushstring(luaState, durabilityKey); + lua_pushnumber(luaState, static_cast(itemFields->durability)); + lua_settable(luaState, -3); + + // Add max durability + lua_pushstring(luaState, maxDurabilityKey); + lua_pushnumber(luaState, static_cast(itemFields->maxDurability)); + lua_settable(luaState, -3); + } + + // Helper function to push bag item to parent table on Lua stack + void PushBagCGItemToTable(uintptr_t *luaState, int32_t bagIndex, uint32_t slot, game::CGItem_C *item) { + if (!item) { + DEBUG_LOG("missing item at slot " << slot); + return; + } + if (!item->itemFields) { + DEBUG_LOG("missing itemfields at slot " << slot << " itemId " << game::GetItemId(item)); + return; + } + + // Adjust slot for special bags (assembly expects relative positions) + uint32_t adjustedSlot = slot; + if (bagIndex == 0) { + adjustedSlot = slot - 0x17; // subtract 23 + } else if (bagIndex == -1) { + adjustedSlot = slot - 0x27; // subtract 39 + } else if (bagIndex == -2) { + adjustedSlot = slot - 0x51; // subtract 81 + } + + // Push slot number as key + lua_pushnumber(luaState, static_cast(adjustedSlot + 1)); // lua is 1 indexed + + // Create and push item info table + CreateItemInfoTable(luaState, item); + + // Set parent_table[slot] = itemInfo + lua_settable(luaState, -3); + } + + uint32_t FindPlayerItemSlot(uintptr_t *luaState) { + luaState = GetLuaStatePtr(); + + // Get player unit + auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); + auto playerUnit = game::GetObjectPtr(playerGuid); + + if (!playerUnit) { + lua_error(luaState, "Unable to get player unit"); + return 0; + } + + uint32_t searchItemId = 0; + const char *searchItemName = nullptr; + + // Check if first param is string (item name) or number (item id) + if (lua_isnumber(luaState, 1)) { + searchItemId = static_cast(lua_tonumber(luaState, 1)); + } else if (lua_isstring(luaState, 1)) { + searchItemName = lua_tostring(luaState, 1); + // Check cache first to avoid string comparisons + uint32_t cachedItemId = GetItemIdFromCache(searchItemName); + if (cachedItemId != 0) { + searchItemId = cachedItemId; + searchItemName = nullptr; // Use ID instead for faster search + } + } else { + lua_error(luaState, "Usage: FindPlayerItemSlot(itemName) or FindPlayerItemSlot(itemId)"); + return 0; + } + + auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); + auto const getContainerGuid = reinterpret_cast(Offsets::GetContainerGuid); + + auto inventory = game::GetPlayerInventoryPtr(playerUnit); + + // Loop through equipment slots 0->18 + for (uint32_t slot = 0; slot <= 18; slot++) { + auto item = getBagItem(inventory, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + lua_pushnil(luaState); // bagIndex = nil for equipped items + lua_pushnumber(luaState, static_cast(slot+1)); // lua is 1 indexed + return 2; + } + } + + // Loop through inventory pack slots 23-38 (bag 0) + for (uint32_t slot = 23; slot <= 38; slot++) { + auto item = getBagItem(inventory, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + PushItemFoundResult(luaState, 0, slot); + return 2; + } + } + + // Loop through bags 1-4 + for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) { + uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 1-4 maps to container 0-3 + if (containerGuid == 0) continue; + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) continue; + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) continue; + + auto bagSize = *bagPtr; + for (uint32_t slot = 0; slot < bagSize; slot++) { + auto item = getBagItem(bagPtr, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + PushItemFoundResult(luaState, bagIndex, slot); + return 2; + } + } + } + + uint64_t bankGuid = *reinterpret_cast(Offsets::BankGuid); + + if (bankGuid > 0) { + // Loop through bank item slots 39-62 (bag -1) + for (uint32_t slot = 39; slot <= 62; slot++) { + auto item = getBagItem(inventory, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + PushItemFoundResult(luaState, -1, slot); + return 2; + } + } + + // Loop through bank bags 5-9 + for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) { + uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8 + if (containerGuid == 0) continue; + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) continue; + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) continue; + + auto bagSize = *bagPtr; + for (uint32_t slot = 0; slot < bagSize; slot++) { + auto item = getBagItem(bagPtr, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + PushItemFoundResult(luaState, bagIndex, slot); + return 2; + } + } + } + } + + // Loop through keyring slots 81-96 (bag -2) + for (uint32_t slot = 81; slot <= 96; slot++) { + auto item = getBagItem(inventory, slot); + if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) { + PushItemFoundResult(luaState, -2, slot); + return 2; + } + } + + // Item not found + lua_pushnil(luaState); + lua_pushnil(luaState); + return 2; + } + + uint32_t GetEquippedItems(uintptr_t *luaState) { + luaState = GetLuaStatePtr(); + + uint64_t guid; + + // Check if a parameter was provided + if (lua_gettop(luaState) >= 1) { + guid = GetUnitGuidFromLuaParam(luaState, 1); + if (guid == 0) { + lua_error(luaState, "Usage: GetEquippedItems() or GetEquippedItems(unitStr) or GetEquippedItems(guid)"); + return 0; + } + } else { + // No parameter, use player + guid = game::ClntObjMgrGetActivePlayerGuid(); + } + + auto unit = game::GetObjectPtr(guid); + + if (!unit) { + lua_error(luaState, "Unable to get unit"); + return 0; + } + + auto const canInspectUnit = reinterpret_cast(Offsets::CanInspectUnit); + if (!canInspectUnit(unit)) { + lua_error(luaState, "Cannot inspect unit"); + return 0; + } + + // Create a new Lua table + lua_newtable(luaState); + + // Check if this is the active player + auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); + bool isPlayer = (guid == playerGuid); + + if (isPlayer) { + // For player, use getBagItem with inventory to get full item data + auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); + auto inventory = game::GetPlayerInventoryPtr(unit); + + for (uint32_t slot = 0; slot <= 18; slot++) { + auto item = getBagItem(inventory, slot); + if (item) { + // Equipment slots don't need bagIndex adjustment (pass -999 as a flag) + PushBagCGItemToTable(luaState, -999, slot, item); + } + } + } else { + // For other units, use CGUnit_C_GetEquippedItemAtSlot (limited data) + auto const getEquippedItem = reinterpret_cast( + Offsets::CGUnit_C_GetEquippedItemAtSlot); + + for (uint32_t slot = 0; slot <= 18; slot++) { + auto cgItem = getEquippedItem(unit, slot); + if (cgItem) { + // Push slot number as key + lua_pushnumber(luaState, static_cast(slot+1)); // lua is 1 indexed + + // Create and push basic item info table + CreateBasicItemInfoTable(luaState, cgItem); + + // Set result[slot] = item info table + lua_settable(luaState, -3); + } + } + } + + return 1; + } + + uint32_t GetEquippedItem(uintptr_t *luaState) { + luaState = GetLuaStatePtr(); + + // Check for required parameters + if (lua_gettop(luaState) < 2) { + lua_error(luaState, "Usage: GetEquippedItem(unitStr, slot) or GetEquippedItem(guid, slot)"); + return 0; + } + + // Get unit GUID from first parameter + uint64_t guid; + if (lua_gettop(luaState) >= 1) { + guid = GetUnitGuidFromLuaParam(luaState, 1); + if (guid == 0) { + lua_error(luaState, "Usage: GetEquippedItem(unitStr, slot) or GetEquippedItem(guid, slot)"); + return 0; + } + } else { + // No parameter, use player + guid = game::ClntObjMgrGetActivePlayerGuid(); + } + + // Get slot from second parameter + if (!lua_isnumber(luaState, 2)) { + lua_error(luaState, "Slot must be a number between 1 and 19"); + return 0; + } + + auto luaSlot = static_cast(lua_tonumber(luaState, 2)); + + // Check slot is in valid range + if (luaSlot < 1 || luaSlot > 19) { + lua_error(luaState, "Slot must be between 1 and 19"); + return 0; + } + + auto unit = game::GetObjectPtr(guid); + if (!unit) { + lua_error(luaState, "Unable to get unit"); + return 0; + } + + auto const canInspectUnit = reinterpret_cast(Offsets::CanInspectUnit); + if (!canInspectUnit(unit)) { + lua_error(luaState, "Cannot inspect unit"); + return 0; + } + + // Check if this is the active player + auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); + bool isPlayer = (guid == playerGuid); + + if (isPlayer) { + // For player, use getBagItem with inventory to get full item data + auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); + auto inventory = game::GetPlayerInventoryPtr(unit); + + auto item = getBagItem(inventory, luaSlot-1); // luaSlot is 1-indexed + CreateItemInfoTable(luaState, item); + } else { + // For other units, use CGUnit_C_GetEquippedItemAtSlot (limited data) + auto const getEquippedItem = reinterpret_cast( + Offsets::CGUnit_C_GetEquippedItemAtSlot); + + auto cgItem = getEquippedItem(unit, luaSlot - 1); // luaSlot is 1-indexed + CreateBasicItemInfoTable(luaState, cgItem); + } + + return 1; + } + + // Helper function to convert Lua slot (1-indexed, relative) to absolute slot + uint32_t ConvertLuaSlot(int32_t bagIndex, uint32_t luaSlot) { + // Reverse the adjustments done in PushItemFoundResult/PushBagCGItemToTable + // luaSlot is 1-indexed, so subtract 1 first to get 0-indexed relative slot + uint32_t relativeSlot = luaSlot - 1; + + // Add the offset for special bags to get absolute slot + if (bagIndex == 0) { + return relativeSlot + 0x17; // add 23 -> absolute slots 23-38 + } else if (bagIndex == -1) { + return relativeSlot + 0x27; // add 39 -> absolute slots 39-62 + } else if (bagIndex == -2) { + return relativeSlot + 0x51; // add 81 -> absolute slots 81-96 + } else { + // Regular bags (1-4, 5-9): slots are already relative (0-indexed) + return relativeSlot; + } + } + + uint32_t GetBagItem(uintptr_t *luaState) { + luaState = GetLuaStatePtr(); + + // Check for required parameters + if (lua_gettop(luaState) < 2) { + lua_error(luaState, "Usage: GetBagItem(bagIndex, slot)"); + return 0; + } + + // Get bag index from first parameter + if (!lua_isnumber(luaState, 1)) { + lua_error(luaState, "Bag index must be a number"); + return 0; + } + auto bagIndex = static_cast(lua_tonumber(luaState, 1)); + + // Get slot from second parameter + if (!lua_isnumber(luaState, 2)) { + lua_error(luaState, "Slot must be a number"); + return 0; + } + auto luaSlot = static_cast(lua_tonumber(luaState, 2)); + + auto slot = ConvertLuaSlot(bagIndex, luaSlot); + + auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); + auto const getContainerGuid = reinterpret_cast(Offsets::GetContainerGuid); + + auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); + auto player = game::GetObjectPtr(playerGuid); + auto inventory = game::GetPlayerInventoryPtr(player); + + game::CGItem_C *item = nullptr; + + if (bagIndex == 0) { + // Bag 0: inventory pack slots 23-38 (Lua slots 1-16) + if (slot < 23 || slot > 38) { + lua_error(luaState, "Slot must be between 1 and 16 for bag 1"); + return 0; + } + item = getBagItem(inventory, slot); + } else if (bagIndex >= 1 && bagIndex <= 4) { + // Bags 1-4: regular bags + uint64_t containerGuid = getContainerGuid(bagIndex-1); // bagIndex 1-4 maps to container 0-3 + if (containerGuid == 0) { + lua_pushnil(luaState); + return 1; + } + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) { + lua_pushnil(luaState); + return 1; + } + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) { + lua_pushnil(luaState); + return 1; + } + + auto bagSize = *bagPtr; + if (slot >= bagSize) { + lua_error(luaState, "Slot exceeds bag size"); + return 0; + } + + item = getBagItem(bagPtr, slot); + } else if (bagIndex == -1) { + // Bag -1: bank item slots 39-62 or buyback slots 69-80 + uint64_t bankGuid = *reinterpret_cast(Offsets::BankGuid); + if (bankGuid == 0 && slot >= 39 && slot <= 62) { + lua_error(luaState, "Bank is not open"); + return 0; + } + + if ((slot >= 39 && slot <= 62) || (slot >= 69 && slot <= 80)) { + item = getBagItem(inventory, slot); + } else { + lua_error(luaState, "For bag -1, slot must be 1-24 (bank) or 31-42 (buyback) (Lua 1-indexed)"); + return 0; + } + } else if (bagIndex >= 4 && bagIndex <= 8) { + // Bank bags 4-8 + uint64_t bankGuid = *reinterpret_cast(Offsets::BankGuid); + if (bankGuid == 0) { + lua_error(luaState, "Bank is not open"); + return 0; + } + + uint64_t containerGuid = getContainerGuid(bagIndex); + if (containerGuid == 0) { + lua_pushnil(luaState); + return 1; + } + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) { + lua_pushnil(luaState); + return 1; + } + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) { + lua_pushnil(luaState); + return 1; + } + + auto bagSize = *bagPtr; + if (slot >= bagSize) { + lua_error(luaState, "Slot exceeds bag size"); + return 0; + } + + item = getBagItem(bagPtr, slot); + } else if (bagIndex == -2) { + // Bag -2: keyring slots 81-96 (Lua slots 1-16) + if (slot < 81 || slot > 96) { + lua_error(luaState, "For bag -2 (keyring), slot must be 1-16 (Lua 1-indexed)"); + return 0; + } + item = getBagItem(inventory, slot); + } else { + lua_error(luaState, "Invalid bag index. Valid values: 0, 1-4, -1, 5-9 (bank), -2 (keyring)"); + return 0; + } + + CreateItemInfoTable(luaState, item); + return 1; + } + + uint32_t GetBagItems(uintptr_t *luaState) { + luaState = GetLuaStatePtr(); + + auto const getContainerGuid = reinterpret_cast(Offsets::GetContainerGuid); + auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); + + // Create main result table: { [bagIndex] = { [slot] = itemInfo } } + lua_newtable(luaState); + + // bag 0 is special + auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); + auto player = game::GetObjectPtr(playerGuid); + auto inventory = game::GetPlayerInventoryPtr(player); + + // Push bag index 0 as key + lua_pushnumber(luaState, static_cast(0)); + + // Create bag table for this bag + lua_newtable(luaState); + + // look through inventory pack slots 23 -> 38 + for (uint32_t slot = 23; slot <= 38; slot++) { + auto item = getBagItem(inventory, slot); + if (item) { + PushBagCGItemToTable(luaState, 0, slot, item); + } + } + + // Set result[bagIndex] = bag table + lua_settable(luaState, -3); + + // Process regular bags index 1-4 + for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) { + uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 1-4 maps to container 0-3 + if (containerGuid == 0) continue; + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) continue; + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) continue; + + // Push bag index as key + lua_pushnumber(luaState, static_cast(bagIndex)); + + // Create bag table for this bag + lua_newtable(luaState); + + auto bagSize = *bagPtr; + for (uint32_t slot = 0; slot < bagSize; slot++) { + auto item = getBagItem(bagPtr, slot); + if (item) { + PushBagCGItemToTable(luaState, bagIndex, slot, item); + } + } + + // Set result[bagIndex] = bag table + lua_settable(luaState, -3); + } + + // Check if bank is available + uint64_t bankGuid = *reinterpret_cast(Offsets::BankGuid); + if (bankGuid > 0) { + // Process bank bags 5-9 + for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) { + uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8 + if (containerGuid == 0) continue; + + auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid); + if (!containerPtr) continue; + + auto bagPtr = GetBagPtrFromContainer(containerPtr); + if (!bagPtr) continue; + + // Push bag index as key + lua_pushnumber(luaState, static_cast(bagIndex)); + + // Create bag table for this bank bag + lua_newtable(luaState); + + auto bagSize = *bagPtr; + for (uint32_t slot = 0; slot < bagSize; slot++) { + auto item = getBagItem(bagPtr, slot); + if (item) { + PushBagCGItemToTable(luaState, bagIndex, slot, item); + } + } + + // Set result[bagIndex] = bag table + lua_settable(luaState, -3); + } + } + + return 1; + } } diff --git a/nampower/items.hpp b/nampower/items.hpp index 5a8fbd1..0693f99 100644 --- a/nampower/items.hpp +++ b/nampower/items.hpp @@ -15,4 +15,10 @@ namespace Nampower { bool LoadItem(uint32_t itemId); // Returns true if item needs async load (caller should wait) bool ProcessItemExport(); // Process one item export per frame, returns true if still exporting game::ItemStats_C* GetItemStats(uint32_t itemId); + + uint32_t FindPlayerItemSlot(uintptr_t *luaState); + uint32_t GetEquippedItems(uintptr_t *luaState); + uint32_t GetEquippedItem(uintptr_t *luaState); + uint32_t GetBagItem(uintptr_t *luaState); + uint32_t GetBagItems(uintptr_t *luaState); } diff --git a/nampower/main.cpp b/nampower/main.cpp index e5e7437..dbd1870 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -61,6 +61,7 @@ namespace Nampower { bool gForceQueueCast; bool gNoQueueCast; + bool gQueuesProcessed; bool lastCastUsedServerDelay; @@ -415,7 +416,9 @@ namespace Nampower { } bool processQueues() { - if (!gCastData.channeling) { + if (!gCastData.channeling && !gQueuesProcessed) { + gQueuesProcessed = true; + // check for high priority script if (RunQueuedScript(1)) { // script ran, stop processing @@ -496,6 +499,8 @@ namespace Nampower { } } + gQueuesProcessed = true; + // Process item export if active (low priority, runs when nothing else is queued) // ProcessItemExport() @@ -551,6 +556,8 @@ namespace Nampower { // process any queued spells/scripts processQueues(); + gQueuesProcessed = false; + return iSceneEnd(ptr); } @@ -1274,6 +1281,7 @@ namespace Nampower { char getItemILevel[] = "GetItemLevel"; RegisterLuaFunction(getItemILevel, reinterpret_cast(Script_GetItemLevel)); + // 2.14 additions char getItemStats[] = "GetItemStats"; RegisterLuaFunction(getItemStats, reinterpret_cast(Script_GetItemStats)); @@ -1294,6 +1302,22 @@ namespace Nampower { char getSpellModifiers[] = "GetSpellModifiers"; RegisterLuaFunction(getSpellModifiers, reinterpret_cast(Script_GetSpellModifiers)); + + // 2.16 additions + char findPlayerItemSlot[] = "FindPlayerItemSlot"; + RegisterLuaFunction(findPlayerItemSlot, reinterpret_cast(FindPlayerItemSlot)); + + char getEquippedItems[] = "GetEquippedItems"; + RegisterLuaFunction(getEquippedItems, reinterpret_cast(GetEquippedItems)); + + char getEquippedItem[] = "GetEquippedItem"; + RegisterLuaFunction(getEquippedItem, reinterpret_cast(GetEquippedItem)); + + char getBagItems[] = "GetBagItems"; + RegisterLuaFunction(getBagItems, reinterpret_cast(GetBagItems)); + + char getBagItem[] = "GetBagItem"; + RegisterLuaFunction(getBagItem, reinterpret_cast(GetBagItem)); } std::once_flag loadFlag; diff --git a/nampower/main.hpp b/nampower/main.hpp index ad769af..e2b80b9 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -30,8 +30,8 @@ namespace Nampower { constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer constexpr uint32_t MAJOR_VERSION = 2; - constexpr uint32_t MINOR_VERSION = 15; - constexpr uint32_t PATCH_VERSION = 1; + constexpr uint32_t MINOR_VERSION = 16; + constexpr uint32_t PATCH_VERSION = 0; constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_GLOBALSINDEX = -10001; @@ -65,6 +65,7 @@ namespace Nampower { extern CastQueue gCastHistory; extern bool gScriptQueued; + extern bool gQueuesProcessed; using RangeCheckSelectedT = bool (__fastcall *)(uintptr_t *playerUnit, const game::SpellRec *, std::uint64_t targetGuid, char ignoreErrors); @@ -129,6 +130,7 @@ namespace Nampower { using lua_tonumberT = double (__fastcall *)(uintptr_t *, int); using lua_pushnumberT = void (__fastcall *)(uintptr_t *, double); using lua_pushstringT = void (__fastcall *)(uintptr_t *, char *); + using lua_pushbooleanT = void (__fastcall *)(uintptr_t *, bool); using lua_pcallT = int (__fastcall *)(uintptr_t *, int nArgs, int nResults, int errFunction); using lua_pushnilT = void (__fastcall *)(uintptr_t *); using lua_errorT = void (__cdecl *)(uintptr_t *, const char *); @@ -147,6 +149,11 @@ namespace Nampower { using CGUnit_C_ClearCastingSpellT = void (__thiscall *)(uintptr_t *unit, uint32_t param_1, int param_2, int param_3); using CGUnit_C_ClearSpellEffectT = void (__thiscall *)(uintptr_t *unit, uint32_t param_1, int param_2); + using CGUnit_C_GetEquippedItemAtSlotT = game::CGItem * (__thiscall *)(uintptr_t *unit, uint32_t slot); + using CGBag_C_GetItemAtSlotT = game::CGItem_C * (__thiscall *)(uintptr_t *bag, uint32_t slot); + using CGUnit_C_GetBagT = uintptr_t * (__thiscall *)(uintptr_t *unit); + using CanInspectUnitT = bool (__fastcall *)(uintptr_t *unit); + using GetContainerGuidT = uint64_t (__fastcall *)(int32_t bagIndex); using GetBuffByIndexT = uintptr_t *(__fastcall *)(int index); diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index 311dfff..9b70102 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -51,6 +51,7 @@ enum class Offsets : std::uint32_t { SpellNeedsTargets = 0X00CECAC0, VisualSpellId = 0X00CEAC58, CasterGuid = 0X00CEAC50, + BankGuid = 0X00BDD038, GetSpellSlotAndType = 0X004B3950, GetSpellSlotFromLua = 0X004B3EC0, OsGetAsyncTimeMs = 0X0042B790, @@ -181,6 +182,10 @@ enum class Offsets : std::uint32_t { CGUnit_C_ClearCastingSpell = 0x0060d040, CGUnit_C_ClearSpellEffect = 0x00614150, + CGUnit_C_GetEquippedItemAtSlot = 0x005f0d60, + CGBag_C_GetItemAtSlot = 0x006228a0, + GetContainerGuid = 0x004f93e0, + CanInspectUnit = 0x004944a0, CGUnit_C_OnAuraRemoved = 0x00612320, CGUnit_C_OnAuraAdded = 0x006123f0, CGUnit_C_OnAuraAddedStack = 0x0062b800, diff --git a/nampower/scripts.cpp b/nampower/scripts.cpp index 868fe7e..954c373 100644 --- a/nampower/scripts.cpp +++ b/nampower/scripts.cpp @@ -11,22 +11,6 @@ #include namespace Nampower { - auto const lua_error = reinterpret_cast(Offsets::lua_error); - - auto const lua_gettop = reinterpret_cast(Offsets::lua_gettop); - auto const lua_isstring = reinterpret_cast(Offsets::lua_isstring); - auto const lua_isnumber = reinterpret_cast(Offsets::lua_isnumber); - - auto const lua_tostring = reinterpret_cast(Offsets::lua_tostring); - auto const lua_tonumber = reinterpret_cast(Offsets::lua_tonumber); - - // Export Lua functions for dbc_fields.hpp templates - lua_pushnumberT lua_pushnumber = reinterpret_cast(Offsets::lua_pushnumber); - lua_pushstringT lua_pushstring = reinterpret_cast(Offsets::lua_pushstring); - lua_pushnilT lua_pushnil = reinterpret_cast(Offsets::lua_pushnil); - lua_newtableT lua_newtable = reinterpret_cast(Offsets::lua_newtable); - lua_settableT lua_settable = reinterpret_cast(Offsets::lua_settable); - bool gScriptQueued; int gScriptPriority = 1; char *queuedScript; @@ -112,14 +96,7 @@ namespace Nampower { target = defaultTarget; } - uint64_t targetGUID; - if (strncmp(target, "0x", 2) == 0 || strncmp(target, "0X", 2) == 0) { - // already a guid - targetGUID = std::stoull(target, nullptr, 16); - } else { - auto const getGUIDFromName = reinterpret_cast(Offsets::GetGUIDFromName); - targetGUID = getGUIDFromName(target); - } + uint64_t targetGUID = GetUnitGuidFromString(target); auto playerUnit = game::GetObjectPtr(game::ClntObjMgrGetActivePlayerGuid()); @@ -462,9 +439,6 @@ namespace Nampower { uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); - // Load item if not cached - LoadItem(itemId); - // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { @@ -550,9 +524,6 @@ namespace Nampower { uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); const char *fieldName = lua_tostring(luaState, 2); - // Load item if not cached - LoadItem(itemId); - // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { @@ -753,16 +724,7 @@ namespace Nampower { } const char *unitToken = lua_tostring(luaState, 1); - uint64_t guid; - - // Check if it's a GUID string (starts with "0x" or "0X") - if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) { - guid = std::stoull(unitToken, nullptr, 16); - } else { - // Get GUID from unit token - auto const getGUIDFromName = reinterpret_cast(Offsets::GetGUIDFromName); - guid = getGUIDFromName(unitToken); - } + uint64_t guid = GetUnitGuidFromString(unitToken); if (guid == 0) { lua_pushnil(luaState); @@ -808,16 +770,7 @@ namespace Nampower { const char *unitToken = lua_tostring(luaState, 1); const char *fieldName = lua_tostring(luaState, 2); - uint64_t guid; - - // Check if it's a GUID string (starts with "0x" or "0X") - if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) { - guid = std::stoull(unitToken, nullptr, 16); - } else { - // Get GUID from unit token - auto const getGUIDFromName = reinterpret_cast(Offsets::GetGUIDFromName); - guid = getGUIDFromName(unitToken); - } + uint64_t guid = GetUnitGuidFromString(unitToken); if (guid == 0) { lua_pushnil(luaState); diff --git a/nampower/spellcast.cpp b/nampower/spellcast.cpp index d0e0d8d..def5653 100644 --- a/nampower/spellcast.cpp +++ b/nampower/spellcast.cpp @@ -412,7 +412,7 @@ namespace Nampower { if (gUserSettings.quickcastOnDoubleCast) { auto currentTime = GetTime(); - if (IsTargetingTerrainSpell() && spellId > 0) { + if (IsTargetingTerrainSpell()) { if (gCastData.channeling || EffectiveCastEndMs() >= currentTime) { // if we are already casting block action as that will interrupt return; diff --git a/nampower/spellevents.cpp b/nampower/spellevents.cpp index cd82264..9ad333e 100644 --- a/nampower/spellevents.cpp +++ b/nampower/spellevents.cpp @@ -25,8 +25,7 @@ namespace Nampower { auto const lua_tostring = reinterpret_cast(Offsets::lua_tostring); auto const unitName = lua_tostring(luaState, 1); - auto const getGUIDFromName = reinterpret_cast(Offsets::GetGUIDFromName); - auto const guid = getGUIDFromName(unitName); + auto const guid = GetUnitGuidFromString(unitName); if (guid) { DEBUG_LOG("Spell target unit " << unitName << " guid " << guid); // update all cast params so we don't have to figure out which one to use