refactor + add cast and cooldown functions

This commit is contained in:
avitasia
2025-12-16 10:30:16 -08:00
parent b381cd5511
commit a0751ccf19
23 changed files with 2464 additions and 1938 deletions
+159
View File
@@ -0,0 +1,159 @@
### Custom Events
#### SPELL_QUEUE_EVENT
I've added a new event you can register in game to get updates when spells are added and popped from the queue.
The event is `SPELL_QUEUE_EVENT` and has 2 parameters:
1. int eventCode - see below
2. int spellId
Possible Event codes:
```
ON_SWING_QUEUED = 0
ON_SWING_QUEUE_POPPED = 1
NORMAL_QUEUED = 2
NORMAL_QUEUE_POPPED = 3
NON_GCD_QUEUED = 4
NON_GCD_QUEUE_POPPED = 5
```
Example from NampowerSettings:
```
local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1
local NORMAL_QUEUED = 2
local NORMAL_QUEUE_POPPED = 3
local NON_GCD_QUEUED = 4
local NON_GCD_QUEUE_POPPED = 5
local function spellQueueEvent(eventCode, spellId)
if eventCode == NORMAL_QUEUED or eventCode == NON_GCD_QUEUED then
local _, _, texture = SpellInfo(spellId) -- superwow function
Nampower.queued_spell.texture:SetTexture(texture)
Nampower.queued_spell:Show()
elseif eventCode == NORMAL_QUEUE_POPPED or eventCode == NON_GCD_QUEUE_POPPED then
Nampower.queued_spell:Hide()
end
end
NampowerSettings:RegisterEvent("SPELL_QUEUE_EVENT", spellQueueEvent)
```
#### SPELL_CAST_EVENT
Event you can register in game to get updates when you cast spells with some additional information. This will only fire for spells you (and certain pets) initiated.
The event is `SPELL_CAST_EVENT` and has 5 parameters:
1. int success - 1 if cast succeeded, 0 if failed
2. int spellId
3. int castType - see below
4. string targetGuid - guid string like "0xF5300000000000A5"
5. int itemId - the id of the item that triggered the spell, 0 if it wasn't triggered by an item
Possible Cast Types:
```
NORMAL=1
NON_GCD=2
ON_SWING=3
CHANNEL=4
TARGETING=5 (targeting is the term I used for spells with terrain targeting)
TARGETING_NON_GCD=6
```
targetGuid will be "0x000000000" unless an explicit target is specified which currently only happens in 2 circumstances:
- It was specified as the 2nd param of CastSpellByName (added by superwow)
- Mouseover casts that use SpellTargetUnit to specify a target
Example (uses ace RegisterEvent):
```
Cursive:RegisterEvent("SPELL_CAST_EVENT", function(success, spellId, castType, targetGuid, itemId)
print(success)
print(spellId)
print(castType)
print(targetGuid)
print(itemId)
end);
```
#### SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER
New events you can register in game to get updates whenever spell damage occurs. SPELL_DAMAGE_EVENT_SELF will only trigger for damage you deal, while SPELL_DAMAGE_EVENT_OTHER will only trigger for damage dealt by others.
Both of these events have the following parameters:
1. string targetGuid - guid string like "0xF5300000000000A5"
2. string casterGuid - guid string like "0xF5300000000000A5"
3. int spellId
4. int amount - the amount of damage dealt. If the 4th value in effectAuraStr is 89 (SPELL_AURA_PERIODIC_DAMAGE_PERCENT) I believe this is the percentage of health lost.
5. string mitigationStr - comma separated string containing "aborb,block,resist" amounts
6. int hitInfo - see below but generally 0 unless the spell was a crit in which case it will be 2
7. int spellSchool - the damage school of the spell, see below
8. string effectAuraStr - comma separated string containing the three spell effect numbers and the aura type (usually means a Dot but not all Dots will have an aura type) if applicable. So "effect1,effect2,effect3,auraType"
Spell hit info enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L109
Spell school enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L641
Spell effect enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L142
Aura type enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellAuraDefines.h#L43
Example (uses ace RegisterEvent):
```
Cursive:RegisterEvent("SPELL_DAMAGE_EVENT_SELF",
function(targetGuidStr,
casterGuidStr,
spellId,
amount,
mitigationStr,
hitInfo,
spellSchool,
effectAuraStr)
print(targetGuidStr .. " " .. casterGuidStr .. " " .. tostring(spellId) .. " " .. tostring(amount) .. " " .. tostring(spellSchool) .. " " .. mitigationStr .. " " .. hitInfo .. " " .. effectAuraStr)
end);
```
#### Buff/Debuff Events
New events fire whenever a buff or debuff is added or removed on you or any other unit that the client tracks.
Events:
```
BUFF_ADDED_SELF
BUFF_REMOVED_SELF
BUFF_ADDED_OTHER
BUFF_REMOVED_OTHER
DEBUFF_ADDED_SELF
DEBUFF_REMOVED_SELF
DEBUFF_ADDED_OTHER
DEBUFF_REMOVED_OTHER
```
All eight events pass the same parameters:
1. string guid - unit guid like "0xF5300000000000A5"
2. int slot - 1-based Lua slot index for the buff/debuff (skips empty slots to match UnitBuff/UnitDebuff ordering)
3. int spellId
4. int stackCount - current stack count for the aura (1 for a new aura; 0 when fully removed)
5. int auraLevel - caster level for the aura from UnitFields.auraLevels (uint8 per slot, 48 entries)
Buff stack gains also fire the appropriate *_ADDED_* events.
Example:
```
local function onAuraEvent(eventName, guid, slot, spellId, stacks, auraLevel)
DEFAULT_CHAT_FRAME:AddMessage(string.format("[%s] %s slot=%d spell=%d stacks=%d level=%d", eventName, guid, slot, spellId, stacks, auraLevel))
end
for _, eventName in ipairs({"BUFF_ADDED_SELF", "BUFF_REMOVED_SELF", "DEBUFF_ADDED_OTHER", "DEBUFF_REMOVED_OTHER"}) do
frame:RegisterEvent(eventName, function(...) onAuraEvent(eventName, ...) end)
end
```
#### UNIT_DIED
Fires when a unit death is recorded in the combat log.
Parameters:
1. string guid - guid of the unit that died
Example:
```
frame:RegisterEvent("UNIT_DIED", function(guid)
DEFAULT_CHAT_FRAME:AddMessage("Unit died: " .. guid)
end)
```
+33 -711
View File
@@ -1,5 +1,3 @@
<b> Checkout the list button above this to easily navigate the readme </b>
# v2.0.0 Changes
Added spell queuing, automatic retry on error, and quickcasting with lots of customization.
@@ -9,7 +7,7 @@ Some other key improvements over Namreeb's version:
- Using high_resolution_clock instead of GetTickCount for faster timing on when to start casts
- Fix broken cast animations when casting spells back to back
### Compatability with other addons
## Compatability with other addons
Queuing can cause issues with some addons that also manage spell casting. Quickheal/Healbot/Quiver do not work well with queuing. Check github issues for other potential incompatibilities.
If someone rewrites these addons to use guids from superwow that would likely fix all issues.
@@ -27,7 +25,7 @@ If all else fails can turn off queuing for a specific macro like so depending on
/run SetCVar("NP_QueueCastTimeSpells", "1")
/run SetCVar("NP_QueueInstantSpells", "1")
```
### Installation
## Installation
Grab the latest nampower.dll from https://gitea.com/avitasia/nampower/releases and place in the same directory as WoW.exe. You can also get the helper addon mentioned below and place that in Interface/Addons.
<b>You will need launch the game with a launcher like Vanillafixes https://github.com/hannesmann/vanillafixes or Unitxp https://github.com/allfoxwy/UnitXP_SP3</b> to actually have the nampower dll get loaded.
@@ -38,12 +36,12 @@ If you would prefer to compile yourself you will need to get:
CMakeLists.txt is currently looking for boost at `set(BOOST_INCLUDEDIR "C:/software/boost_1_80_0")` and hadesmem at `set(HADESMEM_ROOT "C:/software/hadesmem-v142-Debug-Win32")`. Edit as needed.
### Configuration
## Configuration
#### Configure with addon
### Configure with addon
There is a companion addon to make it easy to check/change the settings in game. You can download it here - [nampowersettings](https://gitea.com/avitasia/nampowersettings).
#### Manual Configuration
### Manual Configuration
The following CVars control the behavior of the spell queuing system:
You can access CVars in game with `/run DEFAULT_CHAT_FRAME:AddMessage(GetCVar("CVarName"))`<br>
@@ -99,9 +97,9 @@ SET NP_TargetingQueueWindowMs "1000"
- `NP_NameplateDistance` - The distance in yards to display nameplates. Defaults to whatever was set by the game or vanilla tweaks.
### Existing Lua Changes
## Existing Lua Changes
#### Improved flexibility on spellbook Lua functions
### Improved flexibility on spellbook Lua functions
These built-in Lua spell APIs now accept any of the following as their first argument: 1) spell slot (original behavior), 2) spell name, or 3) `spellId:number`.
Name and spellId lookups are cached internally and validated against current spellbook contents before reuse so you don't have to worry about performance implications or issues after respec'ing.
@@ -118,711 +116,35 @@ Examples:
/run print(GetSpellTexture("Fireball")) -- name search
```
### Custom Lua Functions
### Spell/Item/Unit information
## Custom Lua Functions
#### GetItemStats(itemId)
Returns a Lua table containing all fields for the item's `ItemStats` record (including localized `displayName` and `description`). Returns nil if the item cannot be found or loaded.
For complete documentation of all custom Lua functions added by Nampower, see **[SCRIPTS.md](SCRIPTS.md)**.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
This includes functions for:
- Spell/item/unit information (GetItemStats, GetSpellRec, GetUnitData, etc.)
- Spell casting and queuing (QueueSpellByName, QueueScript, etc.)
- Cast information (GetCastInfo, GetCurrentCastingInfo)
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown)
- Spell lookups and utilities
#### GetItemStatsField(itemId, fieldName)
Fast lookup for a single field on an item. Returns the requested field value; returns nil if the item is not found; raises a Lua error if the field name is invalid.
## Custom Events
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
For complete documentation of all custom events added by Nampower, see **[EVENTS.md](EVENTS.md)**.
**Examples:**
```lua
-- Get item name
local name = GetItemStatsField(19019, "displayName")
print(name) -- "Thunderfury, Blessed Blade of the Windseeker"
Available events:
- SPELL_QUEUE_EVENT - Fires when spells are queued or dequeued
- SPELL_CAST_EVENT - Fires when you cast a spell with additional information
- SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER - Combat damage events
- Buff/Debuff Events (BUFF_ADDED_SELF, BUFF_REMOVED_SELF, etc.)
- UNIT_DIED - Fires when a unit dies
-- Get item level
local ilvl = GetItemStatsField(22589, "itemLevel")
print("Atiesh item level: " .. ilvl) -- 90
-- Get item quality (0=Poor, 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary)
local quality = GetItemStatsField(19019, "quality")
print("Quality: " .. quality) -- 5 (Legendary)
-- Get item delay (weapon speed in milliseconds)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetSpellRecField(spellId, fieldName)
Fast lookup for a single field on a spell. Returns the requested field value; returns nil if the spell is not found; raises a Lua error if the field name is invalid.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
**Examples:**
```lua
-- Get spell name
local name = GetSpellRecField(116, "name")
print(name) -- "Frostbolt"
-- Get spell rank
local rank = GetSpellRecField(116, "rank")
print(rank) -- "Rank 1"
-- Get spell cast time in milliseconds
local castTime = GetSpellRecField(133, "castTime")
print("Fireball cast time: " .. (castTime / 1000) .. " seconds") -- 3.5 seconds
-- Get spell range (max range in yards * 10, so divide by 10)
local maxRange = GetSpellRecField(116, "rangeMax")
print("Frostbolt max range: " .. (maxRange / 10) .. " yards") -- 30 yards
-- Get spell mana cost
local manaCost = GetSpellRecField(116, "manaCost")
print("Mana cost: " .. manaCost)
-- Get spell school (0=Physical, 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane)
local school = GetSpellRecField(116, "school")
print("School: " .. school) -- 4 (Frost)
-- Get spell icon ID
local spellIconID = GetSpellRecField(116, "spellIconID")
print("Icon ID: " .. spellIconID)
```
#### GetSpellModifiers(spellId, modifierType)
Returns the current spell modifiers applied to a spell for the player. This includes buffs, talents, and other effects that modify spell behavior.
**Parameters:**
- `spellId` (number): The spell ID to check
- `modifierType` (number): The type of modifier to check (see list below)
**Returns:**
- 1st param (number): Flat modification value (e.g., +50 damage)
- 2nd param (number): Percent modification value (e.g., 10 for +10%)
- 3rd param (number): Return value from the function (whether there was any percent or flat modifier)
**Modifier Types:**
- 0 = DAMAGE
- 1 = DURATION
- 2 = THREAT
- 3 = ATTACK_POWER
- 4 = CHARGES
- 5 = RANGE
- 6 = RADIUS
- 7 = CRITICAL_CHANCE
- 8 = ALL_EFFECTS
- 9 = NOT_LOSE_CASTING_TIME
- 10 = CASTING_TIME
- 11 = COOLDOWN
- 12 = SPEED
- 14 = COST
- 15 = CRIT_DAMAGE_BONUS
- 16 = RESIST_MISS_CHANCE
- 17 = JUMP_TARGETS
- 18 = CHANCE_OF_SUCCESS
- 19 = ACTIVATION_TIME
- 20 = EFFECT_PAST_FIRST
- 21 = CASTING_TIME_OLD
- 22 = DOT
- 23 = HASTE
- 24 = SPELL_BONUS_DAMAGE
- 27 = MULTIPLE_VALUE
- 28 = RESIST_DISPEL_CHANCE
**Example:**
```lua
-- Check damage modifiers on Frostbolt (spell ID 116)
local flatMod, percentMod, ret = GetSpellModifiers(116, 0)
print("Flat damage bonus: " .. flatMod)
print("Percent damage bonus: " .. percentMod .. "%")
```
#### GetUnitData(unitToken)
Returns a Lua table containing all unit fields for the specified unit. This provides access to low-level unit data like health, mana, stats, auras, resistances, and more.
**Parameters:**
- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string (e.g., "0xF5300000000000A5")
**Returns:**
- A Lua table containing all unit fields, or nil if the unit cannot be found
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Example:**
```lua
-- Get all unit data for your current target
local data = GetUnitData("target")
if data then
print("Target health: " .. data.health .. "/" .. data.maxHealth)
print("Target level: " .. data.level)
print("Target display ID: " .. data.displayId)
end
-- Using a GUID
local data = GetUnitData("0xF5300000000000A5")
```
#### GetUnitField(unitToken, fieldName)
Fast lookup for a single field on a unit. More efficient than GetUnitData when you only need one specific field.
**Parameters:**
- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string
- `fieldName` (string): The name of the field to retrieve
**Returns:**
- The requested field value; returns nil if the unit is not found; raises a Lua error if the field name is invalid
- For array fields (like "aura", "resistances"), returns a Lua table with numeric indices
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Examples:**
```lua
-- Get target's current health
local health = GetUnitField("target", "health")
print("Target health: " .. health)
-- Get player's current mana (power1)
local mana = GetUnitField("player", "power1")
print("Player mana: " .. mana)
-- Get all auras on target (returns a table)
local auras = GetUnitField("target", "aura")
for i, auraId in ipairs(auras) do
print("Aura " .. i .. ": " .. auraId)
end
-- Get all resistances (returns a table)
local resistances = GetUnitField("player", "resistances")
-- resistances[1] = armor, [2] = holy, [3] = fire, [4] = nature, [5] = frost, [6] = shadow, [7] = arcane
```
#### QueueSpellByName(spellName)
Will force queue a spell regardless of the appropriate queue window. If no spell is currently being cast it will be cast immediately.
For example can make a macro with
```
/run QueueSpellByName("Frostbolt");QueueSpellByName("Frostbolt")
```
to cast 2 frostbolts in a row. Currently, can only queue 1 GCD spell at a time and 5 non gcd spells. This means you can't do 3 frostbolts in a row with one macro.
#### CastSpellByNameNoQueue(spellName)
Will force a spell cast to never queue even if your settings would normally queue. Can be used to fix addons that don't work with queued spells.
#### QueueScript(script, [priority])
Queues any arbitrary script using the same logic as a regular spell using NP_SpellQueueWindowMs as the window. If no spell is being cast and you are not on the gcd the script will be run immediately.
Priority is optional and defaults to 1.
Priority 1 means the script will run before any other queued spells.
Priority 2 means the script will run after any queued non gcd spells but before any queued normal spells.
Priority 3 means the script will run after any type of queued spells.
Convert slash commands from other addons like `/equip` to their function form `SlashCmdList.EQUIP` to use them inside QueueScript.
For example, you can equip a libram before casting a queued heal using
```
/run QueueScript('SlashCmdList.EQUIP("Libram of +heal")')
```
#### IsSpellInRange(spellName, [target]) or IsSpellInRange(spellId, [target])
Takes a spell name or spell id and an optional target. Target can the usual UNIT tokens like "player", "target", "mouseover", etc or a unit guid.
If using spell name it must be a spell you have in your spellbook. If using spell id it can be any spell id.
Returns 1 if the spell is in range, 0 if not in range, and -1 if the spell is not valid for this check (must be TARGET_UNIT_PET, TARGET_UNIT_TARGET_ENEMY, TARGET_UNIT_TARGET_ALLY, TARGET_UNIT_TARGET_ANY).
This is because this uses the same underlying function as `IsActionInRange` which returns 1 for spells that are not single target which can be misleading.
Examples:
```
/run local result=IsSpellInRange("Frostbolt"); if result == 1 then print("In range") else if result == 0 then print("Out of range") else print("Not single target") end
```
#### IsSpellUsable(spellName) or IsSpellUsable(spellId)
Takes a spell name or spell id.
Usable does not equal castable. This is most often used to check if a reactive spell is usable.
If using spell name it must be a spell you have in your spellbook. If using spell id it can be any spell id.
Returns:
1st param: 1 if the spell is usable, 0 if not usable.
2nd param: Always 0 if spell is not usable for a different reason other than mana. 1 if out of mana, 0 if not out of mana.
Examples:
```
/run local result=IsSpellUsable("Frostbolt"); if result == 1 then print("Frostbolt usable") else print("Frostbolt not usable") end
```
#### GetCurrentCastingInfo()
Returns:
1st param: Casting spell id or 0
2nd param: Visual spell id or 0. This won't always get cleared after a spell finishes.
3rd param: Auto repeating spell id or 0.
4th param: 1 if casting spell with a cast time, 0 if not.
5th param: 1 if channeling, 0 if not.
6th param: 1 if on swing spell is pending, 0 if not.
7th param: 1 if auto attacking, 0 if not.
For normal spells these will be the same. For some spells like auto-repeating and channeling spells only the visual spell id will be set.
Examples:
```
/run local castId,visId,autoId,casting,channeling,onswing,autoattack=GetCurrentCastingInfo();print(castId);print(visId);print(autoId);print(casting);print(channeling);print(onswing);print(autoattack);
```
#### GetSpellIdForName(spellName)
Returns:
1st param: the max rank spell id for a spell name if it exists in your spellbook. Returns 0 if the spell is not in your spellbook.
Examples:
```
/run local spellId=GetSpellIdForName("Frostbolt");print(spellId)
/run local spellId=GetSpellIdForName("Frostbolt(Rank 1)");print(spellId)
```
#### GetSpellNameAndRankForId(id)
Returns:
1st param: the spell name for a spell id
2nd param: the spell rank for a spell id as a string such as "Rank 1"
Examples:
```
/run local spellName,spellRank=GetSpellNameAndRankForId(116);print(spellName);print(spellRank)
prints "Frostbolt" and "Rank 1"
```
#### GetSpellSlotTypeIdForName(spellName)
Returns:
1st param: the 1 indexed (lua calls expect this) spell slot number for a spell name if it exists in your spellbook. Returns 0 if the spell is not in your spellbook.
2nd param: the book type of the spell, either "spell", "pet" or "unknown".
3rd param: the spell id of the spell. Returns 0 if the spell is not in your spellbook.
Examples:
```
/run local slot, bookType, spellId=GetSpellSlotTypeIdForName("Frostbolt");print(slot);print(bookType);print(spellId)
```
#### GetNampowerVersion()
Returns the current version of Nampower split into major, minor and patch numbers.
So if version was v2.8.6 it would return 2, 8, 6 as integers.
Examples:
```
/run local major, minor, patch=GetNampowerVersion();print(major);print(minor);print(patch)
```
The previous version of this `GetSpellSlotAndTypeForName` was removed as it was returning a 0 indexed slot number which was confusing to use in lua.
#### GetItemLevel(itemId)
Returns the item level of an item. Returns an error if the item id is invalid.
Examples:
```
/run local itemLevel=GetItemLevel(22589);print(itemLevel)
should print 90 for atiesh
```
#### ChannelStopCastingNextTick()
Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel.
### Custom Events
#### SPELL_QUEUE_EVENT
I've added a new event you can register in game to get updates when spells are added and popped from the queue.
The event is `SPELL_QUEUE_EVENT` and has 2 parameters:
1. int eventCode - see below
2. int spellId
Possible Event codes:
```
ON_SWING_QUEUED = 0
ON_SWING_QUEUE_POPPED = 1
NORMAL_QUEUED = 2
NORMAL_QUEUE_POPPED = 3
NON_GCD_QUEUED = 4
NON_GCD_QUEUE_POPPED = 5
```
Example from NampowerSettings:
```
local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1
local NORMAL_QUEUED = 2
local NORMAL_QUEUE_POPPED = 3
local NON_GCD_QUEUED = 4
local NON_GCD_QUEUE_POPPED = 5
local function spellQueueEvent(eventCode, spellId)
if eventCode == NORMAL_QUEUED or eventCode == NON_GCD_QUEUED then
local _, _, texture = SpellInfo(spellId) -- superwow function
Nampower.queued_spell.texture:SetTexture(texture)
Nampower.queued_spell:Show()
elseif eventCode == NORMAL_QUEUE_POPPED or eventCode == NON_GCD_QUEUE_POPPED then
Nampower.queued_spell:Hide()
end
end
NampowerSettings:RegisterEvent("SPELL_QUEUE_EVENT", spellQueueEvent)
```
#### SPELL_CAST_EVENT
Event you can register in game to get updates when you cast spells with some additional information. This will only fire for spells you (and certain pets) initiated.
The event is `SPELL_CAST_EVENT` and has 5 parameters:
1. int success - 1 if cast succeeded, 0 if failed
2. int spellId
3. int castType - see below
4. string targetGuid - guid string like "0xF5300000000000A5"
5. int itemId - the id of the item that triggered the spell, 0 if it wasn't triggered by an item
Possible Cast Types:
```
NORMAL=1
NON_GCD=2
ON_SWING=3
CHANNEL=4
TARGETING=5 (targeting is the term I used for spells with terrain targeting)
TARGETING_NON_GCD=6
```
targetGuid will be "0x000000000" unless an explicit target is specified which currently only happens in 2 circumstances:
- It was specified as the 2nd param of CastSpellByName (added by superwow)
- Mouseover casts that use SpellTargetUnit to specify a target
Example (uses ace RegisterEvent):
```
Cursive:RegisterEvent("SPELL_CAST_EVENT", function(success, spellId, castType, targetGuid, itemId)
print(success)
print(spellId)
print(castType)
print(targetGuid)
print(itemId)
end);
```
#### SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER
New events you can register in game to get updates whenever spell damage occurs. SPELL_DAMAGE_EVENT_SELF will only trigger for damage you deal, while SPELL_DAMAGE_EVENT_OTHER will only trigger for damage dealt by others.
Both of these events have the following parameters:
1. string targetGuid - guid string like "0xF5300000000000A5"
2. string casterGuid - guid string like "0xF5300000000000A5"
3. int spellId
4. int amount - the amount of damage dealt. If the 4th value in effectAuraStr is 89 (SPELL_AURA_PERIODIC_DAMAGE_PERCENT) I believe this is the percentage of health lost.
5. string mitigationStr - comma separated string containing "aborb,block,resist" amounts
6. int hitInfo - see below but generally 0 unless the spell was a crit in which case it will be 2
7. int spellSchool - the damage school of the spell, see below
8. string effectAuraStr - comma separated string containing the three spell effect numbers and the aura type (usually means a Dot but not all Dots will have an aura type) if applicable. So "effect1,effect2,effect3,auraType"
Spell hit info enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L109
Spell school enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L641
Spell effect enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellDefines.h#L142
Aura type enum: https://github.com/vmangos/core/blob/94f05231d4f1b160468744d4caa398cf8b337c48/src/game/Spells/SpellAuraDefines.h#L43
Example (uses ace RegisterEvent):
```
Cursive:RegisterEvent("SPELL_DAMAGE_EVENT_SELF",
function(targetGuidStr,
casterGuidStr,
spellId,
amount,
mitigationStr,
hitInfo,
spellSchool,
effectAuraStr)
print(targetGuidStr .. " " .. casterGuidStr .. " " .. tostring(spellId) .. " " .. tostring(amount) .. " " .. tostring(spellSchool) .. " " .. mitigationStr .. " " .. hitInfo .. " " .. effectAuraStr)
end);
```
#### Buff/Debuff Events
New events fire whenever a buff or debuff is added or removed on you or any other unit that the client tracks.
Events:
```
BUFF_ADDED_SELF
BUFF_REMOVED_SELF
BUFF_ADDED_OTHER
BUFF_REMOVED_OTHER
DEBUFF_ADDED_SELF
DEBUFF_REMOVED_SELF
DEBUFF_ADDED_OTHER
DEBUFF_REMOVED_OTHER
```
All eight events pass the same parameters:
1. string guid - unit guid like "0xF5300000000000A5"
2. int slot - 1-based Lua slot index for the buff/debuff (skips empty slots to match UnitBuff/UnitDebuff ordering)
3. int spellId
4. int stackCount - current stack count for the aura (1 for a new aura; 0 when fully removed)
5. int auraLevel - caster level for the aura from UnitFields.auraLevels (uint8 per slot, 48 entries)
Buff stack gains also fire the appropriate *_ADDED_* events.
Example:
```
local function onAuraEvent(eventName, guid, slot, spellId, stacks, auraLevel)
DEFAULT_CHAT_FRAME:AddMessage(string.format("[%s] %s slot=%d spell=%d stacks=%d level=%d", eventName, guid, slot, spellId, stacks, auraLevel))
end
for _, eventName in ipairs({"BUFF_ADDED_SELF", "BUFF_REMOVED_SELF", "DEBUFF_ADDED_OTHER", "DEBUFF_REMOVED_OTHER"}) do
frame:RegisterEvent(eventName, function(...) onAuraEvent(eventName, ...) end)
end
```
#### UNIT_DIED
Fires when a unit death is recorded in the combat log.
Parameters:
1. string guid - guid of the unit that died
Example:
```
frame:RegisterEvent("UNIT_DIED", function(guid)
DEFAULT_CHAT_FRAME:AddMessage("Unit died: " .. guid)
end)
```
### Bug Reporting
## Bug Reporting
If you encounter any bugs please report them in the issues tab. Please include the nampower_debug.txt file in the same directory as your WoW.exe to help me diagnose the issue. If you are able to reproduce the bug please include the steps to reproduce it. In a future version once bugs are ironed out I'll make logging optional.
### FAQ & Additional Info
## FAQ & Additional Info
#### How does queuing work?
### How does queuing work?
Trying to cast a spell within the appropriate window before your current spell finishes will queue your new spell.
The spell will be cast as soon as possible after the current spell finishes.
@@ -843,7 +165,7 @@ Additionally the queuing system will ignore spells with any of the following att
- SpellEffects::SPELL_EFFECT_OPEN_LOCK
- SpellEffects::SPELL_EFFECT_OPEN_LOCK_ITEM
#### Why do I need a buffer?
### Why do I need a buffer?
From my own testing it seems that a buffer is required on spells to avoid "This ability isn't ready yet"/"Another action in progress" errors.
By that I mean that if you cast a 1.5 second cast time spell every 1.5 seconds without your ping changing you will occasionally get
errors from the server and your cast will get rejected. If you have 150ms+ ping this can be very punishing.
@@ -860,12 +182,12 @@ This means that if you try to cast 2 non gcd spells in the same server tick only
To avoid this happening there is `NP_NonGcdBufferTimeMs` which is added after each non gcd spell. There might be more to
it than this as using the normal buffer of 55ms was still resulting in skipped casts for me. I found 100ms to be a safe value.
#### GCD Spells
### GCD Spells
Only one gcd spell can be queued at a time. Pressing a new gcd spell will replace any existing queued gcd spell.
As of 5/13/2025 the server tick is now subtracted from the gcd timer so a buffer is no longer required for spells with a cast time at least ~50ms less than their gcd :)
#### Non GCD Spells
### Non GCD Spells
Non gcd spells have special handling. You can queue up to 6 non gcd spells,
and they will execute in the order queued with `NP_NonGcdBufferTimeMs` delay after each of them to help avoid server rejection.
The non gcd queue always has priority over queued normal spells.
@@ -877,11 +199,11 @@ One notable exception is shaman totems that were changed to have separate catego
This can be useful if you want to change your mind about the non gcd spell you have queued. For example, if you queue a mana potion and decide you want to use LIP instead last minute.
#### On hit Spells
### On hit Spells
Only one on hit spell can be queued at a time. Pressing a new on hit spell will replace any existing queued on hit spell.
On hit spells have no effect on the gcd or non gcd queues as they are handled entirely separately and are resolved by your auto attack.
#### Channeling Spells
### Channeling Spells
Channeling spells function differently than other spells in that the channel in the client actually begins when you receive
the CHANNEL_START packet from the server. This means the client channel is happening 1/2 your latency after the server channel
and that server tick delay is already included in the cast, whereas regular spells are the other way around (the client is ahead of the server).
@@ -893,7 +215,7 @@ having a tick cut off. This is controlled by the cvar `NP_ChannelLatencyReducti
Channeling spells can be interrupted outside the channel queue window by casting any spell if `NP_InterruptChannelsOutsideQueueWindow` is set to 1. During the channel queue window
you cannot interrupt the channel unless you turn off `NP_QueueChannelingSpells`. You can always move to interrupt a channel at any time.
#### Spells on Cooldown
### Spells on Cooldown
If using `NP_QueueSpellsOnCooldown` when you attempt to cast a spell that has a remaining cooldown of less than `NP_CooldownQueueWindowMs` it will be queued instead of failing with 'Spell not Ready Yet'.
There is a separate queue of size 1 for normal spells and non gcd spells. If something is in either of these cooldown queues and you try to cast a spell that is not on cooldown it will be cast immediately and clear the appropriate cooldown queue.
@@ -901,7 +223,7 @@ For example, if Fire Blast is on cooldown and I queue it and then try to cast Fi
This currently doesn't work for item cooldowns as they work differently, will add in the future.
#### NP_OptimizeBufferUsingPacketTimings
### NP_OptimizeBufferUsingPacketTimings
This feature will attempt to optimize your buffer on individual casts using your latency and server packet timings.
After you begin to cast a spell you will get a cast result packet back from the server letting you know if the cast was successful.
The time between when you send your start cast packet and when you receive the cast result packet consists of:
+688
View File
@@ -0,0 +1,688 @@
# Nampower Custom Lua Functions
This document describes all custom Lua functions and events added by Nampower.
For installation, configuration, and general usage information, see the main [README.md](README.md).
## Table of Contents
- [Custom Lua Functions](#custom-lua-functions)
- [Spell/Item/Unit Information](#spellitemunit-information)
- [Spell Casting and Queuing](#spell-casting-and-queuing)
- [Cast Information](#cast-information)
- [Cooldown Information](#cooldown-information)
---
### Custom Lua Functions
### Spell/Item/Unit information
#### GetItemStats(itemId)
Returns a Lua table containing all fields for the item's `ItemStats` record (including localized `displayName` and `description`). Returns nil if the item cannot be found or loaded.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetItemStatsField(itemId, fieldName)
Fast lookup for a single field on an item. Returns the requested field value; returns nil if the item is not found; raises a Lua error if the field name is invalid.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
**Examples:**
```lua
-- Get item name
local name = GetItemStatsField(19019, "displayName")
print(name) -- "Thunderfury, Blessed Blade of the Windseeker"
-- Get item level
local ilvl = GetItemStatsField(22589, "itemLevel")
print("Atiesh item level: " .. ilvl) -- 90
-- Get item quality (0=Poor, 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary)
local quality = GetItemStatsField(19019, "quality")
print("Quality: " .. quality) -- 5 (Legendary)
-- Get item delay (weapon speed in milliseconds)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetSpellRecField(spellId, fieldName)
Fast lookup for a single field on a spell. Returns the requested field value; returns nil if the spell is not found; raises a Lua error if the field name is invalid.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
**Examples:**
```lua
-- Get spell name
local name = GetSpellRecField(116, "name")
print(name) -- "Frostbolt"
-- Get spell rank
local rank = GetSpellRecField(116, "rank")
print(rank) -- "Rank 1"
-- Get spell cast time in milliseconds
local castTime = GetSpellRecField(133, "castTime")
print("Fireball cast time: " .. (castTime / 1000) .. " seconds") -- 3.5 seconds
-- Get spell range (max range in yards * 10, so divide by 10)
local maxRange = GetSpellRecField(116, "rangeMax")
print("Frostbolt max range: " .. (maxRange / 10) .. " yards") -- 30 yards
-- Get spell mana cost
local manaCost = GetSpellRecField(116, "manaCost")
print("Mana cost: " .. manaCost)
-- Get spell school (0=Physical, 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane)
local school = GetSpellRecField(116, "school")
print("School: " .. school) -- 4 (Frost)
-- Get spell icon ID
local spellIconID = GetSpellRecField(116, "spellIconID")
print("Icon ID: " .. spellIconID)
```
#### GetSpellModifiers(spellId, modifierType)
Returns the current spell modifiers applied to a spell for the player. This includes buffs, talents, and other effects that modify spell behavior.
**Parameters:**
- `spellId` (number): The spell ID to check
- `modifierType` (number): The type of modifier to check (see list below)
**Returns:**
- 1st param (number): Flat modification value (e.g., +50 damage)
- 2nd param (number): Percent modification value (e.g., 10 for +10%)
- 3rd param (number): Return value from the function (whether there was any percent or flat modifier)
**Modifier Types:**
- 0 = DAMAGE
- 1 = DURATION
- 2 = THREAT
- 3 = ATTACK_POWER
- 4 = CHARGES
- 5 = RANGE
- 6 = RADIUS
- 7 = CRITICAL_CHANCE
- 8 = ALL_EFFECTS
- 9 = NOT_LOSE_CASTING_TIME
- 10 = CASTING_TIME
- 11 = COOLDOWN
- 12 = SPEED
- 14 = COST
- 15 = CRIT_DAMAGE_BONUS
- 16 = RESIST_MISS_CHANCE
- 17 = JUMP_TARGETS
- 18 = CHANCE_OF_SUCCESS
- 19 = ACTIVATION_TIME
- 20 = EFFECT_PAST_FIRST
- 21 = CASTING_TIME_OLD
- 22 = DOT
- 23 = HASTE
- 24 = SPELL_BONUS_DAMAGE
- 27 = MULTIPLE_VALUE
- 28 = RESIST_DISPEL_CHANCE
**Example:**
```lua
-- Check damage modifiers on Frostbolt (spell ID 116)
local flatMod, percentMod, ret = GetSpellModifiers(116, 0)
print("Flat damage bonus: " .. flatMod)
print("Percent damage bonus: " .. percentMod .. "%")
```
#### GetUnitData(unitToken)
Returns a Lua table containing all unit fields for the specified unit. This provides access to low-level unit data like health, mana, stats, auras, resistances, and more.
**Parameters:**
- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string (e.g., "0xF5300000000000A5")
**Returns:**
- A Lua table containing all unit fields, or nil if the unit cannot be found
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Example:**
```lua
-- Get all unit data for your current target
local data = GetUnitData("target")
if data then
print("Target health: " .. data.health .. "/" .. data.maxHealth)
print("Target level: " .. data.level)
print("Target display ID: " .. data.displayId)
end
-- Using a GUID
local data = GetUnitData("0xF5300000000000A5")
```
#### GetUnitField(unitToken, fieldName)
Fast lookup for a single field on a unit. More efficient than GetUnitData when you only need one specific field.
**Parameters:**
- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string
- `fieldName` (string): The name of the field to retrieve
**Returns:**
- The requested field value; returns nil if the unit is not found; raises a Lua error if the field name is invalid
- For array fields (like "aura", "resistances"), returns a Lua table with numeric indices
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Examples:**
```lua
-- Get target's current health
local health = GetUnitField("target", "health")
print("Target health: " .. health)
-- Get player's current mana (power1)
local mana = GetUnitField("player", "power1")
print("Player mana: " .. mana)
-- Get all auras on target (returns a table)
local auras = GetUnitField("target", "aura")
for i, auraId in ipairs(auras) do
print("Aura " .. i .. ": " .. auraId)
end
-- Get all resistances (returns a table)
local resistances = GetUnitField("player", "resistances")
-- resistances[1] = armor, [2] = holy, [3] = fire, [4] = nature, [5] = frost, [6] = shadow, [7] = arcane
```
#### QueueSpellByName(spellName)
Will force queue a spell regardless of the appropriate queue window. If no spell is currently being cast it will be cast immediately.
For example can make a macro with
```
/run QueueSpellByName("Frostbolt");QueueSpellByName("Frostbolt")
```
to cast 2 frostbolts in a row. Currently, can only queue 1 GCD spell at a time and 5 non gcd spells. This means you can't do 3 frostbolts in a row with one macro.
#### CastSpellByNameNoQueue(spellName)
Will force a spell cast to never queue even if your settings would normally queue. Can be used to fix addons that don't work with queued spells.
#### QueueScript(script, [priority])
Queues any arbitrary script using the same logic as a regular spell using NP_SpellQueueWindowMs as the window. If no spell is being cast and you are not on the gcd the script will be run immediately.
Priority is optional and defaults to 1.
Priority 1 means the script will run before any other queued spells.
Priority 2 means the script will run after any queued non gcd spells but before any queued normal spells.
Priority 3 means the script will run after any type of queued spells.
Convert slash commands from other addons like `/equip` to their function form `SlashCmdList.EQUIP` to use them inside QueueScript.
For example, you can equip a libram before casting a queued heal using
```
/run QueueScript('SlashCmdList.EQUIP("Libram of +heal")')
```
#### IsSpellInRange(spellName, [target]) or IsSpellInRange(spellId, [target])
Takes a spell name or spell id and an optional target. Target can the usual UNIT tokens like "player", "target", "mouseover", etc or a unit guid.
If using spell name it must be a spell you have in your spellbook. If using spell id it can be any spell id.
Returns 1 if the spell is in range, 0 if not in range, and -1 if the spell is not valid for this check (must be TARGET_UNIT_PET, TARGET_UNIT_TARGET_ENEMY, TARGET_UNIT_TARGET_ALLY, TARGET_UNIT_TARGET_ANY).
This is because this uses the same underlying function as `IsActionInRange` which returns 1 for spells that are not single target which can be misleading.
Examples:
```
/run local result=IsSpellInRange("Frostbolt"); if result == 1 then print("In range") else if result == 0 then print("Out of range") else print("Not single target") end
```
#### IsSpellUsable(spellName) or IsSpellUsable(spellId)
Takes a spell name or spell id.
Usable does not equal castable. This is most often used to check if a reactive spell is usable.
If using spell name it must be a spell you have in your spellbook. If using spell id it can be any spell id.
Returns:
1st param: 1 if the spell is usable, 0 if not usable.
2nd param: Always 0 if spell is not usable for a different reason other than mana. 1 if out of mana, 0 if not out of mana.
Examples:
```
/run local result=IsSpellUsable("Frostbolt"); if result == 1 then print("Frostbolt usable") else print("Frostbolt not usable") end
```
#### GetCurrentCastingInfo()
Returns:
1st param: Casting spell id or 0
2nd param: Visual spell id or 0. This won't always get cleared after a spell finishes.
3rd param: Auto repeating spell id or 0.
4th param: 1 if casting spell with a cast time, 0 if not.
5th param: 1 if channeling, 0 if not.
6th param: 1 if on swing spell is pending, 0 if not.
7th param: 1 if auto attacking, 0 if not.
For normal spells these will be the same. For some spells like auto-repeating and channeling spells only the visual spell id will be set.
Examples:
```
/run local castId,visId,autoId,casting,channeling,onswing,autoattack=GetCurrentCastingInfo();print(castId);print(visId);print(autoId);print(casting);print(channeling);print(onswing);print(autoattack);
```
#### GetCastInfo()
Returns detailed information about the currently active cast or channel. Returns nil if there is no active cast or channel.
GetCurrentCastingInfo was made very early on and doesn't provide enough information for many use cases, but still has some uses and is available for backwards compatibility.
**Returns:**
A Lua table with the following fields, or nil if no cast is active:
- `castId` (number): Unique identifier for this cast
- `spellId` (number): The spell ID being cast
- `guid` (number): Target GUID (0 if no explicit target)
- `castType` (number): Type of cast - 0=NORMAL, 3=CHANNEL, 4=TARGETING
- `castStartS` (number): When the cast started in WoW time (seconds with decimals, e.g., 1234567.890)
- `castEndS` (number): When the cast will end in WoW time (seconds with decimals)
- `castRemainingMs` (number): Milliseconds remaining until cast ends
- `castDurationMs` (number): Total cast duration in milliseconds
- `gcdEndS` (number): When the GCD will end in WoW time (seconds with decimals)
- `gcdRemainingMs` (number): Milliseconds remaining until GCD expires
**Notes:**
- Time fields ending in `S` (castStartS, castEndS, gcdEndS) are absolute timestamps in **seconds** with decimal precision to match GetTime() in Lua
- Duration and remaining fields ending in `Ms` (castRemainingMs, castDurationMs, gcdRemainingMs) are in **milliseconds** for precision
- Returns nil if there is no active cast (castSpellId is 0) and no active channel (channelSpellId is 0)
**Examples:**
```lua
-- Check current cast information
local info = GetCastInfo()
if info then
print("Casting spell: " .. info.spellId)
print("Cast ends at: " .. info.castEndS)
print("Time remaining: " .. info.castRemainingMs .. "ms")
print("GCD ends at: " .. info.gcdEndS)
print("GCD remaining: " .. info.gcdRemainingMs .. "ms")
else
print("No active cast")
end
-- Check if you can cast another spell (GCD check)
local info = GetCastInfo()
if not info or info.gcdRemainingMs == 0 then
print("Ready to cast!")
else
print("On GCD for " .. info.gcdRemainingMs .. "ms more")
end
-- Monitor cast progress
local info = GetCastInfo()
if info and info.castDurationMs > 0 then
local progress = ((info.castDurationMs - info.castRemainingMs) / info.castDurationMs) * 100
print("Cast progress: " .. string.format("%.1f%%", progress))
end
```
#### GetSpellIdCooldown(spellId)
Returns detailed cooldown information for a spell from the spell history. This provides precise timing data for individual spell cooldowns, category cooldowns, and GCD.
**Parameters:**
- `spellId` (number): The spell ID to check
**Returns:**
A Lua table with the following fields:
- `isOnCooldown` (number): 1 if any cooldown is active, 0 otherwise
- `cooldownRemainingMs` (number): Maximum remaining time across all cooldown types in milliseconds
**Individual Spell Cooldown:**
- `individualStartS` (number): When the individual spell cooldown started (seconds, WoW time)
- `individualDurationMs` (number): Total duration of the individual spell cooldown in milliseconds
- `individualRemainingMs` (number): Milliseconds remaining on the individual spell cooldown
- `isOnIndividualCooldown` (number): 1 if the spell-specific cooldown is active, 0 otherwise
**Category Cooldown:**
- `categoryId` (number): The cooldown category ID (0 if no category cooldown)
- `categoryStartS` (number): When the category cooldown started (seconds, WoW time)
- `categoryDurationMs` (number): Total duration of the category cooldown in milliseconds
- `categoryRemainingMs` (number): Milliseconds remaining on the category cooldown
- `isOnCategoryCooldown` (number): 1 if the category cooldown is active, 0 otherwise
**GCD (Global Cooldown):**
- `gcdCategoryId` (number): The GCD category ID (typically 133 for most spells)
- `gcdCategoryStartS` (number): When the GCD started (seconds, WoW time)
- `gcdCategoryDurationMs` (number): Total GCD duration in milliseconds (typically 1500ms)
- `gcdCategoryRemainingMs` (number): Milliseconds remaining on the GCD
- `isOnGcdCategoryCooldown` (number): 1 if the GCD is active, 0 otherwise
**Notes:**
- Time fields ending in `S` are absolute timestamps in **seconds** to match GetTime() in Lua
- Fields ending in `Ms` are in **milliseconds** for precision
- The spell must have been cast at least once for accurate data to be available
- `cooldownRemainingMs` is the maximum of all three cooldown types
**Example:**
```lua
-- Check if Frostbolt is ready to cast
local cd = GetSpellIdCooldown(116) -- Frostbolt
if cd.isOnCooldown == 0 then
print("Frostbolt is ready!")
else
print("Frostbolt on cooldown for " .. cd.cooldownRemainingMs .. "ms")
if cd.isOnGcdCategoryCooldown == 1 then
print(" GCD: " .. cd.gcdCategoryRemainingMs .. "ms remaining")
end
if cd.isOnIndividualCooldown == 1 then
print(" Spell CD: " .. cd.individualRemainingMs .. "ms remaining")
end
if cd.isOnCategoryCooldown == 1 then
print(" Category CD: " .. cd.categoryRemainingMs .. "ms remaining")
end
end
```
#### GetItemIdCooldown(itemId)
Returns detailed cooldown information for an item from the spell history. Works similarly to GetSpellIdCooldown but for items.
**Parameters:**
- `itemId` (number): The item ID to check
**Returns:**
A Lua table with the same structure as GetSpellIdCooldown (see above).
**Notes:**
- Returns the longest cooldown among all spells associated with the item
- If the item has multiple on-use effects, returns information for the one with the longest remaining cooldown
- Item cooldowns are tracked through their associated spell entries in the spell history
**Example:**
```lua
-- Check if a trinket is ready
local cd = GetItemIdCooldown(12345) -- Replace with your trinket ID
if cd.isOnCooldown == 0 then
print("Trinket is ready to use!")
else
print("Trinket on cooldown for " .. cd.cooldownRemainingMs .. "ms")
end
```
#### GetSpellIdForName(spellName)
Returns:
1st param: the max rank spell id for a spell name if it exists in your spellbook. Returns 0 if the spell is not in your spellbook.
Examples:
```
/run local spellId=GetSpellIdForName("Frostbolt");print(spellId)
/run local spellId=GetSpellIdForName("Frostbolt(Rank 1)");print(spellId)
```
#### GetSpellNameAndRankForId(id)
Returns:
1st param: the spell name for a spell id
2nd param: the spell rank for a spell id as a string such as "Rank 1"
Examples:
```
/run local spellName,spellRank=GetSpellNameAndRankForId(116);print(spellName);print(spellRank)
prints "Frostbolt" and "Rank 1"
```
#### GetSpellSlotTypeIdForName(spellName)
Returns:
1st param: the 1 indexed (lua calls expect this) spell slot number for a spell name if it exists in your spellbook. Returns 0 if the spell is not in your spellbook.
2nd param: the book type of the spell, either "spell", "pet" or "unknown".
3rd param: the spell id of the spell. Returns 0 if the spell is not in your spellbook.
Examples:
```
/run local slot, bookType, spellId=GetSpellSlotTypeIdForName("Frostbolt");print(slot);print(bookType);print(spellId)
```
#### GetNampowerVersion()
Returns the current version of Nampower split into major, minor and patch numbers.
So if version was v2.8.6 it would return 2, 8, 6 as integers.
Examples:
```
/run local major, minor, patch=GetNampowerVersion();print(major);print(minor);print(patch)
```
The previous version of this `GetSpellSlotAndTypeForName` was removed as it was returning a 0 indexed slot number which was confusing to use in lua.
#### GetItemLevel(itemId)
Returns the item level of an item. Returns an error if the item id is invalid.
Examples:
```
/run local itemLevel=GetItemLevel(22589);print(itemLevel)
should print 90 for atiesh
```
#### ChannelStopCastingNextTick()
Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel.
+8 -2
View File
@@ -22,8 +22,14 @@ set(SOURCE_FILES
spellchannel.cpp
spellevents.hpp
spellevents.cpp
scripts.hpp
scripts.cpp
misc_scripts.hpp
misc_scripts.cpp
spell_scripts.hpp
spell_scripts.cpp
cooldown_scripts.hpp
cooldown_scripts.cpp
item_scripts.hpp
item_scripts.cpp
items.hpp
items.cpp
dbc_fields.hpp
+4 -4
View File
@@ -49,12 +49,12 @@ namespace Nampower {
}
void push(const CastSpellParams &params, bool replaceMatchingNonGcdCategory) {
if (replaceMatchingNonGcdCategory && params.castType == CastType::NON_GCD && params.gcDCategory != 0) {
auto nonGcdParams = findGcdCategory(params.gcDCategory);
if (replaceMatchingNonGcdCategory && params.castType == CastType::NON_GCD && params.gcdCategory != 0) {
auto nonGcdParams = findGcdCategory(params.gcdCategory);
if (nonGcdParams) {
DEBUG_LOG("Replacing queued nonGcd spell " << game::GetSpellName(nonGcdParams->spellId) << " with "
<< game::GetSpellName(params.spellId)
<< " for gcdCategory " << params.gcDCategory);
<< " for gcdCategory " << params.gcdCategory);
*nonGcdParams = params;
return;
}
@@ -156,7 +156,7 @@ namespace Nampower {
CastSpellParams *findGcdCategory(uint32_t gcdCategory) {
for (int i = 0; i < size; i++) {
int index = (front + i) % maxSize;
if (queue[index].gcDCategory == gcdCategory) {
if (queue[index].gcdCategory == gcdCategory) {
return &queue[index];
}
}
+261
View File
@@ -0,0 +1,261 @@
#include "cooldown_scripts.hpp"
#include "helper.hpp"
#include "game.hpp"
#include "items.hpp"
#include "offsets.hpp"
namespace Nampower {
struct CooldownDetail {
bool isOnCooldown = false;
uint32_t cooldownRemainingMs = 0;
uint32_t individualStartMs = 0;
uint32_t individualDurationMs = 0;
uint32_t individualRemainingMs = 0;
bool isOnIndividualCooldown = false;
uint32_t categoryId = 0;
uint32_t categoryStartMs = 0;
uint32_t categoryDurationMs = 0;
uint32_t categoryRemainingMs = 0;
bool isOnCategoryCooldown = false;
uint32_t gcdCategoryId = 0;
uint32_t gcdCategoryStartMs = 0;
uint32_t gcdCategoryDurationMs = 0;
uint32_t gcdCategoryRemainingMs = 0;
bool isOnGcdCategoryCooldown = false;
};
inline void PushTableInt(uintptr_t *luaState, char *key, int value) {
lua_pushstring(luaState, key);
lua_pushnumber(luaState, value);
lua_settable(luaState, -3);
}
game::SpellHistoryEntry *GetSpellHistoryHead() {
auto const spellHistoryAddr = static_cast<uintptr_t>(Offsets::SpellHistories);
// Try treating the offset as the object itself first.
auto head = *reinterpret_cast<game::SpellHistoryEntry **>(spellHistoryAddr + 8);
if (!head) {
// Some clients store a pointer at the address instead of the object.
auto const maybePtr = *reinterpret_cast<uintptr_t *>(spellHistoryAddr);
if (maybePtr) {
head = *reinterpret_cast<game::SpellHistoryEntry **>(maybePtr + 8);
}
}
if (reinterpret_cast<uintptr_t>(head) & 0x1) {
return nullptr;
}
return head;
}
CooldownDetail GetCooldownFromSpellHistory(uint32_t spellId, uint32_t itemId, uint32_t itemCategoryOverride) {
CooldownDetail detail{};
auto const spellRec = game::GetSpellInfo(spellId);
if (!spellRec) {
return detail;
}
auto const category = itemCategoryOverride ? itemCategoryOverride : spellRec->Category;
auto const startRecoveryCategory = spellRec->StartRecoveryCategory;
auto const now = static_cast<uint32_t>(GetWowTimeMs() & 0xFFFFFFFF); // get bottom 32 bits of time in ms
int entryCount = 0;
for (auto entry = GetSpellHistoryHead();
entry && ((reinterpret_cast<uintptr_t>(entry) & 0x1) == 0);
entry = reinterpret_cast<game::SpellHistoryEntry *>(entry->field4_0x4)) {
// Individual spell cooldown
if (entry->spellID == spellId && entry->itemID == itemId &&
(entry->recoveryTime != 0 || entry->onHold)) {
auto end = entry->onHold
? uint64_t(now) + entry->recoveryTime
: uint64_t(entry->recoveryStart) + entry->recoveryTime;
if (end > now) {
detail.individualStartMs = entry->onHold ? now : entry->recoveryStart;
detail.individualDurationMs = entry->recoveryTime;
detail.individualRemainingMs = static_cast<uint32_t>(end - now);
detail.isOnIndividualCooldown = true;
}
}
// Category cooldown
if (entry->categoryRecoveryTime != 0 && entry->category == category && category != 0) {
auto start = entry->onHold ? now : entry->categoryRecoveryStart;
auto end = uint64_t(start) + entry->categoryRecoveryTime;
if (end > now) {
detail.categoryId = entry->category;
detail.categoryStartMs = start;
detail.categoryDurationMs = entry->categoryRecoveryTime;
detail.categoryRemainingMs = static_cast<uint32_t>(end - now);
detail.isOnCategoryCooldown = true;
}
}
// GCD category cooldown (startRecoveryCategory)
if (entry->startRecoveryCategory == startRecoveryCategory && entry->startRecoveryTime != 0 && startRecoveryCategory != 0) {
auto start = entry->onHold ? now : entry->recoveryStart;
auto end = entry->onHold
? uint64_t(now) + entry->startRecoveryTime
: uint64_t(entry->recoveryStart) + entry->startRecoveryTime;
if (end > now) {
detail.gcdCategoryId = entry->startRecoveryCategory;
detail.gcdCategoryStartMs = start;
detail.gcdCategoryDurationMs = entry->startRecoveryTime;
detail.gcdCategoryRemainingMs = static_cast<uint32_t>(end - now);
detail.isOnGcdCategoryCooldown = true;
}
}
}
// Calculate overall cooldown status
detail.isOnCooldown = detail.isOnIndividualCooldown || detail.isOnCategoryCooldown || detail.isOnGcdCategoryCooldown;
// Find the maximum remaining cooldown
detail.cooldownRemainingMs = detail.individualRemainingMs;
if (detail.categoryRemainingMs > detail.cooldownRemainingMs) {
detail.cooldownRemainingMs = detail.categoryRemainingMs;
}
if (detail.gcdCategoryRemainingMs > detail.cooldownRemainingMs) {
detail.cooldownRemainingMs = detail.gcdCategoryRemainingMs;
}
return detail;
}
void PushCooldownDetailTable(uintptr_t *luaState, const CooldownDetail &detail) {
static char isOnCooldownKey[] = "isOnCooldown";
static char cooldownRemainingMsKey[] = "cooldownRemainingMs";
static char individualStartSKey[] = "individualStartS";
static char individualDurationMsKey[] = "individualDurationMs";
static char individualRemainingMsKey[] = "individualRemainingMs";
static char isOnIndividualCooldownKey[] = "isOnIndividualCooldown";
static char categoryIdKey[] = "categoryId";
static char categoryStartSKey[] = "categoryStartS";
static char categoryDurationMsKey[] = "categoryDurationMs";
static char categoryRemainingMsKey[] = "categoryRemainingMs";
static char isOnCategoryCooldownKey[] = "isOnCategoryCooldown";
static char gcdCategoryIdKey[] = "gcdCategoryId";
static char gcdCategoryStartSKey[] = "gcdCategoryStartS";
static char gcdCategoryDurationMsKey[] = "gcdCategoryDurationMs";
static char gcdCategoryRemainingMsKey[] = "gcdCategoryRemainingMs";
static char isOnGcdCategoryCooldownKey[] = "isOnGcdCategoryCooldown";
lua_newtable(luaState);
// Overall cooldown status
PushTableInt(luaState, isOnCooldownKey, detail.isOnCooldown ? 1 : 0);
PushTableValue(luaState, cooldownRemainingMsKey, detail.cooldownRemainingMs);
// Individual cooldown
PushTableValue(luaState, individualStartSKey, detail.individualStartMs / 1000.0);
PushTableValue(luaState, individualDurationMsKey, detail.individualDurationMs);
PushTableValue(luaState, individualRemainingMsKey, detail.individualRemainingMs);
PushTableInt(luaState, isOnIndividualCooldownKey, detail.isOnIndividualCooldown ? 1 : 0);
// Category cooldown
PushTableValue(luaState, categoryIdKey, detail.categoryId);
PushTableValue(luaState, categoryStartSKey, detail.categoryStartMs / 1000.0);
PushTableValue(luaState, categoryDurationMsKey, detail.categoryDurationMs);
PushTableValue(luaState, categoryRemainingMsKey, detail.categoryRemainingMs);
PushTableInt(luaState, isOnCategoryCooldownKey, detail.isOnCategoryCooldown ? 1 : 0);
// GCD category cooldown
PushTableValue(luaState, gcdCategoryIdKey, detail.gcdCategoryId);
PushTableValue(luaState, gcdCategoryStartSKey, detail.gcdCategoryStartMs / 1000.0);
PushTableValue(luaState, gcdCategoryDurationMsKey, detail.gcdCategoryDurationMs);
PushTableValue(luaState, gcdCategoryRemainingMsKey, detail.gcdCategoryRemainingMs);
PushTableInt(luaState, isOnGcdCategoryCooldownKey, detail.isOnGcdCategoryCooldown ? 1 : 0);
}
CooldownDetail GetItemCooldownDetail(uint32_t itemId) {
auto *itemStats = GetItemStats(itemId);
CooldownDetail best{};
uint32_t bestRemaining = 0;
if (itemStats) {
for (int i = 0; i < 5; ++i) {
auto const spellId = static_cast<uint32_t>(itemStats->m_spellID[i]);
if (spellId == 0) {
continue;
}
auto const categoryOverride = static_cast<uint32_t>(itemStats->m_spellCategory[i]);
auto const detail = GetCooldownFromSpellHistory(spellId, itemId, categoryOverride);
if (!detail.isOnCooldown) {
continue;
}
if (detail.cooldownRemainingMs >= bestRemaining) {
best = detail;
bestRemaining = detail.cooldownRemainingMs;
}
}
}
if (!best.isOnCooldown) {
best = GetCooldownFromSpellHistory(0, itemId, 0);
}
return best;
}
uint32_t Script_GetSpellIdCooldown(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetSpellCooldown(spellId)");
return 0;
}
uint32_t spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
auto const cooldown = GetCooldownFromSpellHistory(spellId, 0, 0);
PushCooldownDetailTable(luaState, cooldown);
return 1;
}
uint32_t Script_GetItemIdCooldown(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
auto param1IsString = lua_isstring(luaState, 1);
auto param1IsNumber = lua_isnumber(luaState, 1);
if (!param1IsString && !param1IsNumber) {
lua_error(luaState, "Usage: GetItemCooldown(itemId) or GetItemCooldown(itemName)");
return 0;
}
uint32_t itemId = 0;
if (param1IsNumber) {
itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
} else {
lua_error(luaState, "Item name lookup not yet implemented");
return 0;
}
// Item cooldowns rely on the associated spell entries being in the history with the item id set.
auto const cooldown = GetItemCooldownDetail(itemId);
PushCooldownDetailTable(luaState, cooldown);
return 1;
}
}
+12
View File
@@ -0,0 +1,12 @@
//
// Cooldown-related Lua script bindings
//
#pragma once
#include "main.hpp"
namespace Nampower {
uint32_t Script_GetSpellIdCooldown(uintptr_t *luaState);
uint32_t Script_GetItemIdCooldown(uintptr_t *luaState);
}
+1 -1
View File
@@ -56,7 +56,7 @@ namespace Nampower {
// Initialize field maps (call once before using field lookups)
void InitializeFieldMaps();
// Forward declarations for Lua functions (defined in offsets.hpp/scripts.cpp)
// Forward declarations for Lua functions (defined in offsets.hpp/misc_scripts.cpp/spell_scripts.cpp)
using lua_pushnumberT = void (__fastcall *)(uintptr_t *, double);
using lua_pushstringT = void (__fastcall *)(uintptr_t *, char *);
using lua_newtableT = void (__fastcall *)(uintptr_t *);
+21
View File
@@ -149,6 +149,27 @@ namespace game {
char targetString[128];
};
struct SpellHistoryEntry {
uint32_t field0_0x0;
uint32_t field4_0x4;
uint32_t spellID;
uint32_t itemID;
uint32_t recoveryStart;
uint32_t recoveryTime;
uint32_t category;
uint32_t categoryRecoveryStart;
uint32_t categoryRecoveryTime;
bool onHold;
uint8_t padding1;
uint8_t padding2;
uint8_t padding3;
uint32_t startRecoveryCategory;
uint32_t startRecoveryTime;
uint32_t field_unknown1;
uint32_t field_unknown2;
uint32_t field_unknown3;
};
struct CSpriteClickEvent {
unsigned __int64 objectGUID;
unsigned int button;
+37
View File
@@ -57,4 +57,41 @@ namespace Nampower {
float GetNameplateDistance();
void SetNameplateDistance(float distance);
// Lua table helper functions
inline void PushTableValue(uintptr_t *luaState, char *key, uint32_t value) {
lua_pushstring(luaState, key);
lua_pushnumber(luaState, value);
lua_settable(luaState, -3);
}
inline void PushTableValue(uintptr_t *luaState, char *key, uint64_t value) {
lua_pushstring(luaState, key);
lua_pushnumber(luaState, static_cast<double>(value));
lua_settable(luaState, -3);
}
inline void PushTableValue(uintptr_t *luaState, char *key, double value) {
lua_pushstring(luaState, key);
lua_pushnumber(luaState, value);
lua_settable(luaState, -3);
}
inline void PushTableValue(uintptr_t *luaState, char *key, int32_t value) {
lua_pushstring(luaState, key);
lua_pushnumber(luaState, value);
lua_settable(luaState, -3);
}
inline void PushTableValue(uintptr_t *luaState, char *key, char *value) {
lua_pushstring(luaState, key);
lua_pushstring(luaState, value);
lua_settable(luaState, -3);
}
inline void PushTableValue(uintptr_t *luaState, char *key, const char *value) {
lua_pushstring(luaState, key);
lua_pushstring(luaState, const_cast<char *>(value));
lua_settable(luaState, -3);
}
}
+611
View File
@@ -0,0 +1,611 @@
#include "item_scripts.hpp"
#include "helper.hpp"
#include "items.hpp"
#include "logging.hpp"
#include "offsets.hpp"
#include <cctype>
#include <cstring>
#include <string>
#include <unordered_map>
namespace Nampower {
// Local cache for item name lookups
static std::unordered_map<std::string, uint32_t> itemNameToIdCache;
// String keys used when pushing item data to Lua
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 ToLowerCase(const char *str) {
std::string result;
if (!str) return result;
while (*str) {
result += static_cast<char>(tolower(*str));
++str;
}
return result;
}
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;
}
void CacheItemNameToId(const char *itemName, uint32_t itemId) {
if (!itemName || itemId == 0) return;
std::string lowerName = ToLowerCase(itemName);
itemNameToIdCache[lowerName] = itemId;
}
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) {
if (searchItemId != 0) {
return itemId == searchItemId;
}
if (searchItemName) {
auto itemStats = GetItemStats(itemId);
if (itemStats && itemStats->m_displayName[0]) {
bool matches = _stricmp(itemStats->m_displayName[0], searchItemName) == 0;
if (matches) {
CacheItemNameToId(searchItemName, itemId);
}
return matches;
}
}
return false;
}
void PushItemFoundResult(uintptr_t *luaState, int32_t bagIndex, uint32_t slot) {
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<double>(bagIndex));
lua_pushnumber(luaState, static_cast<double>(adjustedSlot + 1)); // lua is 1 indexed
}
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr) {
auto vftable = game::GetObjectVFTable(containerPtr);
using GetBagPtrT = uintptr_t * (__thiscall *)(uintptr_t *);
auto getBagPtrFunc = reinterpret_cast<GetBagPtrT>(vftable[4]);
return getBagPtrFunc(containerPtr);
}
void CreateBasicItemInfoTable(uintptr_t *luaState, game::CGItem *cgItem) {
if (!cgItem) {
lua_pushnil(luaState);
return;
}
lua_newtable(luaState);
PushTableValue(luaState, itemIdKey, cgItem->itemId);
PushTableValue(luaState, permanentEnchantIdKey, cgItem->permanentEnchantId);
PushTableValue(luaState, tempEnchantIdKey, cgItem->tempEnchantId);
}
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;
lua_newtable(luaState);
PushTableValue(luaState, itemIdKey, itemId);
PushTableValue(luaState, stackCountKey, itemFields->stackCount);
PushTableValue(luaState, durationKey, itemFields->duration);
PushTableValue(luaState, flagsKey, itemFields->flags);
PushTableValue(luaState, permanentEnchantIdKey, itemFields->permEnchantmentSlot.id);
PushTableValue(luaState, tempEnchantIdKey, itemFields->tempEnchantmentSlot.id);
PushTableValue(luaState, tempEnchantmentTimeLeftMsKey, itemFields->tempEnchantmentSlot.duration);
PushTableValue(luaState, tempEnchantmentChargesKey, itemFields->tempEnchantmentSlot.charges);
PushTableValue(luaState, durabilityKey, itemFields->durability);
PushTableValue(luaState, maxDurabilityKey, itemFields->maxDurability);
}
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;
}
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<double>(adjustedSlot + 1)); // lua is 1 indexed
CreateItemInfoTable(luaState, item);
lua_settable(luaState, -3);
}
uint32_t Script_FindPlayerItemSlot(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
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;
if (lua_isnumber(luaState, 1)) {
searchItemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
} else if (lua_isstring(luaState, 1)) {
searchItemName = lua_tostring(luaState, 1);
uint32_t cachedItemId = GetItemIdFromCache(searchItemName);
if (cachedItemId != 0) {
searchItemId = cachedItemId;
searchItemName = nullptr;
}
} else {
lua_error(luaState, "Usage: FindPlayerItemSlot(itemName) or FindPlayerItemSlot(itemId)");
return 0;
}
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto item = getBagItem(inventory, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
lua_pushnil(luaState);
lua_pushnumber(luaState, static_cast<double>(slot+1)); // lua is 1 indexed
return 2;
}
}
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;
}
}
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<uint64_t *>(Offsets::BankGuid);
if (bankGuid > 0) {
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;
}
}
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;
}
}
}
}
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;
}
}
lua_pushnil(luaState);
lua_pushnil(luaState);
return 2;
}
uint32_t Script_GetEquippedItems(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
uint64_t guid;
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 {
guid = game::ClntObjMgrGetActivePlayerGuid();
}
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_error(luaState, "Unable to get unit");
return 0;
}
auto const canInspectUnit = reinterpret_cast<CanInspectUnitT>(Offsets::CanInspectUnit);
if (!canInspectUnit(unit)) {
lua_error(luaState, "Cannot inspect unit");
return 0;
}
lua_newtable(luaState);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
bool isPlayer = (guid == playerGuid);
if (isPlayer) {
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto inventory = game::GetPlayerInventoryPtr(unit);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto item = getBagItem(inventory, slot);
if (item) {
PushBagCGItemToTable(luaState, -999, slot, item);
}
}
} else {
auto const getEquippedItem = reinterpret_cast<CGUnit_C_GetEquippedItemAtSlotT>(
Offsets::CGUnit_C_GetEquippedItemAtSlot);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto cgItem = getEquippedItem(unit, slot);
if (cgItem) {
lua_pushnumber(luaState, static_cast<double>(slot+1)); // lua is 1 indexed
CreateBasicItemInfoTable(luaState, cgItem);
lua_settable(luaState, -3);
}
}
}
return 1;
}
uint32_t Script_GetEquippedItem(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (lua_gettop(luaState) < 2) {
lua_error(luaState, "Usage: GetEquippedItem(unitStr, slot) or GetEquippedItem(guid, slot)");
return 0;
}
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 {
guid = game::ClntObjMgrGetActivePlayerGuid();
}
if (!lua_isnumber(luaState, 2)) {
lua_error(luaState, "Slot must be a number between 1 and 19");
return 0;
}
auto luaSlot = static_cast<uint32_t>(lua_tonumber(luaState, 2));
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<CanInspectUnitT>(Offsets::CanInspectUnit);
if (!canInspectUnit(unit)) {
lua_error(luaState, "Cannot inspect unit");
return 0;
}
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
bool isPlayer = (guid == playerGuid);
if (isPlayer) {
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto inventory = game::GetPlayerInventoryPtr(unit);
auto item = getBagItem(inventory, luaSlot-1); // luaSlot is 1-indexed
CreateItemInfoTable(luaState, item);
} else {
auto const getEquippedItem = reinterpret_cast<CGUnit_C_GetEquippedItemAtSlotT>(
Offsets::CGUnit_C_GetEquippedItemAtSlot);
auto cgItem = getEquippedItem(unit, luaSlot - 1); // luaSlot is 1-indexed
CreateBasicItemInfoTable(luaState, cgItem);
}
return 1;
}
uint32_t ConvertLuaSlot(int32_t bagIndex, uint32_t luaSlot) {
uint32_t relativeSlot = luaSlot - 1;
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 {
return relativeSlot;
}
}
uint32_t Script_GetBagItem(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (lua_gettop(luaState) < 2) {
lua_error(luaState, "Usage: GetBagItem(bagIndex, slot)");
return 0;
}
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Bag index must be a number");
return 0;
}
auto bagIndex = static_cast<int32_t>(lua_tonumber(luaState, 1));
if (!lua_isnumber(luaState, 2)) {
lua_error(luaState, "Slot must be a number");
return 0;
}
auto luaSlot = static_cast<uint32_t>(lua_tonumber(luaState, 2));
auto slot = ConvertLuaSlot(bagIndex, luaSlot);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto player = game::GetObjectPtr(playerGuid);
auto inventory = game::GetPlayerInventoryPtr(player);
game::CGItem_C *item = nullptr;
if (bagIndex == 0) {
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) {
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) {
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(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) {
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(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) {
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 Script_GetBagItems(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
lua_newtable(luaState);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto player = game::GetObjectPtr(playerGuid);
auto inventory = game::GetPlayerInventoryPtr(player);
lua_pushnumber(luaState, static_cast<double>(0));
lua_newtable(luaState);
for (uint32_t slot = 23; slot <= 38; slot++) {
auto item = getBagItem(inventory, slot);
if (item) {
PushBagCGItemToTable(luaState, 0, slot, item);
}
}
lua_settable(luaState, -3);
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;
lua_pushnumber(luaState, static_cast<double>(bagIndex));
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);
}
}
lua_settable(luaState, -3);
}
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
if (bankGuid > 0) {
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;
lua_pushnumber(luaState, static_cast<double>(bagIndex));
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);
}
}
lua_settable(luaState, -3);
}
}
return 1;
}
}
+15
View File
@@ -0,0 +1,15 @@
//
// Item-related Lua script bindings
//
#pragma once
#include "main.hpp"
namespace Nampower {
uint32_t Script_FindPlayerItemSlot(uintptr_t *luaState);
uint32_t Script_GetEquippedItems(uintptr_t *luaState);
uint32_t Script_GetEquippedItem(uintptr_t *luaState);
uint32_t Script_GetBagItem(uintptr_t *luaState);
uint32_t Script_GetBagItems(uintptr_t *luaState);
}
-726
View File
@@ -22,9 +22,6 @@ namespace Nampower {
// Track pending async loads (can have multiple at once)
static std::unordered_set<uint32_t> pendingItemIds;
// Cache for itemName -> itemId mappings (case-insensitive)
static std::unordered_map<std::string, uint32_t> itemNameToIdCache;
// Export state tracking
static bool isExporting = false;
static uint32_t currentExportItemId = 0;
@@ -34,18 +31,6 @@ namespace Nampower {
auto const getRow = reinterpret_cast<DBCache_ItemCacheDBGetRowT>(Offsets::DBCache_ItemCacheDBGetRow);
auto const itemCache = reinterpret_cast<void *>(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";
@@ -359,715 +344,4 @@ 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<char>(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<double>(bagIndex));
lua_pushnumber(luaState, static_cast<double>(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<GetBagPtrT>(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<double>(cgItem->itemId));
lua_settable(luaState, -3);
// Add permanentEnchantId
lua_pushstring(luaState, permanentEnchantIdKey);
lua_pushnumber(luaState, static_cast<double>(cgItem->permanentEnchantId));
lua_settable(luaState, -3);
// Add tempEnchantId
lua_pushstring(luaState, tempEnchantIdKey);
lua_pushnumber(luaState, static_cast<double>(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<double>(itemId));
lua_settable(luaState, -3);
// Add stackCount
lua_pushstring(luaState, stackCountKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->stackCount));
lua_settable(luaState, -3);
// Add duration
lua_pushstring(luaState, durationKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->duration));
lua_settable(luaState, -3);
// Add flags
lua_pushstring(luaState, flagsKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->flags));
lua_settable(luaState, -3);
// Add permanent enchantment
lua_pushstring(luaState, permanentEnchantIdKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->permEnchantmentSlot.id));
lua_settable(luaState, -3);
// Add temp enchantment
lua_pushstring(luaState, tempEnchantIdKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->tempEnchantmentSlot.id));
lua_settable(luaState, -3);
// Add temp enchantment time left
lua_pushstring(luaState, tempEnchantmentTimeLeftMsKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->tempEnchantmentSlot.duration));
lua_settable(luaState, -3);
// Add temp enchantment charges
lua_pushstring(luaState, tempEnchantmentChargesKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->tempEnchantmentSlot.charges));
lua_settable(luaState, -3);
// Add durability
lua_pushstring(luaState, durabilityKey);
lua_pushnumber(luaState, static_cast<double>(itemFields->durability));
lua_settable(luaState, -3);
// Add max durability
lua_pushstring(luaState, maxDurabilityKey);
lua_pushnumber(luaState, static_cast<double>(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<double>(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<uint32_t>(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<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(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<double>(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<uint64_t *>(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<CanInspectUnitT>(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<CGBag_C_GetItemAtSlotT>(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<CGUnit_C_GetEquippedItemAtSlotT>(
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<double>(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<uint32_t>(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<CanInspectUnitT>(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<CGBag_C_GetItemAtSlotT>(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<CGUnit_C_GetEquippedItemAtSlotT>(
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<int32_t>(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<uint32_t>(lua_tonumber(luaState, 2));
auto slot = ConvertLuaSlot(bagIndex, luaSlot);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(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<uint64_t *>(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<uint64_t *>(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<GetContainerGuidT>(Offsets::GetContainerGuid);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(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<double>(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<double>(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<uint64_t *>(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<double>(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;
}
}
-5
View File
@@ -16,9 +16,4 @@ namespace Nampower {
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);
}
+23 -7
View File
@@ -33,10 +33,14 @@
#include "main.hpp"
#include "spellevents.hpp"
#include "spellcast.hpp"
#include "scripts.hpp"
#include "misc_scripts.hpp"
#include "spell_scripts.hpp"
#include "spellchannel.hpp"
#include "helper.hpp"
#include "items.hpp"
#include "item_scripts.hpp"
#include "cooldown_scripts.hpp"
#include "item_scripts.hpp"
#include "auras.hpp"
#include <cstdint>
@@ -166,7 +170,7 @@ namespace Nampower {
uintptr_t *GetLuaStatePtr() {
typedef uintptr_t *(__fastcall *GETCONTEXT)(void);
static auto p_GetContext = reinterpret_cast<GETCONTEXT>(0x7040D0);
static auto p_GetContext = reinterpret_cast<GETCONTEXT>(Offsets::lua_state_ptr);
return p_GetContext();
}
@@ -316,6 +320,7 @@ namespace Nampower {
void ResetCastFlags() {
// don't reset delayEndMs
gCastData.castEndMs = 0;
gCastData.castSpellId = 0;
gCastData.gcdEndMs = 0;
ResetChannelingFlags();
}
@@ -1305,19 +1310,30 @@ namespace Nampower {
// 2.16 additions
char findPlayerItemSlot[] = "FindPlayerItemSlot";
RegisterLuaFunction(findPlayerItemSlot, reinterpret_cast<uintptr_t *>(FindPlayerItemSlot));
RegisterLuaFunction(findPlayerItemSlot, reinterpret_cast<uintptr_t *>(Script_FindPlayerItemSlot));
char getEquippedItems[] = "GetEquippedItems";
RegisterLuaFunction(getEquippedItems, reinterpret_cast<uintptr_t *>(GetEquippedItems));
RegisterLuaFunction(getEquippedItems, reinterpret_cast<uintptr_t *>(Script_GetEquippedItems));
char getEquippedItem[] = "GetEquippedItem";
RegisterLuaFunction(getEquippedItem, reinterpret_cast<uintptr_t *>(GetEquippedItem));
RegisterLuaFunction(getEquippedItem, reinterpret_cast<uintptr_t *>(Script_GetEquippedItem));
char getBagItems[] = "GetBagItems";
RegisterLuaFunction(getBagItems, reinterpret_cast<uintptr_t *>(GetBagItems));
RegisterLuaFunction(getBagItems, reinterpret_cast<uintptr_t *>(Script_GetBagItems));
char getBagItem[] = "GetBagItem";
RegisterLuaFunction(getBagItem, reinterpret_cast<uintptr_t *>(GetBagItem));
RegisterLuaFunction(getBagItem, reinterpret_cast<uintptr_t *>(Script_GetBagItem));
// 2.17 additions
char GetCastInfo[] = "GetCastInfo";
RegisterLuaFunction(GetCastInfo, reinterpret_cast<uintptr_t *>(Script_GetCastInfo));
char getSpellIdCooldown[] = "GetSpellIdCooldown";
RegisterLuaFunction(getSpellIdCooldown, reinterpret_cast<uintptr_t *>(Script_GetSpellIdCooldown));
char getItemIdCooldown[] = "GetItemIdCooldown";
RegisterLuaFunction(getItemIdCooldown, reinterpret_cast<uintptr_t *>(Script_GetItemIdCooldown));
}
std::once_flag loadFlag;
+1 -1
View File
@@ -30,7 +30,7 @@ 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 = 16;
constexpr uint32_t MINOR_VERSION = 17;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
+538
View File
@@ -0,0 +1,538 @@
//
// Created by pmacc on 1/8/2025.
//
#include "misc_scripts.hpp"
#include "offsets.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
#include "unit_fields.hpp"
#include "helper.hpp"
#include <cstring>
namespace Nampower {
// Lua table field name constants
namespace LuaFields {
static char castId[] = "castId";
static char spellId[] = "spellId";
static char guid[] = "guid";
static char castType[] = "castType";
static char castStartS[] = "castStartS";
static char castEndS[] = "castEndS";
static char castRemainingMs[] = "castRemainingMs";
static char castDurationMs[] = "castDurationMs";
static char gcdEndS[] = "gcdEndS";
static char gcdRemainingMs[] = "gcdRemainingMs";
}
bool gScriptQueued;
int gScriptPriority = 1;
char *queuedScript;
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
auto const castingSpellId = reinterpret_cast<uint32_t *>(Offsets::CastingSpellId);
lua_pushnumber(luaState, *castingSpellId);
auto const isCasting = gCastData.castEndMs > GetTime();
auto const isChanneling = gCastData.channeling;
auto const visualSpellId = reinterpret_cast<uint32_t *>(Offsets::VisualSpellId);
lua_pushnumber(luaState, *visualSpellId);
auto const autoRepeatingSpellId = reinterpret_cast<uint32_t *>(Offsets::AutoRepeatingSpellId);
lua_pushnumber(luaState, *autoRepeatingSpellId);
auto playerUnit = game::GetObjectPtr(game::ClntObjMgrGetActivePlayerGuid());
if (isCasting) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
if (isChanneling) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
if (gCastData.pendingOnSwingCast) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
auto const attackPtr = playerUnit + 0x312; // auto attacking
if (attackPtr && *reinterpret_cast<uint32_t *>(attackPtr) > 0) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
return 7;
}
uint32_t Script_GetCastInfo(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
// Check if there's an active cast or channel
uint32_t activeSpellId = 0;
uint32_t castEndTime = 0;
CastSpellParams* castParams = nullptr;
// Check for active channeling spell first
if (gCastData.channeling && gCastData.channelSpellId != 0) {
activeSpellId = gCastData.channelSpellId;
castEndTime = gCastData.channelEndMs;
// cast will be finished for channels
castParams = gCastHistory.findNewestSuccessfulSpellId(activeSpellId);
}
// Check for active cast spell
else if (gCastData.castSpellId != 0) {
activeSpellId = gCastData.castSpellId;
// Use the max of castEndMs and gcdEndMs
castEndTime = (gCastData.castEndMs > gCastData.gcdEndMs) ? gCastData.castEndMs : gCastData.gcdEndMs;
// cast won't be finished yet
castParams = gCastHistory.findNewestWaitingForServerSpellId(activeSpellId);
}
// If no active cast or channel, return nil
if (activeSpellId == 0) {
lua_pushnil(luaState);
return 1;
}
if (castParams == nullptr || castParams->castId == 0) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Get current time and calculate offset to convert to WoW time
uint32_t currentTime = GetTime();
uint64_t currentWowTime = GetWowTimeMs();
int64_t timeOffset = static_cast<int64_t>(currentWowTime) - static_cast<int64_t>(currentTime);
// Convert all timestamps to WoW time and convert to seconds
double castStartTimeWow = (castParams->castStartTimeMs + timeOffset) / 1000.0;
double castEndTimeWow = (castEndTime + timeOffset) / 1000.0;
double gcdEndTimeWow = (gCastData.gcdEndMs + timeOffset) / 1000.0;
// Add fields to table
PushTableValue(luaState, LuaFields::castId, castParams->castId);
PushTableValue(luaState, LuaFields::spellId, castParams->spellId);
PushTableValue(luaState, LuaFields::guid, castParams->guid);
PushTableValue(luaState, LuaFields::castType, static_cast<uint32_t>(castParams->castType));
PushTableValue(luaState, LuaFields::castStartS, castStartTimeWow);
PushTableValue(luaState, LuaFields::castEndS, castEndTimeWow);
uint32_t timeRemaining = (castEndTime > currentTime) ? (castEndTime - currentTime) : 0;
PushTableValue(luaState, LuaFields::castRemainingMs, timeRemaining);
uint32_t duration = (castEndTime > castParams->castStartTimeMs) ?
(castEndTime - castParams->castStartTimeMs) : 0;
PushTableValue(luaState, LuaFields::castDurationMs, duration);
// Add GCD info
PushTableValue(luaState, LuaFields::gcdEndS, gcdEndTimeWow);
uint32_t gcdRemaining = (gCastData.gcdEndMs > currentTime) ? (gCastData.gcdEndMs - currentTime) : 0;
PushTableValue(luaState, LuaFields::gcdRemainingMs, gcdRemaining);
return 1; // Return the table
}
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (gCastData.channeling) {
DEBUG_LOG("ChannelStopCastingNextTick activated, canceling next tick");
gCastData.cancelChannelNextTick = true;
}
return 0;
}
uint32_t Script_GetNampowerVersion(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
lua_pushnumber(luaState, MAJOR_VERSION);
lua_pushnumber(luaState, MINOR_VERSION);
lua_pushnumber(luaState, PATCH_VERSION);
return 3;
}
uint32_t Script_GetItemLevel(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (lua_isnumber(luaState, 1)) {
auto const itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Pointer to ItemDBCache
void *itemDbCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
// Parameters for the DBCache<>::GetRecord function
int **param2 = nullptr;
int *param3 = nullptr;
int *param4 = nullptr;
char param5 = 0;
// Call the DBCache<>::GetRecord function
auto getRecord = reinterpret_cast<uintptr_t *(__thiscall *)(void *, uint32_t, int **, int *, int *, char)>(
Offsets::DBCacheGetRecord
);
uintptr_t *itemObject = getRecord(itemDbCache, itemId, param2, param3, param4, param5);
if (itemObject == nullptr) {
lua_error(luaState, "Item not found in DBCache");
return 0;
}
uint32_t itemLevel = *reinterpret_cast<uint32_t *>(itemObject + 14);
lua_pushnumber(luaState, itemLevel);
return 1;
} else {
lua_error(luaState, "Usage: GetItemLevel(itemId)");
}
return 0;
}
uint32_t Script_QueueScript(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
DEBUG_LOG("Trying to queue script");
auto const currentTime = GetTime();
auto effectiveCastEndMs = EffectiveCastEndMs();
auto remainingEffectiveCastTime = (effectiveCastEndMs > currentTime) ? effectiveCastEndMs - currentTime : 0;
auto remainingGcd = (gCastData.gcdEndMs > currentTime) ? gCastData.gcdEndMs - currentTime : 0;
auto inSpellQueueWindow = InSpellQueueWindow(remainingEffectiveCastTime, remainingGcd, false);
if (inSpellQueueWindow) {
// check if valid string
if (lua_isstring(luaState, 1)) {
auto script = lua_tostring(luaState, 1);
if (script != nullptr && strlen(script) > 0) {
// save the script to be run later
queuedScript = script;
gScriptQueued = true;
// check if priority is set
if (lua_isnumber(luaState, 2)) {
gScriptPriority = (int) lua_tonumber(luaState, 2);
DEBUG_LOG("Queuing script priority " << gScriptPriority << ": " << script);
} else {
DEBUG_LOG("Queuing script: " << script);
}
}
} else {
DEBUG_LOG("Invalid script");
lua_error(luaState, "Usage: QueueScript(\"script\", (optional)priority)");
}
} else {
// just call regular runscript
auto const runScript = reinterpret_cast<LuaScriptT >(Offsets::Script_RunScript);
return runScript(luaState);
}
return 0;
}
bool RunQueuedScript(int priority) {
if (gScriptQueued && gScriptPriority == priority) {
auto currentTime = GetTime();
auto effectiveCastEndMs = EffectiveCastEndMs();
// get max of cooldown and gcd
auto delay = effectiveCastEndMs > gCastData.gcdEndMs ? effectiveCastEndMs : gCastData.gcdEndMs;
if (delay <= currentTime) {
DEBUG_LOG("Running queued script priority " << gScriptPriority << ": " << queuedScript);
LuaCall(queuedScript);
gScriptQueued = false;
gScriptPriority = 1;
return true;
}
}
return false;
}
uint32_t Script_GetItemStats(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetItemStats(itemId)");
return 0;
}
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Get from cache
game::ItemStats_C *item = GetItemStats(itemId);
if (!item) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, item, itemStatsFields, itemStatsFieldsCount);
// Push string fields manually (require language)
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
PushTableValue(luaState, const_cast<char *>("displayName"),
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
PushTableValue(luaState, const_cast<char *>("description"),
item->m_description ? item->m_description : const_cast<char *>(""));
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetItemStatsField(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName)");
return 0;
}
InitializeFieldMaps();
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
const char *fieldName = lua_tostring(luaState, 2);
// Get from cache
game::ItemStats_C *item = GetItemStats(itemId);
if (!item) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = itemStatsFieldMap.find(fieldName);
if (simpleIt != itemStatsFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(item) + itemStatsFields[i].offset;
switch (itemStatsFields[i].type) {
case FieldType::INT32:
lua_pushnumber(luaState, *reinterpret_cast<const int32_t *>(fieldPtr));
return 1;
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
return 1;
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
return 1;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
case FieldType::STRING: {
const char *str = *reinterpret_cast<const char *const *>(fieldPtr);
lua_pushstring(luaState, str ? const_cast<char *>(str) : const_cast<char *>(""));
return 1;
}
default:
break;
}
}
// O(1) hash table lookup for array fields
auto arrayIt = itemStatsArrayFieldMap.find(fieldName);
if (arrayIt != itemStatsArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = itemStatsArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(item) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1);
switch (field.type) {
case FieldType::INT32:
lua_pushnumber(luaState, reinterpret_cast<const int32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT32:
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
break;
default:
lua_pushnumber(luaState, 0);
break;
}
lua_settable(luaState, -3);
}
return 1;
}
// Check for special string fields
if (strcmp(fieldName, "displayName") == 0) {
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState,
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
return 1;
}
if (strcmp(fieldName, "description") == 0) {
lua_pushstring(luaState, item->m_description ? item->m_description : const_cast<char *>(""));
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
uint32_t Script_GetUnitData(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: GetUnitData(unitToken) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
return 0;
}
const char *unitToken = lua_tostring(luaState, 1);
uint64_t guid = GetUnitGuidFromString(unitToken);
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount);
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetUnitField(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: GetUnitField(unitToken, fieldName) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
return 0;
}
InitializeUnitFieldMaps();
const char *unitToken = lua_tostring(luaState, 1);
const char *fieldName = lua_tostring(luaState, 2);
uint64_t guid = GetUnitGuidFromString(unitToken);
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = unitFieldsFieldMap.find(fieldName);
if (simpleIt != unitFieldsFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + unitFieldsFields[i].offset;
switch (unitFieldsFields[i].type) {
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
return 1;
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
return 1;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(fieldPtr)));
return 1;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
default:
break;
}
}
// O(1) hash table lookup for array fields
auto arrayIt = unitFieldsArrayFieldMap.find(fieldName);
if (arrayIt != unitFieldsArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = unitFieldsArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1);
switch (field.type) {
case FieldType::UINT32:
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
break;
default:
lua_pushnumber(luaState, 0);
break;
}
lua_settable(luaState, -3);
}
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
}
+32
View File
@@ -0,0 +1,32 @@
//
// Created by pmacc on 1/8/2025.
//
#pragma once
#include <Windows.h>
#include "main.hpp"
namespace Nampower {
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState);
uint32_t Script_GetCastInfo(uintptr_t *luaState);
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState);
uint32_t Script_GetNampowerVersion(uintptr_t *luaState);
uint32_t Script_GetItemLevel(uintptr_t *luaState);
uint32_t Script_QueueScript(uintptr_t *luaState);
uint32_t Script_GetItemStats(uintptr_t *luaState);
uint32_t Script_GetItemStatsField(uintptr_t *luaState);
uint32_t Script_GetUnitData(uintptr_t *luaState);
uint32_t Script_GetUnitField(uintptr_t *luaState);
bool RunQueuedScript(int priority);
}
+1
View File
@@ -82,6 +82,7 @@ enum class Offsets : std::uint32_t {
SendCast = 0x6E54F0,
CreateCastbar = 0x6E7A53,
CheckAndReportSpellInhibitFlags = 0x006094f0,
SpellHistories = 0X00CECAEC,
LockedTargetGuid = 0x00B4E2D8,
OnSpriteRightClick = 0x00492820,
@@ -1,20 +1,15 @@
//
// Created by pmacc on 1/8/2025.
// Created by pmacc on 1/15/2025.
//
#include "scripts.hpp"
#include "spell_scripts.hpp"
#include "helper.hpp"
#include "offsets.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
#include "unit_fields.hpp"
#include "helper.hpp"
#include <cstring>
namespace Nampower {
bool gScriptQueued;
int gScriptPriority = 1;
char *queuedScript;
uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
@@ -176,50 +171,6 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
auto const castingSpellId = reinterpret_cast<uint32_t *>(Offsets::CastingSpellId);
lua_pushnumber(luaState, *castingSpellId);
auto const isCasting = gCastData.castEndMs > GetTime();
auto const isChanneling = gCastData.channeling;
auto const visualSpellId = reinterpret_cast<uint32_t *>(Offsets::VisualSpellId);
lua_pushnumber(luaState, *visualSpellId);
auto const autoRepeatingSpellId = reinterpret_cast<uint32_t *>(Offsets::AutoRepeatingSpellId);
lua_pushnumber(luaState, *autoRepeatingSpellId);
auto playerUnit = game::GetObjectPtr(game::ClntObjMgrGetActivePlayerGuid());
if (isCasting) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
if (isChanneling) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
if (gCastData.pendingOnSwingCast) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
auto const attackPtr = playerUnit + 0x312; // auto attacking
if (attackPtr && *reinterpret_cast<uint32_t *>(attackPtr) > 0) {
lua_pushnumber(luaState, 1);
} else {
lua_pushnumber(luaState, 0);
}
return 7;
}
uint32_t Script_GetSpellIdForName(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
@@ -310,165 +261,6 @@ namespace Nampower {
return 0;
}
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (gCastData.channeling) {
DEBUG_LOG("ChannelStopCastingNextTick activated, canceling next tick");
gCastData.cancelChannelNextTick = true;
}
return 0;
}
uint32_t Script_GetNampowerVersion(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
lua_pushnumber(luaState, MAJOR_VERSION);
lua_pushnumber(luaState, MINOR_VERSION);
lua_pushnumber(luaState, PATCH_VERSION);
return 3;
}
uint32_t Script_GetItemLevel(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (lua_isnumber(luaState, 1)) {
auto const itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Pointer to ItemDBCache
void *itemDbCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
// Parameters for the DBCache<>::GetRecord function
int **param2 = nullptr;
int *param3 = nullptr;
int *param4 = nullptr;
char param5 = 0;
// Call the DBCache<>::GetRecord function
auto getRecord = reinterpret_cast<uintptr_t *(__thiscall *)(void *, uint32_t, int **, int *, int *, char)>(
Offsets::DBCacheGetRecord
);
uintptr_t *itemObject = getRecord(itemDbCache, itemId, param2, param3, param4, param5);
if (itemObject == nullptr) {
lua_error(luaState, "Item not found in DBCache");
return 0;
}
uint32_t itemLevel = *reinterpret_cast<uint32_t *>(itemObject + 14);
lua_pushnumber(luaState, itemLevel);
return 1;
} else {
lua_error(luaState, "Usage: GetItemLevel(itemId)");
}
return 0;
}
uint32_t Script_QueueScript(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
DEBUG_LOG("Trying to queue script");
auto const currentTime = GetTime();
auto effectiveCastEndMs = EffectiveCastEndMs();
auto remainingEffectiveCastTime = (effectiveCastEndMs > currentTime) ? effectiveCastEndMs - currentTime : 0;
auto remainingGcd = (gCastData.gcdEndMs > currentTime) ? gCastData.gcdEndMs - currentTime : 0;
auto inSpellQueueWindow = InSpellQueueWindow(remainingEffectiveCastTime, remainingGcd, false);
if (inSpellQueueWindow) {
// check if valid string
if (lua_isstring(luaState, 1)) {
auto script = lua_tostring(luaState, 1);
if (script != nullptr && strlen(script) > 0) {
// save the script to be run later
queuedScript = script;
gScriptQueued = true;
// check if priority is set
if (lua_isnumber(luaState, 2)) {
gScriptPriority = (int) lua_tonumber(luaState, 2);
DEBUG_LOG("Queuing script priority " << gScriptPriority << ": " << script);
} else {
DEBUG_LOG("Queuing script: " << script);
}
}
} else {
DEBUG_LOG("Invalid script");
lua_error(luaState, "Usage: QueueScript(\"script\", (optional)priority)");
}
} else {
// just call regular runscript
auto const runScript = reinterpret_cast<LuaScriptT >(Offsets::Script_RunScript);
return runScript(luaState);
}
return 0;
}
bool RunQueuedScript(int priority) {
if (gScriptQueued && gScriptPriority == priority) {
auto currentTime = GetTime();
auto effectiveCastEndMs = EffectiveCastEndMs();
// get max of cooldown and gcd
auto delay = effectiveCastEndMs > gCastData.gcdEndMs ? effectiveCastEndMs : gCastData.gcdEndMs;
if (delay <= currentTime) {
DEBUG_LOG("Running queued script priority " << gScriptPriority << ": " << queuedScript);
LuaCall(queuedScript);
gScriptQueued = false;
gScriptPriority = 1;
return true;
}
}
return false;
}
uint32_t Script_GetItemStats(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetItemStats(itemId)");
return 0;
}
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Get from cache
game::ItemStats_C *item = GetItemStats(itemId);
if (!item) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, item, itemStatsFields, itemStatsFieldsCount);
// Push string fields manually (require language)
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState, const_cast<char *>("displayName"));
lua_pushstring(luaState,
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
lua_settable(luaState, -3);
lua_pushstring(luaState, const_cast<char *>("description"));
lua_pushstring(luaState, item->m_description ? item->m_description : const_cast<char *>(""));
lua_settable(luaState, -3);
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetSpellRec(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
@@ -494,16 +286,13 @@ namespace Nampower {
// Push string fields manually (require language)
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState, const_cast<char *>("name"));
lua_pushstring(luaState, spell->SpellName[language] ? const_cast<char *>(spell->SpellName[language])
: const_cast<char *>(""));
lua_settable(luaState, -3);
lua_pushstring(luaState, const_cast<char *>("rank"));
lua_pushstring(luaState, reinterpret_cast<const char *>(spell->Rank[language])
? const_cast<char *>(reinterpret_cast<const char *>(spell->Rank[language]))
: const_cast<char *>(""));
lua_settable(luaState, -3);
PushTableValue(luaState, const_cast<char *>("name"),
spell->SpellName[language] ? const_cast<char *>(spell->SpellName[language])
: const_cast<char *>(""));
PushTableValue(luaState, const_cast<char *>("rank"),
reinterpret_cast<const char *>(spell->Rank[language])
? const_cast<char *>(reinterpret_cast<const char *>(spell->Rank[language]))
: const_cast<char *>(""));
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount);
@@ -511,106 +300,6 @@ namespace Nampower {
return 1; // Return the table
}
uint32_t Script_GetItemStatsField(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName)");
return 0;
}
InitializeFieldMaps();
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
const char *fieldName = lua_tostring(luaState, 2);
// Get from cache
game::ItemStats_C *item = GetItemStats(itemId);
if (!item) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = itemStatsFieldMap.find(fieldName);
if (simpleIt != itemStatsFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(item) + itemStatsFields[i].offset;
switch (itemStatsFields[i].type) {
case FieldType::INT32:
lua_pushnumber(luaState, *reinterpret_cast<const int32_t *>(fieldPtr));
return 1;
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
return 1;
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
return 1;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
case FieldType::STRING: {
const char *str = *reinterpret_cast<const char *const *>(fieldPtr);
lua_pushstring(luaState, str ? const_cast<char *>(str) : const_cast<char *>(""));
return 1;
}
default:
break;
}
}
// O(1) hash table lookup for array fields
auto arrayIt = itemStatsArrayFieldMap.find(fieldName);
if (arrayIt != itemStatsArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = itemStatsArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(item) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1);
switch (field.type) {
case FieldType::INT32:
lua_pushnumber(luaState, reinterpret_cast<const int32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT32:
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
break;
default:
lua_pushnumber(luaState, 0);
break;
}
lua_settable(luaState, -3);
}
return 1;
}
// Check for special string fields
if (strcmp(fieldName, "displayName") == 0) {
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState,
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
return 1;
}
if (strcmp(fieldName, "description") == 0) {
lua_pushstring(luaState, item->m_description ? item->m_description : const_cast<char *>(""));
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
uint32_t Script_GetSpellRecField(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
@@ -715,142 +404,6 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetUnitData(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: GetUnitData(unitToken) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
return 0;
}
const char *unitToken = lua_tostring(luaState, 1);
uint64_t guid = GetUnitGuidFromString(unitToken);
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount);
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetUnitField(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: GetUnitField(unitToken, fieldName) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
return 0;
}
InitializeUnitFieldMaps();
const char *unitToken = lua_tostring(luaState, 1);
const char *fieldName = lua_tostring(luaState, 2);
uint64_t guid = GetUnitGuidFromString(unitToken);
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = unitFieldsFieldMap.find(fieldName);
if (simpleIt != unitFieldsFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + unitFieldsFields[i].offset;
switch (unitFieldsFields[i].type) {
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
return 1;
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
return 1;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(fieldPtr)));
return 1;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
default:
break;
}
}
// O(1) hash table lookup for array fields
auto arrayIt = unitFieldsArrayFieldMap.find(fieldName);
if (arrayIt != unitFieldsArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = unitFieldsArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1);
switch (field.type) {
case FieldType::UINT32:
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
break;
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
break;
default:
lua_pushnumber(luaState, 0);
break;
}
lua_settable(luaState, -3);
}
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
uint32_t Script_GetSpellModifiers(uintptr_t *luaState) {
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
@@ -969,4 +522,5 @@ namespace Nampower {
return 0;
}
}
@@ -1,5 +1,5 @@
//
// Created by pmacc on 1/8/2025.
// Created by pmacc on 1/15/2025.
//
#pragma once
@@ -18,37 +18,17 @@ namespace Nampower {
uint32_t Script_SpellStopCastingHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState);
uint32_t Script_GetSpellIdForName(uintptr_t *luaState);
uint32_t Script_GetSpellNameAndRankForId(uintptr_t *luaState);
uint32_t Script_GetSpellSlotTypeIdForName(uintptr_t *luaState);
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState);
uint32_t Script_GetNampowerVersion(uintptr_t *luaState);
uint32_t Script_GetItemLevel(uintptr_t *luaState);
uint32_t Script_QueueScript(uintptr_t *luaState);
uint32_t Script_GetItemStats(uintptr_t *luaState);
uint32_t Script_GetSpellRec(uintptr_t *luaState);
uint32_t Script_GetItemStatsField(uintptr_t *luaState);
uint32_t Script_GetSpellRecField(uintptr_t *luaState);
uint32_t Script_GetUnitData(uintptr_t *luaState);
uint32_t Script_GetUnitField(uintptr_t *luaState);
uint32_t Script_GetSpellModifiers(uintptr_t *luaState);
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type);
bool RunQueuedScript(int priority);
}
}
+3 -1
View File
@@ -222,6 +222,7 @@ namespace Nampower {
}
gCastData.castEndMs = castTime ? currentTime + castTime + bufferMs : 0;
gCastData.castSpellId = castTime ? spell->Id : 0;
gCastData.bufferMs = bufferMs;
// check if we can lower buffers
@@ -310,7 +311,7 @@ namespace Nampower {
params->item = item;
params->guid = guid;
params->gcDCategory = gcDCategory;
params->gcdCategory = gcDCategory;
params->castTimeMs = castTimeMs;
params->castStartTimeMs = castStartTimeMs;
params->castType = castType;
@@ -740,6 +741,7 @@ namespace Nampower {
return false;
} else {
gCastData.castEndMs = 0;
gCastData.castSpellId = 0;
}
// is there a Gcd?
+3 -1
View File
@@ -79,7 +79,7 @@ struct CastSpellParams {
/* *********************** */
/* Additional data */
uint32_t gcDCategory; // comes from spell->StartRecoveryCategory
uint32_t gcdCategory; // comes from spell->StartRecoveryCategory
uint32_t castTimeMs; // spell's cast time in ms
uint32_t castStartTimeMs; // event time in ms
CastType castType;
@@ -110,6 +110,8 @@ struct CastData {
uint32_t bufferMs;
uint32_t castSpellId; // spell id for the active cast (when castEndMs is active)
bool onSwingQueued;
bool pendingOnSwingCast;
uint32_t onSwingSpellId;