Merge pull request 'add more access to spell/item/unit data' (#1) from beta into master

Reviewed-on: https://gitea.com/avitasia/nampower/pulls/1
This commit is contained in:
avitasia
2025-12-03 15:50:32 +00:00
22 changed files with 2261 additions and 150 deletions
+144
View File
@@ -0,0 +1,144 @@
# DBC Field Names
Related structs live in `nampower/game.hpp` (notably `SpellRec` and `ItemStats_C`).
## ItemStats_C Fields (alphabetical)
- `allowableClass`
- `allowableRace`
- `ammoType`
- `area`
- `bagFamily`
- `block`
- `bonding`
- `buyPrice`
- `class`
- `containerSlots`
- `delay`
- `displayInfoID`
- `duration`
- `flags`
- `inventoryType`
- `itemLevel`
- `itemSet`
- `languageID`
- `lockID`
- `map`
- `material`
- `maxCount`
- `maxDurability`
- `pageMaterial`
- `pageText`
- `quality`
- `randomProperty`
- `rangedModRange`
- `requiredCityRank`
- `requiredHonorRank`
- `requiredLevel`
- `requiredRep`
- `requiredRepRank`
- `requiredSkill`
- `requiredSkillRank`
- `requiredSpell`
- `sellPrice`
- `sheatheType`
- `stackable`
- `startQuestID`
- `subclass`
### ItemStats_C Array Fields (alphabetical)
- `bonusAmount`
- `bonusStat`
- `damageType`
- `maxDamage`
- `minDamage`
- `resistances`
- `spellCategory`
- `spellCategoryCooldown`
- `spellCharges`
- `spellCooldown`
- `spellID`
- `spellTrigger`
## SpellRec Fields (alphabetical)
- `activeIconID`
- `attributes`
- `attributesEx`
- `attributesEx2`
- `attributesEx3`
- `attributesEx4`
- `auraInterruptFlags`
- `baseLevel`
- `castUI`
- `category`
- `categoryRecoveryTime`
- `casterAuraState`
- `castingTimeIndex`
- `channelInterruptFlags`
- `dispel`
- `dmgClass`
- `durationIndex`
- `equippedItemClass`
- `equippedItemInventoryTypeMask`
- `equippedItemSubClassMask`
- `id`
- `interruptFlags`
- `manaCost`
- `manaCostPercentage`
- `manaCostPerlevel`
- `manaPerSecond`
- `manaPerSecondPerLevel`
- `maxAffectedTargets`
- `maxLevel`
- `maxTargetLevel`
- `mechanic`
- `minFactionId`
- `minReputation`
- `modalNextSpell`
- `powerType`
- `preventionType`
- `procChance`
- `procCharges`
- `procFlags`
- `rangeIndex`
- `recoveryTime`
- `requiredAuraVision`
- `school`
- `spellFamilyFlags`
- `spellFamilyName`
- `spellIconID`
- `spellLevel`
- `spellPriority`
- `spellVisual`
- `spellVisual2`
- `stackAmount`
- `stances`
- `stancesNot`
- `startRecoveryCategory`
- `startRecoveryTime`
- `targetAuraState`
- `targetCreatureType`
- `targets`
### SpellRec Array Fields (alphabetical)
- `dmgMultiplier`
- `effect`
- `effectAmplitude`
- `effectApplyAuraName`
- `effectBaseDice`
- `effectBasePoints`
- `effectChainTarget`
- `effectDicePerLevel`
- `effectDieSides`
- `effectImplicitTargetA`
- `effectImplicitTargetB`
- `effectItemType`
- `effectMechanic`
- `effectMiscValue`
- `effectMultipleValue`
- `effectPointsPerComboPoint`
- `effectRadiusIndex`
- `effectRealPointsPerLevel`
- `effectTriggerSpell`
- `reagent`
- `reagentCount`
- `totem`
+198
View File
@@ -98,8 +98,206 @@ 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
#### 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.
See examples below for differences in how BOOKTYPE works.
Functions: `GetSpellTexture`, `GetSpellName`, `GetSpellCooldown`, `GetSpellAutocast`, `ToggleSpellAutocast`, `PickupSpell`, `CastSpell`, `IsCurrentCast`, `IsSpellPassive`.
Examples:
```
/run print(GetSpellTexture(1, "spell")) -- booktype required
/run print(GetSpellTexture("spellId:25978")) -- defaults to BOOKTYPE_SPELL
/run print(GetSpellTexture("spellId:6268", "pet")) -- "pet" needed for pet spells
/run print(GetSpellTexture("Fireball")) -- name search
```
### 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
```
#### 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
+266
View File
@@ -0,0 +1,266 @@
# Unit Fields Reference
This document lists all available unit fields that can be accessed via `GetUnitData()` and `GetUnitField()` functions.
## Overview
Unit fields provide low-level access to game data for any unit (player, target, pet, NPCs, etc.). These fields are part of the WoW 1.12 client's internal data structures.
## Usage
```lua
-- Get all fields at once
local data = GetUnitData("target")
if data then
print("Health: " .. data.health)
end
-- Get a single field (more efficient)
local health = GetUnitField("target", "health")
```
## Simple Fields
These fields return a single value (number).
### Object References (UINT64)
These fields contain GUIDs (globally unique identifiers) for game objects:
- **charm** - GUID of the unit that has charmed this unit
- **summon** - GUID of the unit's current summon
- **charmedBy** - GUID of the unit that charmed this unit
- **summonedBy** - GUID of the unit that summoned this unit
- **createdBy** - GUID of the unit that created this unit
- **target** - GUID of the unit's current target
- **persuaded** - GUID of the unit being persuaded
- **channelObject** - GUID of the object being channeled (for channeling spells)
### Health and Power (UINT32)
- **health** - Current health points
- **power1** - Current mana (or energy/rage depending on class)
- **power2** - Current rage
- **power3** - Current focus (hunter pets)
- **power4** - Current energy (rogues, druids in cat/bear form)
- **power5** - Current happiness (hunter pets)
- **maxHealth** - Maximum health points
- **maxPower1** - Maximum mana
- **maxPower2** - Maximum rage
- **maxPower3** - Maximum focus
- **maxPower4** - Maximum energy
- **maxPower5** - Maximum happiness
### Basic Unit Info (UINT32)
- **level** - Unit's level
- **factionTemplate** - Unit's faction template ID
- **flags** - Unit flags (combat, dead, mounted, etc.)
- **dynamicFlags** - Dynamic unit flags (lootable, tapped, etc.)
- **auraState** - Aura state flags (reactive abilities like Revenge, Overpower)
- **npcFlags** - NPC flags (vendor, trainer, quest giver, etc.)
- **npcEmoteState** - Current emote state ID
### Display and Appearance (UINT32)
- **displayId** - Current display model ID
- **nativeDisplayId** - Original/native display model ID
- **mountDisplayId** - Mount display model ID (if mounted)
### Combat Stats (UINT32)
- **baseAttackTime** - Base main-hand attack time in milliseconds
- **offhandAttackTime** - Off-hand attack time in milliseconds
- **rangedAttackTime** - Ranged attack time in milliseconds
### Combat Stats (FLOAT)
- **boundingRadius** - Unit's bounding radius for collision
- **combatReach** - Unit's combat reach distance
- **minDamage** - Minimum main-hand damage
- **maxDamage** - Maximum main-hand damage
- **minOffhandDamage** - Minimum off-hand damage
- **maxOffhandDamage** - Maximum off-hand damage
- **minRangedDamage** - Minimum ranged damage
- **maxRangedDamage** - Maximum ranged damage
### Pet Info (UINT32)
- **petNumber** - Pet's number identifier
- **petNameTimestamp** - Timestamp of pet name
- **petExperience** - Pet's current experience
- **petNextLevelExp** - Experience needed for pet's next level
### Spell/Channel Info (UINT32)
- **channelSpell** - Spell ID of currently channeling spell
- **createdBySpell** - Spell ID that created this unit (for summons)
### Casting Speed (FLOAT)
- **modCastSpeed** - Cast speed modifier (1.0 = normal, <1.0 = faster, >1.0 = slower)
### Training (UINT32)
- **trainingPoints** - Available training points (for pets and talents)
### Base Stats (UINT32)
- **stat0** - Strength
- **stat1** - Agility
- **stat2** - Stamina
- **stat3** - Intellect
- **stat4** - Spirit
- **baseMana** - Base mana before modifiers
- **baseHealth** - Base health before modifiers
### Attack Power (UINT32)
- **attackPower** - Total melee attack power
- **attackPowerMods** - Attack power modifiers
- **rangedAttackPower** - Total ranged attack power
- **rangedAttackPowerMods** - Ranged attack power modifiers
### Attack Power Multipliers (FLOAT)
- **attackPowerMultiplier** - Melee attack power multiplier
- **rangedAttackPowerMultiplier** - Ranged attack power multiplier
### Packed Byte Fields (UINT32)
These are special fields that pack multiple values into a single 32-bit integer:
- **bytes0** - Contains race, class, gender, and power type
- Byte 0: Race
- Byte 1: Class
- Byte 2: Gender
- Byte 3: Power type
- **bytes1** - Contains stand state, pet talent points, vis flags, and anim tier
- Byte 0: Stand state
- Byte 1: Pet talent points
- Byte 2: Vis flags
- Byte 3: Anim tier
- **bytes2** - Contains sheath state, pvp flags, pet flags, and shape shift form
- Byte 0: Sheath state
- Byte 1: PvP flags
- Byte 2: Pet flags
- Byte 3: Shape shift form
## Array Fields
These fields return a Lua table with numeric indices (1-based).
### Visual Items (UINT32 arrays)
- **virtualItemDisplay** [3] - Display IDs for virtual items (weapon visuals, etc.)
- **virtualItemInfo** [6] - Additional virtual item information
### Auras/Buffs/Debuffs (UINT32 arrays)
- **aura** [48] - Array of aura (buff/debuff) spell IDs on the unit
- Index 1-48 contains spell IDs (0 = no aura in that slot)
- **auraFlags** [6] - Bit flags for aura properties (positive, negative, cancelable, etc.)
- Each UINT32 contains flags for 8 auras (4 bits per aura)
- **auraLevels** [12] - Aura caster levels
- Each UINT32 contains 4 aura levels (8 bits per level)
- **auraApplications** [12] - Aura stack counts
- Each UINT32 contains 4 aura stacks (8 bits per stack)
### Resistances (UINT32 arrays)
- **resistances** [7] - Resistance values for all schools
- Index 1: Armor (physical resistance)
- Index 2: Holy resistance
- Index 3: Fire resistance
- Index 4: Nature resistance
- Index 5: Frost resistance
- Index 6: Shadow resistance
- Index 7: Arcane resistance
### Spell Power Modifiers (FLOAT arrays)
- **powerCostModifier** [7] - Flat mana cost modifications per school
- Index 1-7: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
- **powerCostMultiplier** [7] - Percent mana cost modifications per school
- Index 1-7: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
## Examples
### Health Monitoring
```lua
-- Monitor target health percentage
local health = GetUnitField("target", "health")
local maxHealth = GetUnitField("target", "maxHealth")
local healthPct = (health / maxHealth) * 100
print("Target health: " .. healthPct .. "%")
```
### Checking Auras
```lua
-- Check if target has a specific debuff
local auras = GetUnitField("target", "aura")
local CURSE_OF_AGONY = 980
for i, spellId in ipairs(auras) do
if spellId == CURSE_OF_AGONY then
print("Target has Curse of Agony!")
break
end
end
```
### Reading Resistances
```lua
-- Get all player resistances
local resistances = GetUnitField("player", "resistances")
print("Armor: " .. resistances[1])
print("Holy resistance: " .. resistances[2])
print("Fire resistance: " .. resistances[3])
print("Nature resistance: " .. resistances[4])
print("Frost resistance: " .. resistances[5])
print("Shadow resistance: " .. resistances[6])
print("Arcane resistance: " .. resistances[7])
```
### Checking Combat State
```lua
-- Check if unit is in combat (flags field bit check)
local flags = GetUnitField("target", "flags")
local UNIT_FLAG_IN_COMBAT = 0x00080000
local inCombat = bit.band(flags, UNIT_FLAG_IN_COMBAT) ~= 0
print("In combat: " .. tostring(inCombat))
```
### Monitoring Cast Speed
```lua
-- Check your current cast speed modifier
local modCastSpeed = GetUnitField("player", "modCastSpeed")
-- 1.0 = normal, 0.9 = 10% faster, 1.1 = 10% slower
print("Cast speed modifier: " .. modCastSpeed)
```
### Attack Power Info
```lua
-- Get your attack power
local ap = GetUnitField("player", "attackPower")
local apMods = GetUnitField("player", "attackPowerMods")
local apMult = GetUnitField("player", "attackPowerMultiplier")
print("Base AP: " .. ap)
print("AP Mods: " .. apMods)
print("AP Multiplier: " .. apMult)
```
### Pet Information
```lua
-- Check pet experience
if UnitExists("pet") then
local petXP = GetUnitField("pet", "petExperience")
local petNextXP = GetUnitField("pet", "petNextLevelExp")
local progress = (petXP / petNextXP) * 100
print("Pet XP: " .. progress .. "%")
end
```
## Notes
- GUID fields return numbers but represent 64-bit values. Lua handles these as doubles.
- Array indices are 1-based (Lua convention), not 0-based.
- Returns `nil` if the unit doesn't exist or the field cannot be read.
- Some fields may require specific game state to be meaningful (e.g., pet fields only work when you have a pet).
- Unit flags and other bit fields require bitwise operations to interpret properly.
## Related Functions
- `GetUnitData(unitToken)` - Get all fields at once
- `GetUnitField(unitToken, fieldName)` - Get a single field efficiently
- `GetSpellModifiers(spellId, modifierType)` - Get spell modifiers
- `GetItemStats(itemId)` - Get item data
- `GetSpellRec(spellId)` - Get spell data
+6
View File
@@ -24,6 +24,12 @@ set(SOURCE_FILES
spellevents.cpp
scripts.hpp
scripts.cpp
items.hpp
items.cpp
dbc_fields.hpp
dbc_fields.cpp
unit_fields.hpp
unit_fields.cpp
)
add_library(${DLL_NAME} SHARED ${SOURCE_FILES})
+189
View File
@@ -0,0 +1,189 @@
//
// Created by pmacc on 1/8/2025.
//
#include "dbc_fields.hpp"
#include "offsets.hpp"
namespace Nampower {
// Hash tables for O(1) field lookups
std::unordered_map<std::string, size_t> itemStatsFieldMap;
std::unordered_map<std::string, size_t> itemStatsArrayFieldMap;
std::unordered_map<std::string, size_t> spellRecFieldMap;
std::unordered_map<std::string, size_t> spellRecArrayFieldMap;
static bool fieldMapsInitialized = false;
// ItemStats_C field descriptors
const FieldDescriptor itemStatsFields[] = {
{"class", offsetof(game::ItemStats_C, m_class), FieldType::INT32},
{"subclass", offsetof(game::ItemStats_C, m_subclass), FieldType::INT32},
{"displayInfoID", offsetof(game::ItemStats_C, m_displayInfoID), FieldType::INT32},
{"quality", offsetof(game::ItemStats_C, m_quality), FieldType::INT32},
{"flags", offsetof(game::ItemStats_C, m_flags), FieldType::INT32},
{"buyPrice", offsetof(game::ItemStats_C, m_buyPrice), FieldType::INT32},
{"sellPrice", offsetof(game::ItemStats_C, m_sellPrice), FieldType::INT32},
{"inventoryType", offsetof(game::ItemStats_C, m_inventoryType), FieldType::INT32},
{"allowableClass", offsetof(game::ItemStats_C, m_allowableClass), FieldType::INT32},
{"allowableRace", offsetof(game::ItemStats_C, m_allowableRace), FieldType::INT32},
{"itemLevel", offsetof(game::ItemStats_C, m_itemLevel), FieldType::INT32},
{"requiredLevel", offsetof(game::ItemStats_C, m_requiredLevel), FieldType::INT32},
{"requiredSkill", offsetof(game::ItemStats_C, m_requiredSkill), FieldType::INT32},
{"requiredSkillRank", offsetof(game::ItemStats_C, m_requiredSkillRank), FieldType::INT32},
{"requiredSpell", offsetof(game::ItemStats_C, m_requiredSpell), FieldType::INT32},
{"requiredHonorRank", offsetof(game::ItemStats_C, m_requiredHonorRank), FieldType::INT32},
{"requiredCityRank", offsetof(game::ItemStats_C, m_requiredCityRank), FieldType::INT32},
{"requiredRep", offsetof(game::ItemStats_C, m_requiredRep), FieldType::INT32},
{"requiredRepRank", offsetof(game::ItemStats_C, m_requiredRepRank), FieldType::INT32},
{"maxCount", offsetof(game::ItemStats_C, m_maxCount), FieldType::INT32},
{"stackable", offsetof(game::ItemStats_C, m_stackable), FieldType::INT32},
{"containerSlots", offsetof(game::ItemStats_C, m_containerSlots), FieldType::INT32},
{"delay", offsetof(game::ItemStats_C, m_delay), FieldType::INT32},
{"ammoType", offsetof(game::ItemStats_C, m_ammoType), FieldType::INT32},
{"rangedModRange", offsetof(game::ItemStats_C, m_rangedModRange), FieldType::INT32},
{"bonding", offsetof(game::ItemStats_C, m_bonding), FieldType::INT32},
{"pageText", offsetof(game::ItemStats_C, m_pageText), FieldType::INT32},
{"languageID", offsetof(game::ItemStats_C, m_languageID), FieldType::INT32},
{"pageMaterial", offsetof(game::ItemStats_C, m_pageMaterial), FieldType::INT32},
{"startQuestID", offsetof(game::ItemStats_C, m_startQuestID), FieldType::INT32},
{"lockID", offsetof(game::ItemStats_C, m_lockID), FieldType::INT32},
{"material", offsetof(game::ItemStats_C, m_material), FieldType::INT32},
{"sheatheType", offsetof(game::ItemStats_C, m_sheatheType), FieldType::INT32},
{"randomProperty", offsetof(game::ItemStats_C, m_randomProperty), FieldType::INT32},
{"block", offsetof(game::ItemStats_C, m_block), FieldType::INT32},
{"itemSet", offsetof(game::ItemStats_C, m_itemSet), FieldType::INT32},
{"maxDurability", offsetof(game::ItemStats_C, m_maxDurability), FieldType::INT32},
{"area", offsetof(game::ItemStats_C, m_area), FieldType::INT32},
{"map", offsetof(game::ItemStats_C, m_map), FieldType::INT32},
{"duration", offsetof(game::ItemStats_C, m_duration), FieldType::INT32},
{"bagFamily", offsetof(game::ItemStats_C, m_bagFamily), FieldType::INT32},
};
const size_t itemStatsFieldsCount = sizeof(itemStatsFields) / sizeof(itemStatsFields[0]);
const ArrayFieldDescriptor itemStatsArrayFields[] = {
{"bonusStat", offsetof(game::ItemStats_C, m_bonusStat), FieldType::INT32, 10},
{"bonusAmount", offsetof(game::ItemStats_C, m_bonusAmount), FieldType::INT32, 10},
{"minDamage", offsetof(game::ItemStats_C, m_minDamage), FieldType::FLOAT, 5},
{"maxDamage", offsetof(game::ItemStats_C, m_maxDamage), FieldType::FLOAT, 5},
{"damageType", offsetof(game::ItemStats_C, m_damageType), FieldType::INT32, 5},
{"resistances", offsetof(game::ItemStats_C, m_resistances), FieldType::INT32, 7},
{"spellID", offsetof(game::ItemStats_C, m_spellID), FieldType::INT32, 5},
{"spellTrigger", offsetof(game::ItemStats_C, m_spellTrigger), FieldType::INT32, 5},
{"spellCharges", offsetof(game::ItemStats_C, m_spellCharges), FieldType::INT32, 5},
{"spellCooldown", offsetof(game::ItemStats_C, m_spellCooldown), FieldType::INT32, 5},
{"spellCategory", offsetof(game::ItemStats_C, m_spellCategory), FieldType::INT32, 5},
{"spellCategoryCooldown", offsetof(game::ItemStats_C, m_spellCategoryCooldown), FieldType::INT32, 5},
};
const size_t itemStatsArrayFieldsCount = sizeof(itemStatsArrayFields) / sizeof(itemStatsArrayFields[0]);
// SpellRec field descriptors
const FieldDescriptor spellRecFields[] = {
{"id", offsetof(game::SpellRec, Id), FieldType::UINT32},
{"school", offsetof(game::SpellRec, School), FieldType::UINT32},
{"category", offsetof(game::SpellRec, Category), FieldType::UINT32},
{"castUI", offsetof(game::SpellRec, castUI), FieldType::UINT32},
{"dispel", offsetof(game::SpellRec, Dispel), FieldType::UINT32},
{"mechanic", offsetof(game::SpellRec, Mechanic), FieldType::UINT32},
{"attributes", offsetof(game::SpellRec, Attributes), FieldType::UINT32},
{"attributesEx", offsetof(game::SpellRec, AttributesEx), FieldType::UINT32},
{"attributesEx2", offsetof(game::SpellRec, AttributesEx2), FieldType::UINT32},
{"attributesEx3", offsetof(game::SpellRec, AttributesEx3), FieldType::UINT32},
{"attributesEx4", offsetof(game::SpellRec, AttributesEx4), FieldType::UINT32},
{"stances", offsetof(game::SpellRec, Stances), FieldType::UINT32},
{"stancesNot", offsetof(game::SpellRec, StancesNot), FieldType::UINT32},
{"targets", offsetof(game::SpellRec, Targets), FieldType::UINT32},
{"targetCreatureType", offsetof(game::SpellRec, TargetCreatureType), FieldType::UINT32},
{"requiresSpellFocus", offsetof(game::SpellRec, RequiresSpellFocus), FieldType::UINT32},
{"casterAuraState", offsetof(game::SpellRec, CasterAuraState), FieldType::UINT32},
{"targetAuraState", offsetof(game::SpellRec, TargetAuraState), FieldType::UINT32},
{"castingTimeIndex", offsetof(game::SpellRec, CastingTimeIndex), FieldType::UINT32},
{"recoveryTime", offsetof(game::SpellRec, RecoveryTime), FieldType::UINT32},
{"categoryRecoveryTime", offsetof(game::SpellRec, CategoryRecoveryTime), FieldType::UINT32},
{"interruptFlags", offsetof(game::SpellRec, InterruptFlags), FieldType::UINT32},
{"auraInterruptFlags", offsetof(game::SpellRec, AuraInterruptFlags), FieldType::UINT32},
{"channelInterruptFlags", offsetof(game::SpellRec, ChannelInterruptFlags), FieldType::UINT32},
{"procFlags", offsetof(game::SpellRec, procFlags), FieldType::UINT32},
{"procChance", offsetof(game::SpellRec, procChance), FieldType::UINT32},
{"procCharges", offsetof(game::SpellRec, procCharges), FieldType::UINT32},
{"maxLevel", offsetof(game::SpellRec, maxLevel), FieldType::UINT32},
{"baseLevel", offsetof(game::SpellRec, baseLevel), FieldType::UINT32},
{"spellLevel", offsetof(game::SpellRec, spellLevel), FieldType::UINT32},
{"durationIndex", offsetof(game::SpellRec, DurationIndex), FieldType::UINT32},
{"powerType", offsetof(game::SpellRec, powerType), FieldType::UINT32},
{"manaCost", offsetof(game::SpellRec, manaCost), FieldType::UINT32},
{"manaCostPerlevel", offsetof(game::SpellRec, manaCostPerlevel), FieldType::UINT32},
{"manaPerSecond", offsetof(game::SpellRec, manaPerSecond), FieldType::UINT32},
{"manaPerSecondPerLevel", offsetof(game::SpellRec, manaPerSecondPerLevel), FieldType::UINT32},
{"rangeIndex", offsetof(game::SpellRec, rangeIndex), FieldType::UINT32},
{"speed", offsetof(game::SpellRec, speed), FieldType::FLOAT},
{"modalNextSpell", offsetof(game::SpellRec, modalNextSpell), FieldType::UINT32},
{"stackAmount", offsetof(game::SpellRec, StackAmount), FieldType::UINT32},
{"equippedItemClass", offsetof(game::SpellRec, EquippedItemClass), FieldType::INT32},
{"equippedItemSubClassMask", offsetof(game::SpellRec, EquippedItemSubClassMask), FieldType::INT32},
{"equippedItemInventoryTypeMask", offsetof(game::SpellRec, EquippedItemInventoryTypeMask), FieldType::INT32},
{"spellVisual", offsetof(game::SpellRec, SpellVisual), FieldType::UINT32},
{"spellVisual2", offsetof(game::SpellRec, SpellVisual2), FieldType::UINT32},
{"spellIconID", offsetof(game::SpellRec, SpellIconID), FieldType::UINT32},
{"activeIconID", offsetof(game::SpellRec, activeIconID), FieldType::UINT32},
{"spellPriority", offsetof(game::SpellRec, spellPriority), FieldType::UINT32},
{"manaCostPercentage", offsetof(game::SpellRec, ManaCostPercentage), FieldType::UINT32},
{"startRecoveryCategory", offsetof(game::SpellRec, StartRecoveryCategory), FieldType::UINT32},
{"startRecoveryTime", offsetof(game::SpellRec, StartRecoveryTime), FieldType::UINT32},
{"maxTargetLevel", offsetof(game::SpellRec, MaxTargetLevel), FieldType::UINT32},
{"spellFamilyName", offsetof(game::SpellRec, SpellFamilyName), FieldType::UINT32},
{"spellFamilyFlags", offsetof(game::SpellRec, SpellFamilyFlags), FieldType::UINT64},
{"maxAffectedTargets", offsetof(game::SpellRec, MaxAffectedTargets), FieldType::UINT32},
{"dmgClass", offsetof(game::SpellRec, DmgClass), FieldType::UINT32},
{"preventionType", offsetof(game::SpellRec, PreventionType), FieldType::UINT32},
{"stanceBarOrder", offsetof(game::SpellRec, StanceBarOrder), FieldType::UINT32},
{"minFactionId", offsetof(game::SpellRec, MinFactionId), FieldType::UINT32},
{"minReputation", offsetof(game::SpellRec, MinReputation), FieldType::UINT32},
{"requiredAuraVision", offsetof(game::SpellRec, RequiredAuraVision), FieldType::UINT32},
};
const size_t spellRecFieldsCount = sizeof(spellRecFields) / sizeof(spellRecFields[0]);
const ArrayFieldDescriptor spellRecArrayFields[] = {
{"totem", offsetof(game::SpellRec, Totem), FieldType::UINT32, 2},
{"reagent", offsetof(game::SpellRec, Reagent), FieldType::INT32, 8},
{"reagentCount", offsetof(game::SpellRec, ReagentCount), FieldType::UINT32, 8},
{"effect", offsetof(game::SpellRec, Effect), FieldType::UINT32, 3},
{"effectDieSides", offsetof(game::SpellRec, EffectDieSides), FieldType::INT32, 3},
{"effectBaseDice", offsetof(game::SpellRec, EffectBaseDice), FieldType::UINT32, 3},
{"effectDicePerLevel", offsetof(game::SpellRec, EffectDicePerLevel), FieldType::FLOAT, 3},
{"effectRealPointsPerLevel", offsetof(game::SpellRec, EffectRealPointsPerLevel), FieldType::FLOAT, 3},
{"effectBasePoints", offsetof(game::SpellRec, EffectBasePoints), FieldType::INT32, 3},
{"effectMechanic", offsetof(game::SpellRec, EffectMechanic), FieldType::UINT32, 3},
{"effectImplicitTargetA", offsetof(game::SpellRec, EffectImplicitTargetA), FieldType::UINT32, 3},
{"effectImplicitTargetB", offsetof(game::SpellRec, EffectImplicitTargetB), FieldType::UINT32, 3},
{"effectRadiusIndex", offsetof(game::SpellRec, EffectRadiusIndex), FieldType::UINT32, 3},
{"effectApplyAuraName", offsetof(game::SpellRec, EffectApplyAuraName), FieldType::UINT32, 3},
{"effectAmplitude", offsetof(game::SpellRec, EffectAmplitude), FieldType::UINT32, 3},
{"effectMultipleValue", offsetof(game::SpellRec, EffectMultipleValue), FieldType::FLOAT, 3},
{"effectChainTarget", offsetof(game::SpellRec, EffectChainTarget), FieldType::UINT32, 3},
{"effectItemType", offsetof(game::SpellRec, EffectItemType), FieldType::UINT32, 3},
{"effectMiscValue", offsetof(game::SpellRec, EffectMiscValue), FieldType::INT32, 3},
{"effectTriggerSpell", offsetof(game::SpellRec, EffectTriggerSpell), FieldType::UINT32, 3},
{"effectPointsPerComboPoint", offsetof(game::SpellRec, EffectPointsPerComboPoint), FieldType::FLOAT, 3},
{"dmgMultiplier", offsetof(game::SpellRec, DmgMultiplier), FieldType::FLOAT, 3},
};
const size_t spellRecArrayFieldsCount = sizeof(spellRecArrayFields) / sizeof(spellRecArrayFields[0]);
void InitializeFieldMaps() {
if (fieldMapsInitialized) return;
// Build hash maps for O(1) lookups
for (size_t i = 0; i < itemStatsFieldsCount; ++i) {
itemStatsFieldMap[itemStatsFields[i].name] = i;
}
for (size_t i = 0; i < itemStatsArrayFieldsCount; ++i) {
itemStatsArrayFieldMap[itemStatsArrayFields[i].name] = i;
}
for (size_t i = 0; i < spellRecFieldsCount; ++i) {
spellRecFieldMap[spellRecFields[i].name] = i;
}
for (size_t i = 0; i < spellRecArrayFieldsCount; ++i) {
spellRecArrayFieldMap[spellRecArrayFields[i].name] = i;
}
fieldMapsInitialized = true;
}
}
+137
View File
@@ -0,0 +1,137 @@
//
// Created by pmacc on 1/8/2025.
//
#pragma once
#include "main.hpp"
#include <unordered_map>
#include <string>
#include <cstddef>
namespace Nampower {
// Field type enumeration
enum class FieldType {
INT32,
UINT32,
FLOAT,
STRING,
UINT64
};
// Field descriptor for simple fields
struct FieldDescriptor {
const char* name;
size_t offset;
FieldType type;
};
// Field descriptor for array fields
struct ArrayFieldDescriptor {
const char* name;
size_t offset;
FieldType type;
size_t count;
};
// ItemStats_C field descriptors
extern const FieldDescriptor itemStatsFields[];
extern const size_t itemStatsFieldsCount;
extern const ArrayFieldDescriptor itemStatsArrayFields[];
extern const size_t itemStatsArrayFieldsCount;
// SpellRec field descriptors
extern const FieldDescriptor spellRecFields[];
extern const size_t spellRecFieldsCount;
extern const ArrayFieldDescriptor spellRecArrayFields[];
extern const size_t spellRecArrayFieldsCount;
// Hash tables for O(1) field lookups
extern std::unordered_map<std::string, size_t> itemStatsFieldMap;
extern std::unordered_map<std::string, size_t> itemStatsArrayFieldMap;
extern std::unordered_map<std::string, size_t> spellRecFieldMap;
extern std::unordered_map<std::string, size_t> spellRecArrayFieldMap;
// Initialize field maps (call once before using field lookups)
void InitializeFieldMaps();
// Forward declarations for Lua functions (defined in offsets.hpp/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 *);
using lua_settableT = void (__fastcall *)(uintptr_t *, int);
extern lua_pushnumberT lua_pushnumber;
extern lua_pushstringT lua_pushstring;
extern lua_newtableT lua_newtable;
extern lua_settableT lua_settable;
// Helper template functions to push fields to Lua (inline for performance)
template<typename T>
inline void PushFieldsToLua(uintptr_t* luaState, const T* obj, const FieldDescriptor* fields, size_t fieldCount) {
for (size_t i = 0; i < fieldCount; ++i) {
const auto& field = fields[i];
lua_pushstring(luaState, const_cast<char*>(field.name));
const char* fieldPtr = reinterpret_cast<const char*>(obj) + field.offset;
switch (field.type) {
case FieldType::INT32:
lua_pushnumber(luaState, *reinterpret_cast<const int32_t*>(fieldPtr));
break;
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t*>(fieldPtr));
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float*>(fieldPtr));
break;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t*>(fieldPtr)));
break;
case FieldType::STRING: {
const char* str = *reinterpret_cast<const char* const*>(fieldPtr);
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
}
lua_settable(luaState, -3);
}
}
template<typename T>
inline void PushArrayFieldsToLua(uintptr_t* luaState, const T* obj, const ArrayFieldDescriptor* fields, size_t fieldCount) {
for (size_t i = 0; i < fieldCount; ++i) {
const auto& field = fields[i];
lua_pushstring(luaState, const_cast<char*>(field.name));
lua_newtable(luaState);
const char* fieldPtr = reinterpret_cast<const char*>(obj) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1); // Lua is 1-indexed
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::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float*>(fieldPtr)[j]);
break;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(reinterpret_cast<const uint64_t*>(fieldPtr)[j]));
break;
case FieldType::STRING: {
const char* str = reinterpret_cast<const char* const*>(fieldPtr)[j];
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
}
lua_settable(luaState, -3);
}
lua_settable(luaState, -3);
}
}
}
+1 -1
View File
@@ -85,7 +85,7 @@ namespace game {
int GetSpellModifier(const SpellRec *spellRec, SpellModOp spellMod) {
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const SpellRec *spellRec, int *returnVal, SpellModOp modOp);
auto const getModifiers = reinterpret_cast<Spell_C_GetSpellModifiersT>(Offsets::Spell_C_GetSpellModifiers);
auto const getModifiers = reinterpret_cast<Spell_C_GetSpellModifiersT>(Offsets::Spell_C_ApplySpellModifiers);
auto modificationPercentage = 0;
getModifiers(spellRec, &modificationPercentage, spellMod);
+3 -2
View File
@@ -30,6 +30,7 @@
#pragma once
#include <cstdint>
#include <string>
namespace game {
#pragma pack(push, 1)
@@ -209,7 +210,7 @@ namespace game {
int m_containerSlots;
int m_bonusStat[10];
int m_bonusAmount[10];
int m_minDamage[5];
float m_minDamage[5];
float m_maxDamage[5];
int m_damageType[5];
int m_resistances[7];
@@ -229,7 +230,7 @@ namespace game {
int m_pageMaterial;
int m_startQuestID;
int m_lockID;
char *m_material;
int m_material;
int m_sheatheType;
int m_randomProperty;
int m_block;
+148 -1
View File
@@ -5,8 +5,155 @@
#include "helper.hpp"
#include "offsets.hpp"
#include "main.hpp"
#include "dbc_fields.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <unordered_map>
#include <string>
namespace Nampower {
uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) {
struct CachedEntry {
uint32_t slot;
uint32_t type;
std::string cachedNameLower;
};
static std::unordered_map<std::string, CachedEntry> spellSlotCache;
auto const normalize = [](const char *name) -> std::string {
std::string out = name ? name : "";
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return char(std::tolower(c)); });
return out;
};
const std::string key = normalize(spellName);
auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot, uint32_t type) -> bool {
if (!requestedLower) {
return false;
}
uint32_t spellId = 0;
if (type == 0) {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4);
} else if (type == 1) {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4);
} else {
return false;
}
if (spellId == 0) {
return false;
}
auto const spell = game::GetSpellInfo(spellId);
if (!spell) {
return false;
}
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
auto const actualName = reinterpret_cast<const char *>(spell->SpellName[language]);
if (!actualName) {
return false;
}
auto const actualLower = normalize(actualName);
return _stricmp(actualLower.c_str(), requestedLower) == 0;
};
if (!key.empty()) {
auto cacheIt = spellSlotCache.find(key);
if (cacheIt != spellSlotCache.end()) {
auto const &entry = cacheIt->second;
if (entry.slot < 1024 && spellNameMatchesAtSlot(key.c_str(), entry.slot, entry.type)) {
if (spellType) {
*spellType = entry.type;
}
return entry.slot;
}
}
}
auto const getSpellSlotAndType = reinterpret_cast<GetSpellSlotAndTypeT>(Offsets::GetSpellSlotAndType);
auto const slot = getSpellSlotAndType(spellName, spellType);
if (slot < 1024 && spellType && !key.empty()) {
spellSlotCache[key] = {slot, *spellType, key};
}
return slot;
}
uint32_t GetSpellIdFromSpellName(const char *spellName) {
uint32_t bookType;
uint32_t spellSlot = GetSpellSlotAndTypeForName(spellName, &bookType);
uint32_t spellId = 0;
if (spellSlot < 1024) {
if (bookType == 0) {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownSpells) +
spellSlot * 4);
} else {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) +
spellSlot * 4);
}
}
return spellId;
}
uint32_t ConvertSpellIdToSpellSlot(uint32_t spellId, uint32_t bookType) {
static std::unordered_map<uint64_t, uint32_t> spellIdToSlotCache;
auto const cacheKey = (uint64_t(spellId) << 1) | (bookType & 0x1);
auto const slotHasSpellId = [bookType](uint32_t slot, uint32_t expectedId) -> bool {
if (slot >= 1024) {
return false;
}
uint32_t slotSpellId = 0;
if (bookType == 0) {
slotSpellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4);
} else {
slotSpellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4);
}
return slotSpellId == expectedId;
};
auto cacheIt = spellIdToSlotCache.find(cacheKey);
if (cacheIt != spellIdToSlotCache.end()) {
if (slotHasSpellId(cacheIt->second, spellId)) {
return cacheIt->second;
}
}
// Search through player spellbook
if (bookType == 0) {
for (uint32_t spellSlot = 0; spellSlot < 1024; spellSlot++) {
uint32_t slotSpellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownSpells) +
spellSlot * 4);
if (slotSpellId == spellId) {
spellIdToSlotCache[cacheKey] = spellSlot;
return spellSlot;
}
}
} else {
// Search through pet spellbook
for (uint32_t spellSlot = 0; spellSlot < 1024; spellSlot++) {
uint32_t slotSpellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) +
spellSlot * 4);
if (slotSpellId == spellId) {
spellIdToSlotCache[cacheKey] = spellSlot;
return spellSlot;
}
}
}
return 0;
}
bool SpellIsOnGcd(const game::SpellRec *spell) {
if (spell->Id == 51714) {
// power overwhelming gcd removed but client not updated
@@ -122,4 +269,4 @@ namespace Nampower {
void SetNameplateDistance(float distance) {
*reinterpret_cast<float *>(Offsets::NameplateDistance) = distance * distance;
}
}
}
+5
View File
@@ -7,6 +7,11 @@
#include "game.hpp"
namespace Nampower {
uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType);
uint32_t GetSpellIdFromSpellName(const char *spellName);
uint32_t ConvertSpellIdToSpellSlot(uint32_t spellId, uint32_t bookType);
bool SpellIsOnGcd(const game::SpellRec *spell);
+293
View File
@@ -0,0 +1,293 @@
//
// Created by pmacc on 9/21/2024.
//
#include "items.hpp"
#include "offsets.hpp"
#include "logging.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
namespace Nampower {
// Global dictionary to store itemId -> ItemStats_C mappings
static std::unordered_map<uint32_t, game::ItemStats_C *> itemStatsCache;
auto const getRow = reinterpret_cast<DBCache_ItemCacheDBGetRowT>(Offsets::DBCache_ItemCacheDBGetRow);
auto const itemCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
// Callback function for DBCache
void __stdcall ItemStatsCallback(uintptr_t *tooltip, char param_2) {
if (tooltip != nullptr) {
auto itemId = *reinterpret_cast<uint32_t *>(tooltip + 0xE6);
int ptr;
auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0);
if (itemStats) {
DEBUG_LOG("Queried itemId " << itemId << " -> " << itemStats);
// Store in cache
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
}
}
}
void LoadItem(uint32_t itemId) {
// Check if already in cache
auto it = itemStatsCache.find(itemId);
if (it != itemStatsCache.end()) {
return;
}
if (!itemCache) {
return;
}
int ptr;
auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0);
if (itemStats) {
// Store in cache
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
} else {
// wait for .005 sec
Sleep(5);
}
}
game::ItemStats_C* GetItemStats(uint32_t itemId) {
auto it = itemStatsCache.find(itemId);
if (it != itemStatsCache.end()) {
return it->second;
}
return nullptr;
}
std::string escapeJsonString(const char *str) {
if (!str) return "null";
std::string result = "\"";
for (const char *p = str; *p; ++p) {
switch (*p) {
case '"':
result += "\\\"";
break;
case '\\':
result += "\\\\";
break;
case '\b':
result += "\\b";
break;
case '\f':
result += "\\f";
break;
case '\n':
result += "\\n";
break;
case '\r':
result += "\\r";
break;
case '\t':
result += "\\t";
break;
default:
if (static_cast<unsigned char>(*p) < 0x20) {
char buf[8];
snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(*p));
result += buf;
} else {
result += *p;
}
}
}
result += "\"";
return result;
}
// Register a function if you want to use this
// Don't want everyone scraping all items.
void ExportAllItems() {
DEBUG_LOG("Starting getAllItems - fetching items 1 to 100000");
// Loop through all item IDs
for (uint32_t itemId = 1; itemId <= 100000; ++itemId) {
LoadItem(itemId);
// Log progress every 1000 items
if (itemId % 1000 == 0) {
DEBUG_LOG("Progress: " << itemId << " / 100000 items processed");
}
}
DEBUG_LOG("Finished fetching items, found " << itemStatsCache.size() << " valid items");
DEBUG_LOG("Writing items to items.json");
// Write all cached items to JSON file (overwrite mode)
try {
std::ofstream jsonFile("items.json", std::ios::trunc);
if (!jsonFile.is_open()) {
DEBUG_LOG("Failed to open items.json for writing");
return;
}
jsonFile << "[\n";
bool first = true;
for (const auto &pair: itemStatsCache) {
if (!first) {
jsonFile << ",\n";
}
first = false;
uint32_t itemId = pair.first;
game::ItemStats_C *item = pair.second;
if (!item) continue;
std::ostringstream json;
json << " {\n";
json << " \"id\": " << itemId << ",\n";
json << " \"class\": " << item->m_class << ",\n";
json << " \"subclass\": " << item->m_subclass << ",\n";
json << " \"displayName\": " << escapeJsonString(item->m_displayName[0]) << ",\n";
json << " \"displayInfoID\": " << item->m_displayInfoID << ",\n";
json << " \"quality\": " << item->m_quality << ",\n";
json << " \"flags\": " << static_cast<int>(item->m_flags) << ",\n";
json << " \"buyPrice\": " << item->m_buyPrice << ",\n";
json << " \"sellPrice\": " << item->m_sellPrice << ",\n";
json << " \"inventoryType\": " << item->m_inventoryType << ",\n";
json << " \"allowableClass\": " << item->m_allowableClass << ",\n";
json << " \"allowableRace\": " << item->m_allowableRace << ",\n";
json << " \"itemLevel\": " << item->m_itemLevel << ",\n";
json << " \"requiredLevel\": " << item->m_requiredLevel << ",\n";
json << " \"requiredSkill\": " << item->m_requiredSkill << ",\n";
json << " \"requiredSkillRank\": " << item->m_requiredSkillRank << ",\n";
json << " \"requiredSpell\": " << item->m_requiredSpell << ",\n";
json << " \"requiredHonorRank\": " << item->m_requiredHonorRank << ",\n";
json << " \"requiredCityRank\": " << item->m_requiredCityRank << ",\n";
json << " \"requiredRep\": " << item->m_requiredRep << ",\n";
json << " \"requiredRepRank\": " << item->m_requiredRepRank << ",\n";
json << " \"maxCount\": " << item->m_maxCount << ",\n";
json << " \"stackable\": " << item->m_stackable << ",\n";
json << " \"containerSlots\": " << item->m_containerSlots << ",\n";
// Arrays
json << " \"bonusStat\": [";
for (int i = 0; i < 10; ++i) {
json << item->m_bonusStat[i];
if (i < 9) json << ", ";
}
json << "],\n";
json << " \"bonusAmount\": [";
for (int i = 0; i < 10; ++i) {
json << item->m_bonusAmount[i];
if (i < 9) json << ", ";
}
json << "],\n";
json << " \"minDamage\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_minDamage[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"maxDamage\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_maxDamage[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"damageType\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_damageType[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"resistances\": [";
for (int i = 0; i < 7; ++i) {
json << item->m_resistances[i];
if (i < 6) json << ", ";
}
json << "],\n";
json << " \"delay\": " << item->m_delay << ",\n";
json << " \"ammoType\": " << item->m_ammoType << ",\n";
json << " \"rangedModRange\": " << item->m_rangedModRange << ",\n";
json << " \"spellID\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellID[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"spellTrigger\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellTrigger[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"spellCharges\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellCharges[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"spellCooldown\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellCooldown[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"spellCategory\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellCategory[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"spellCategoryCooldown\": [";
for (int i = 0; i < 5; ++i) {
json << item->m_spellCategoryCooldown[i];
if (i < 4) json << ", ";
}
json << "],\n";
json << " \"bonding\": " << item->m_bonding << ",\n";
json << " \"description\": " << escapeJsonString(item->m_description) << ",\n";
json << " \"pageText\": " << item->m_pageText << ",\n";
json << " \"languageID\": " << item->m_languageID << ",\n";
json << " \"pageMaterial\": " << item->m_pageMaterial << ",\n";
json << " \"startQuestID\": " << item->m_startQuestID << ",\n";
json << " \"lockID\": " << item->m_lockID << ",\n";
json << " \"material\": " << item->m_material << ",\n";
json << " \"sheatheType\": " << item->m_sheatheType << ",\n";
json << " \"randomProperty\": " << item->m_randomProperty << ",\n";
json << " \"block\": " << item->m_block << ",\n";
json << " \"itemSet\": " << item->m_itemSet << ",\n";
json << " \"maxDurability\": " << item->m_maxDurability << ",\n";
json << " \"area\": " << item->m_area << ",\n";
json << " \"map\": " << item->m_map << ",\n";
json << " \"duration\": " << item->m_duration << ",\n";
json << " \"bagFamily\": " << item->m_bagFamily << "\n";
json << " }";
jsonFile << json.str();
}
jsonFile << "\n]\n";
jsonFile.close();
DEBUG_LOG("Successfully wrote " << itemStatsCache.size() << " items to items.json");
} catch (const std::exception &e) {
DEBUG_LOG("Exception writing to items.json: " << e.what());
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//
// Created by pmacc on 9/21/2024.
//
#pragma once
#include "main.hpp"
namespace Nampower {
using TooltipItemStatsCallbackT = void (__stdcall *)(uintptr_t *tooltip, char param_2);
using DBCache_ItemCacheDBGetRowT = uint32_t * (__fastcall *)(void *this_ptr, void *dummy_edx, uint32_t itemId, int *param_2, TooltipItemStatsCallbackT callback, int *param_4, char param_5);
void ExportAllItems();
void LoadItem(uint32_t itemId);
game::ItemStats_C* GetItemStats(uint32_t itemId);
}
+85 -50
View File
@@ -36,6 +36,7 @@
#include "scripts.hpp"
#include "spellchannel.hpp"
#include "helper.hpp"
#include "items.hpp"
#include <cstdint>
#include <memory>
@@ -92,6 +93,7 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<CancelSpellT>> gCancelSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<SignalEventT>> gSignalEventDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_SpellFailedT>> gSpellFailedDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellModifiersT>> gSpell_C_GetSpellModifiersDetour;
std::unique_ptr<hadesmem::PatchRaw> gCastbarPatch;
std::unique_ptr<hadesmem::PatchDetour<ISceneEndT>> gIEndSceneDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetAutoRepeatingSpellT>> gSpell_C_GetAutoRepeatingSpellDetour;
@@ -99,23 +101,10 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<SpellGoT>> gSpellGoDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gSpellTargetUnitDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gSpellStopCastingDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gCastSpellByNameNoQueueDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gQueueSpellByNameDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> qQueueScriptDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gIsSpellInRangeDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gIsSpellUsableDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetCurrentCastingInfoDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetSpellIdForNameDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetSpellNameAndRankForIdDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetSpellSlotAndTypeForNameDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gChannelStopCastingNextTickDetour;
std::unique_ptr<hadesmem::PatchDetour<OnSpriteRightClickT>> gOnSpriteRightClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleSpriteClickT>> gSpell_C_HandleSpriteClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_TargetSpellT>> gSpell_C_TargetSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetNampowerVersionDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gGetItemLevelDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellCooldownDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellDelayedDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gCastResultHandlerDetour;
@@ -128,6 +117,12 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT>> gPeriodicAuraLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT>> gSpellNonMeleeDmgLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<CGPlayer_C_OnAttackIconPressedT>> gCGPlayer_C_OnAttackIconPressedDetour;
std::unique_ptr<hadesmem::PatchDetour<InvalidFunctionPtrCheckT>> gInvalidFunctionPtrCheckDetour;
std::unique_ptr<hadesmem::PatchDetour<GetSpellSlotFromLuaT>> gGetSpellSlotFromLuaDetour;
uint32_t GetTime() {
return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime;
@@ -163,6 +158,34 @@ namespace Nampower {
return p_GetContext();
}
void SetTarget(uint64_t target) {
auto setTargetFn = reinterpret_cast<TargetUnitT>(Offsets::ScriptTargetUnit);
setTargetFn(&target);
}
void SetSelectionTarget(uint64_t target) {
auto dataStore = CDataStore();
uint32_t opcode = 317; // CMSG_SET_SELECTION
dataStore.Put(opcode);
dataStore.Put(target);
dataStore.Finalize();
auto const clientServicesSend = reinterpret_cast<ClientServices_SendT>(Offsets::ClientServices_Send);
clientServicesSend(&dataStore);
}
void SetAttackTarget(uint64_t target) {
auto dataStore = CDataStore();
uint32_t opcode = 0x141; // CMSG_ATTACKSWING
dataStore.Put(opcode);
dataStore.Put(target);
dataStore.Finalize();
auto const clientServicesSend = reinterpret_cast<ClientServices_SendT>(Offsets::ClientServices_Send);
clientServicesSend(&dataStore);
}
void RegisterLuaFunction(char *name, uintptr_t *func) {
DEBUG_LOG("Registering " << name << " to " << func);
@@ -513,6 +536,17 @@ namespace Nampower {
return iSceneEnd(ptr);
}
void InvalidFunctionPtrCheckHook(hadesmem::PatchDetourBase *detour, uint32_t param_1) {
// remove original call to allow registering lua functions at any address range
}
void
Spell_C_GetSpellModifiersHook(hadesmem::PatchDetourBase *detour, const game::SpellRec *spellRec, int *returnVal,
game::SpellModOp modOp) {
auto const getSpellModifiers = detour->GetTrampolineT<Spell_C_GetSpellModifiersT>();
getSpellModifiers(spellRec, returnVal, modOp);
}
void updateFromCvar(const char *cvar, const char *value) {
if (strcmp(cvar, "NP_QueueCastTimeSpells") == 0) {
gUserSettings.queueCastTimeSpells = atoi(value) != 0;
@@ -1086,29 +1120,14 @@ namespace Nampower {
&Script_SpellStopCastingHook);
gSpell_C_TargetSpellDetour = createHook<Spell_C_TargetSpellT>(process, Offsets::Spell_C_TargetSpell,
&Spell_C_TargetSpellHook);
gCastSpellByNameNoQueueDetour = createHook<LuaScriptT>(process, Offsets::Script_CastSpellByNameNoQueue,
Script_CastSpellByNameNoQueue);
gQueueSpellByNameDetour = createHook<LuaScriptT>(process, Offsets::Script_QueueSpellByName,
Script_QueueSpellByName);
qQueueScriptDetour = createHook<LuaScriptT>(process, Offsets::Script_QueueScript, Script_QueueScript);
gIsSpellInRangeDetour = createHook<LuaScriptT>(process, Offsets::Script_IsSpellInRange, Script_IsSpellInRange);
gIsSpellUsableDetour = createHook<LuaScriptT>(process, Offsets::Script_IsSpellUsable, Script_IsSpellUsable);
gGetCurrentCastingInfoDetour = createHook<LuaScriptT>(process, Offsets::Script_GetCurrentCastingInfo,
Script_GetCurrentCastingInfo);
gGetSpellIdForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellIdForName,
Script_GetSpellIdForName);
gGetSpellNameAndRankForIdDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellNameAndRankForId,
Script_GetSpellNameAndRankForId);
gGetSpellSlotAndTypeForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellSlotTypeIdForName,
Script_GetSpellSlotTypeIdForName);
gOnSpriteRightClickDetour = createHook<OnSpriteRightClickT>(process, Offsets::OnSpriteRightClick,
OnSpriteRightClickHook);
gChannelStopCastingNextTickDetour = createHook<LuaScriptT>(process, Offsets::Script_ChannelStopCastingNextTick,
Script_ChannelStopCastingNextTick);
gGetNampowerVersionDetour = createHook<LuaScriptT>(process, Offsets::Script_GetNampowerVersion,
Script_GetNampowerVersion);
gGetItemLevelDetour = createHook<LuaScriptT>(process, Offsets::Script_GetItemLevel, Script_GetItemLevel);
gIEndSceneDetour = createHook<ISceneEndT>(process, Offsets::ISceneEndPtr, &ISceneEndHook);
gInvalidFunctionPtrCheckDetour = createHook<InvalidFunctionPtrCheckT>(process, Offsets::InvalidFunctionPtrCheck,
&InvalidFunctionPtrCheckHook);
gGetSpellSlotFromLuaDetour = createHook<GetSpellSlotFromLuaT>(process, Offsets::GetSpellSlotFromLua,
&GetSpellSlotFromLuaHook);
}
void SpellVisualsInitializeHook(hadesmem::PatchDetourBase *detour) {
@@ -1135,46 +1154,62 @@ namespace Nampower {
// register our own lua functions
DEBUG_LOG("Registering Custom Lua functions");
char queueSpellByName[] = "QueueSpellByName";
RegisterLuaFunction(queueSpellByName, reinterpret_cast<uintptr_t *>(Offsets::Script_QueueSpellByName));
RegisterLuaFunction(queueSpellByName, reinterpret_cast<uintptr_t *>(Script_QueueSpellByName));
char castSpellByNameNoQueue[] = "CastSpellByNameNoQueue";
RegisterLuaFunction(castSpellByNameNoQueue,
reinterpret_cast<uintptr_t *>(Offsets::Script_CastSpellByNameNoQueue));
RegisterLuaFunction(castSpellByNameNoQueue, reinterpret_cast<uintptr_t *>(Script_CastSpellByNameNoQueue));
char queueScript[] = "QueueScript";
RegisterLuaFunction(queueScript, reinterpret_cast<uintptr_t *>(Offsets::Script_QueueScript));
RegisterLuaFunction(queueScript, reinterpret_cast<uintptr_t *>(Script_QueueScript));
char isSpellInRange[] = "IsSpellInRange";
RegisterLuaFunction(isSpellInRange, reinterpret_cast<uintptr_t *>(Offsets::Script_IsSpellInRange));
RegisterLuaFunction(isSpellInRange, reinterpret_cast<uintptr_t *>(Script_IsSpellInRange));
char isSpellUsable[] = "IsSpellUsable";
RegisterLuaFunction(isSpellUsable, reinterpret_cast<uintptr_t *>(Offsets::Script_IsSpellUsable));
RegisterLuaFunction(isSpellUsable, reinterpret_cast<uintptr_t *>(Script_IsSpellUsable));
char getCurrentCastingInfo[] = "GetCurrentCastingInfo";
RegisterLuaFunction(getCurrentCastingInfo,
reinterpret_cast<uintptr_t *>(Offsets::Script_GetCurrentCastingInfo));
RegisterLuaFunction(getCurrentCastingInfo, reinterpret_cast<uintptr_t *>(Script_GetCurrentCastingInfo));
char getSpellIdForName[] = "GetSpellIdForName";
RegisterLuaFunction(getSpellIdForName,
reinterpret_cast<uintptr_t *>(Offsets::Script_GetSpellIdForName));
RegisterLuaFunction(getSpellIdForName, reinterpret_cast<uintptr_t *>(Script_GetSpellIdForName));
char getSpellNameAndRankForId[] = "GetSpellNameAndRankForId";
RegisterLuaFunction(getSpellNameAndRankForId,
reinterpret_cast<uintptr_t *>(Offsets::Script_GetSpellNameAndRankForId));
RegisterLuaFunction(getSpellNameAndRankForId, reinterpret_cast<uintptr_t *>(Script_GetSpellNameAndRankForId));
char getSpellSlotTypeIdForName[] = "GetSpellSlotTypeIdForName";
RegisterLuaFunction(getSpellSlotTypeIdForName,
reinterpret_cast<uintptr_t *>(Offsets::Script_GetSpellSlotTypeIdForName));
RegisterLuaFunction(getSpellSlotTypeIdForName, reinterpret_cast<uintptr_t *>(Script_GetSpellSlotTypeIdForName));
char channelStopCastingNextTick[] = "ChannelStopCastingNextTick";
RegisterLuaFunction(channelStopCastingNextTick,
reinterpret_cast<uintptr_t *>(Offsets::Script_ChannelStopCastingNextTick));
reinterpret_cast<uintptr_t *>(Script_ChannelStopCastingNextTick));
char getNampowerVersion[] = "GetNampowerVersion";
RegisterLuaFunction(getNampowerVersion, reinterpret_cast<uintptr_t *>(Offsets::Script_GetNampowerVersion));
RegisterLuaFunction(getNampowerVersion, reinterpret_cast<uintptr_t *>(Script_GetNampowerVersion));
char getItemILevel[] = "GetItemLevel";
RegisterLuaFunction(getItemILevel, reinterpret_cast<uintptr_t *>(Offsets::Script_GetItemLevel));
RegisterLuaFunction(getItemILevel, reinterpret_cast<uintptr_t *>(Script_GetItemLevel));
char getItemStats[] = "GetItemStats";
RegisterLuaFunction(getItemStats, reinterpret_cast<uintptr_t *>(Script_GetItemStats));
char getSpellRec[] = "GetSpellRec";
RegisterLuaFunction(getSpellRec, reinterpret_cast<uintptr_t *>(Script_GetSpellRec));
char getItemStatsField[] = "GetItemStatsField";
RegisterLuaFunction(getItemStatsField, reinterpret_cast<uintptr_t *>(Script_GetItemStatsField));
char getSpellRecField[] = "GetSpellRecField";
RegisterLuaFunction(getSpellRecField, reinterpret_cast<uintptr_t *>(Script_GetSpellRecField));
char getUnitData[] = "GetUnitData";
RegisterLuaFunction(getUnitData, reinterpret_cast<uintptr_t *>(Script_GetUnitData));
char getUnitField[] = "GetUnitField";
RegisterLuaFunction(getUnitField, reinterpret_cast<uintptr_t *>(Script_GetUnitField));
char getSpellModifiers[] = "GetSpellModifiers";
RegisterLuaFunction(getSpellModifiers, reinterpret_cast<uintptr_t *>(Script_GetSpellModifiers));
}
std::once_flag loadFlag;
+20 -3
View File
@@ -30,8 +30,8 @@ namespace Nampower {
constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 11;
constexpr uint32_t PATCH_VERSION = 1;
constexpr uint32_t MINOR_VERSION = 12;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
constexpr int32_t LUA_GLOBALSINDEX = -10001;
@@ -93,8 +93,12 @@ namespace Nampower {
using Spell_C_GetSpellCooldownT = int (__fastcall *)(uint32_t spellId, uint32_t isPetSpell,
uint32_t *duration, uint64_t *startTime, uint32_t *enable);
using Spell_C_IsSpellUsableT = int (__fastcall *)(const game::SpellRec *spellRec, uint32_t *usesManaReturn);
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const game::SpellRec *spellRec, int *returnVal, game::SpellModOp modOp);
using GetSpellSlotAndTypeT = int (__fastcall *)(const char *, uint32_t *);
using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid);
using GetSpellSlotAndTypeT = uint32_t (__fastcall *)(const char *, uint32_t *);
using GetSpellSlotFromLuaT = uint32_t (__fastcall *)(int param_1, uint32_t *slot, uint32_t *type);
using GetTimeMsT = uint64_t (__stdcall *)();
using GetClientConnectionT = uintptr_t *(__stdcall *)();
@@ -122,6 +126,8 @@ namespace Nampower {
using lua_pushnilT = void (__fastcall *)(uintptr_t *);
using lua_errorT = void (__cdecl *)(uintptr_t *, const char *);
using lua_settopT = void (__fastcall *)(uintptr_t *, int);
using lua_newtableT = void (__fastcall *)(uintptr_t *);
using lua_settableT = void (__fastcall *)(uintptr_t *, int);
using Spell_C_CooldownEventTriggeredT = void (__fastcall *)(uint32_t spellId,
uint64_t *targetGUID,
@@ -137,12 +143,16 @@ namespace Nampower {
using GetBuffByIndexT = uintptr_t *(__fastcall *)(int index);
using TargetUnitT = void (__fastcall *)(uint64_t *guid);
using CVarLookupT = uintptr_t *(__fastcall *)(const char *);
using SetCVarT = int (__fastcall *)(uintptr_t *luaPtr);
using CVarRegisterT = int *(__fastcall *)(char *name, char *help, int unk1, const char *defaultValuePtr,
void *callbackPtr,
int category, char unk2, int unk3);
using InvalidFunctionPtrCheckT = void (__fastcall *)(uint32_t param_1);
void RegisterLuaFunction(char *, uintptr_t *func);
void LuaCall(const char *code);
@@ -170,4 +180,11 @@ namespace Nampower {
bool processQueues();
uint32_t EffectiveCastEndMs();
void SetTarget(uint64_t target);
void SetSelectionTarget(uint64_t target);
void SetAttackTarget(uint64_t target);
}
+15 -15
View File
@@ -49,6 +49,7 @@ enum class Offsets : std::uint32_t {
AutoRepeatingSpellId = 0xCEAC30,
VisualSpellId = 0X00CEAC58,
GetSpellSlotAndType = 0X004B3950,
GetSpellSlotFromLua = 0X004B3EC0,
OsGetAsyncTimeMs = 0X0042B790,
ChannelTargetGuid = 0xC4D980,
NameplateDistance = 0xC4D988, // float containing the distance squared ready to be pythagorean'd
@@ -76,6 +77,7 @@ enum class Offsets : std::uint32_t {
LockedTargetGuid = 0x00B4E2D8,
OnSpriteRightClick = 0x00492820,
CGGameUI_Target = 0X00493540,
ScriptTargetUnit = 0X00489A40,
SpellVisualsHandleCastStart = 0X006EC220,
PlaySpellVisualHandler = 0X006E98D0,
@@ -102,7 +104,8 @@ enum class Offsets : std::uint32_t {
Spell_C_GetSpellCooldown = 0X006E2EA0,
Spell_C_IsSpellUsable = 0X006E3D60,
Spell_C_GetDuration = 0X006EA000,
Spell_C_GetSpellModifiers = 0x006e6af0,
Spell_C_ApplySpellModifiers = 0x006e6af0,
Spell_C_GetSpellModifierValues = 0x006e6b30,
CVarLookup = 0x0063DEC0,
RegisterCVar = 0X0063DB90,
@@ -123,20 +126,6 @@ enum class Offsets : std::uint32_t {
Script_RunScript = 0x0048B980,
Script_SpellStopCasting = 0x006E6E80,
// Added script functions
Script_QueueSpellByName = 0x004B4B38, // unused address at the end of Script_CastSpellByName. Need to be < 0x7FEDAC to avoid Invalid Function Pointer
Script_CastSpellByNameNoQueue = 0X004B4B64,
Script_QueueScript = 0X0048B968, // unused address at the end of Script_StopCinematic
Script_IsSpellInRange = 0x004E76D8,
Script_IsSpellUsable = 0X004E77A4,
Script_GetCurrentCastingInfo = 0X004E77F8,
Script_GetSpellIdForName = 0X004E7828,
Script_GetSpellNameAndRankForId = 0X004E7844,
Script_GetSpellSlotTypeIdForName = 0X004E784A,
Script_ChannelStopCastingNextTick = 0X004E7858,
Script_GetNampowerVersion = 0X004E7874,
Script_GetItemLevel = 0X004E787A,
lua_state_ptr = 0x7040D0,
lua_isstring = 0x006F3510,
@@ -151,6 +140,11 @@ enum class Offsets : std::uint32_t {
lua_pcall = 0x006F41A0,
lua_error = 0X006F4940,
lua_settop = 0X006F3080,
lua_newtable = 0x006F3C90,
lua_settable = 0x006F3E20,
lua_pushboolean = 0x006F39F0,
lua_rawseti = 0x006F3EA0,
lua_gettop = 0x006F3070,
CGInputControlGetActive = 0XBE1148,
@@ -174,4 +168,10 @@ enum class Offsets : std::uint32_t {
CGUnit_C_ClearCastingSpell = 0x0060d040,
CGUnit_C_ClearSpellEffect = 0x00614150,
CGPlayer_C_OnAttackIconPressed = 0X006131A0,
DBCache_ItemCacheDBGetRow = 0x0055BA30,
InvalidFunctionPtrCheck = 0x0042A320,
};
+559 -41
View File
@@ -4,6 +4,11 @@
#include "scripts.hpp"
#include "offsets.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
#include "unit_fields.hpp"
#include "helper.hpp"
#include <cstring>
namespace Nampower {
auto const lua_error = reinterpret_cast<lua_errorT>(Offsets::lua_error);
@@ -14,38 +19,18 @@ namespace Nampower {
auto const lua_tostring = reinterpret_cast<lua_tostringT>(Offsets::lua_tostring);
auto const lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber);
auto const lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
auto const lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring);
// Export Lua functions for dbc_fields.hpp templates
lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring);
lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil);
lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable);
lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable);
bool gScriptQueued;
int gScriptPriority = 1;
char *queuedScript;
uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) {
auto const getSpellSlotAndType = reinterpret_cast<GetSpellSlotAndTypeT>(Offsets::GetSpellSlotAndType);
return getSpellSlotAndType(spellName, spellType);
}
uint32_t GetSpellIdFromSpellName(const char *spellName) {
uint32_t bookType;
uint32_t spellSlot = GetSpellSlotAndTypeForName(spellName, &bookType);
uint32_t spellId = 0;
if (spellSlot < 1024) {
if (bookType == 0) {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownSpells) +
spellSlot * 4);
} else {
spellId = *reinterpret_cast<uint32_t *>(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) +
spellSlot * 4);
}
}
return spellId;
}
uint32_t Script_CastSpellByNameNoQueue(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState) {
DEBUG_LOG("Casting next spell without queuing");
// turn on forceQueue and then call regular CastSpellByName
gNoQueueCast = true;
@@ -56,7 +41,7 @@ namespace Nampower {
return result;
}
uint32_t Script_QueueSpellByName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_QueueSpellByName(uintptr_t *luaState) {
DEBUG_LOG("Force queuing next cast spell");
// turn on forceQueue and then call regular CastSpellByName
gForceQueueCast = true;
@@ -78,7 +63,7 @@ namespace Nampower {
return spellStopCasting(luaState);
}
uint32_t Script_IsSpellInRange(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_IsSpellInRange(uintptr_t *luaState) {
auto param1IsString = lua_isstring(luaState, 1);
auto param1IsNumber = lua_isnumber(luaState, 1);
if (param1IsString || param1IsNumber) {
@@ -150,7 +135,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_IsSpellUsable(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_IsSpellUsable(uintptr_t *luaState) {
auto param1IsString = lua_isstring(luaState, 1);
auto param1IsNumber = lua_isnumber(luaState, 1);
if (param1IsString || param1IsNumber) {
@@ -204,7 +189,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetCurrentCastingInfo(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) {
auto const castingSpellId = reinterpret_cast<uint32_t *>(Offsets::CastingSpellId);
lua_pushnumber(luaState, *castingSpellId);
@@ -246,7 +231,7 @@ namespace Nampower {
return 7;
}
uint32_t Script_GetSpellIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetSpellIdForName(uintptr_t *luaState) {
if (lua_isstring(luaState, 1)) {
auto const spellName = lua_tostring(luaState, 1);
auto const spellId = GetSpellIdFromSpellName(spellName);
@@ -259,7 +244,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetSpellNameAndRankForId(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetSpellNameAndRankForId(uintptr_t *luaState) {
if (lua_isnumber(luaState, 1)) {
auto const spellId = uint32_t(lua_tonumber(luaState, 1));
auto const spell = game::GetSpellInfo(spellId);
@@ -271,7 +256,7 @@ namespace Nampower {
return 2;
} else {
DEBUG_LOG("Spell not found for id: " << spellId);
lua_error(luaState, "Spell not found");
lua_error(luaState, "Spell not found");
}
} else {
lua_error(luaState, "Usage: GetSpellNameAndRankForId(spellId)");
@@ -280,7 +265,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetSpellSlotTypeIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetSpellSlotTypeIdForName(uintptr_t *luaState) {
if (lua_isstring(luaState, 1)) {
auto const spellName = lua_tostring(luaState, 1);
uint32_t bookType;
@@ -330,7 +315,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_ChannelStopCastingNextTick(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState) {
if (gCastData.channeling) {
DEBUG_LOG("ChannelStopCastingNextTick activated, canceling next tick");
gCastData.cancelChannelNextTick = true;
@@ -339,7 +324,7 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetNampowerVersion(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetNampowerVersion(uintptr_t *luaState) {
lua_pushnumber(luaState, MAJOR_VERSION);
lua_pushnumber(luaState, MINOR_VERSION);
lua_pushnumber(luaState, PATCH_VERSION);
@@ -347,7 +332,7 @@ namespace Nampower {
return 3;
}
uint32_t Script_GetItemLevel(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_GetItemLevel(uintptr_t *luaState) {
if (lua_isnumber(luaState, 1)) {
auto const itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
@@ -380,7 +365,7 @@ namespace Nampower {
return 0;
}
bool Script_QueueScript(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
uint32_t Script_QueueScript(uintptr_t *luaState) {
DEBUG_LOG("Trying to queue script");
auto const currentTime = GetTime();
@@ -418,7 +403,7 @@ namespace Nampower {
return runScript(luaState);
}
return false;
return 0;
}
bool RunQueuedScript(int priority) {
@@ -440,4 +425,537 @@ namespace Nampower {
return false;
}
}
uint32_t Script_GetItemStats(uintptr_t *luaState) {
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetItemStats(itemId)");
return 0;
}
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Load item if not cached
LoadItem(itemId);
// 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) {
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: GetSpellRec(spellId)");
return 0;
}
uint32_t spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
// Get spell info
auto spell = game::GetSpellInfo(spellId);
if (!spell) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, spell, spellRecFields, spellRecFieldsCount);
// 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);
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetItemStatsField(uintptr_t *luaState) {
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);
// Load item if not cached
LoadItem(itemId);
// 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::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::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) {
if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: GetSpellRecField(spellId, fieldName)");
return 0;
}
InitializeFieldMaps();
uint32_t spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
const char *fieldName = lua_tostring(luaState, 2);
// Get spell info
auto spell = game::GetSpellInfo(spellId);
if (!spell) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = spellRecFieldMap.find(fieldName);
if (simpleIt != spellRecFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(spell) + spellRecFields[i].offset;
switch (spellRecFields[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::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(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;
}
}
}
// O(1) hash table lookup for array fields
auto arrayIt = spellRecArrayFieldMap.find(fieldName);
if (arrayIt != spellRecArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = spellRecArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(spell) + 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::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, "name") == 0) {
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState, spell->SpellName[language] ? const_cast<char *>(spell->SpellName[language])
: const_cast<char *>(""));
return 1;
}
if (strcmp(fieldName, "rank") == 0) {
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
lua_pushstring(luaState, reinterpret_cast<const char *>(spell->Rank[language])
? const_cast<char *>(reinterpret_cast<const char *>(spell->Rank[language]))
: const_cast<char *>(""));
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
uint32_t Script_GetUnitData(uintptr_t *luaState) {
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;
// Check if it's a GUID string (starts with "0x" or "0X")
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
guid = std::stoull(unitToken, nullptr, 16);
} else {
// Get GUID from unit token
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
guid = getGUIDFromName(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) {
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;
// Check if it's a GUID string (starts with "0x" or "0X")
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
guid = std::stoull(unitToken, nullptr, 16);
} else {
// Get GUID from unit token
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
guid = getGUIDFromName(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::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::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) {
if (!lua_isnumber(luaState, 1) || !lua_isnumber(luaState, 2)) {
lua_error(luaState, "Usage: GetSpellModifiers(spellId, modifierType) - modifierType: 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");
return 0;
}
uint32_t spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
int modifierType = static_cast<int>(lua_tonumber(luaState, 2));
// Validate modifier type
if (modifierType < 0 || modifierType >= game::MAX_SPELLMOD) {
lua_error(luaState, "Invalid modifier type. Must be between 0 and 28.");
return 0;
}
// Get spell info
auto spell = game::GetSpellInfo(spellId);
if (!spell) {
lua_error(luaState, "Invalid spell id");
return 0;
}
// Call Spell_C_GetSpellModifierValues
using Spell_C_GetSpellModifierValuesT = uint32_t (__fastcall *)(game::SpellRec *spellRec, int mod, int *flatMod, uint32_t *percentMod);
auto const getModifierValues = reinterpret_cast<Spell_C_GetSpellModifierValuesT>(Offsets::Spell_C_GetSpellModifierValues);
int flatModificationValue = 0;
uint32_t percentModificationValue = 0;
auto ret = getModifierValues(const_cast<game::SpellRec *>(spell), modifierType, &flatModificationValue, &percentModificationValue);
// show flat modifications
lua_pushnumber(luaState, flatModificationValue);
if (flatModificationValue != 0 && percentModificationValue >= 100) {
percentModificationValue -= 100;
} else if (modifierType == 9 && percentModificationValue >= 100) {
percentModificationValue -= 100;
}
// show percent modifications
lua_pushnumber(luaState, percentModificationValue);
// show return
lua_pushnumber(luaState, ret);
return 3;
}
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type) {
// Get Lua state pointer
uintptr_t *luaState = GetLuaStatePtr();
// Check if first parameter is not a number
if (lua_isnumber(luaState, 1) == 0) {
const char *spellStr = lua_tostring(luaState, 1);
// Check if string starts with "spellId:" (case-insensitive)
if (_strnicmp(spellStr, "spellId:", 8) == 0) {
// Extract spell ID from string
uint32_t spellId = atoi(spellStr + 8);
if (spellId > 0) {
// Check if second parameter has "pet"
uint32_t bookType = 0;
if (lua_isstring(luaState, 2)) {
const char *bookTypeStr = lua_tostring(luaState, 2);
if (_strcmpi(bookTypeStr, "pet") == 0) {
bookType = 1;
}
}
uint32_t spellSlot = ConvertSpellIdToSpellSlot(spellId, bookType);
if(!spellSlot){
lua_error(luaState, "Unable convert spell id to spell slot.");
return 0;
}
if (slot) {
*slot = spellSlot;
}
if (type) {
*type = bookType; // Use the requested bookType
}
return 1;
} else {
lua_error(luaState, "Unable parse spell id. Format is spellId:123.");
return 0;
}
} else {
uint32_t bookType;
auto spellSlot = GetSpellSlotAndTypeForName(spellStr, &bookType);
// returns large number if spell not found
if (spellSlot == 0 || spellSlot > 100000) {
lua_error(luaState, "Unable to find spell slot for spell name.");
return 0;
} else {
if (slot) {
*slot = spellSlot;
}
if (type) {
*type = bookType;
}
}
return 1;
}
} else {
auto const getSpellSlotFromLua = detour->GetTrampolineT<GetSpellSlotFromLuaT>();
// Fall back to original function
return getSpellSlotFromLua(param_1, slot, type);
}
return 0;
}
}
+28 -12
View File
@@ -8,31 +8,47 @@
#include "main.hpp"
namespace Nampower {
uint32_t Script_CastSpellByNameNoQueue(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState);
uint32_t Script_QueueSpellByName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_QueueSpellByName(uintptr_t *luaState);
uint32_t Script_IsSpellInRange(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_IsSpellInRange(uintptr_t *luaState);
uint32_t Script_IsSpellUsable(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_IsSpellUsable(uintptr_t *luaState);
uint32_t Script_SpellStopCastingHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetCurrentCastingInfo(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState);
uint32_t Script_GetSpellIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetSpellIdForName(uintptr_t *luaState);
uint32_t Script_GetSpellNameAndRankForId(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetSpellNameAndRankForId(uintptr_t *luaState);
uint32_t Script_GetSpellSlotTypeIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetSpellSlotTypeIdForName(uintptr_t *luaState);
uint32_t Script_ChannelStopCastingNextTick(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState);
uint32_t Script_GetNampowerVersion(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetNampowerVersion(uintptr_t *luaState);
uint32_t Script_GetItemLevel(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint32_t Script_GetItemLevel(uintptr_t *luaState);
bool Script_QueueScript(hadesmem::PatchDetourBase *detour, 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);
}
+10 -23
View File
@@ -108,8 +108,6 @@ namespace Nampower {
}
gLastCastData.startTimeMs = currentTime;
// if queued cast, simulate spellcast start
}
void CastQueuedNonGcdSpell() {
@@ -120,7 +118,7 @@ namespace Nampower {
gCastData.castingQueuedSpell = true;
gCastData.numRetries = nonGcdCastParams.numRetries;
Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.playerUnit, nonGcdCastParams.spellId,
Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.casterUnit, nonGcdCastParams.spellId,
nonGcdCastParams.item, nonGcdCastParams.guid);
gLastCastData.wasQueued = true;
} else {
@@ -140,7 +138,7 @@ namespace Nampower {
DEBUG_LOG("Triggering queued cast of " << game::GetSpellName(gLastNormalCastParams.spellId));
gCastData.castingQueuedSpell = true;
gCastData.numRetries = gLastNormalCastParams.numRetries;
Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.playerUnit, gLastNormalCastParams.spellId,
Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.casterUnit, gLastNormalCastParams.spellId,
gLastNormalCastParams.item, gLastNormalCastParams.guid);
gLastCastData.wasQueued = true;
} else {
@@ -163,7 +161,7 @@ namespace Nampower {
}
void SaveCastParams(CastSpellParams *params,
uint32_t *playerUnit,
uint32_t *casterUnit,
uint32_t spellId,
uintptr_t *item,
std::uint64_t guid,
@@ -172,7 +170,7 @@ namespace Nampower {
uint32_t castStartTimeMs,
CastType castType,
uint32_t numRetries) {
params->playerUnit = playerUnit;
params->casterUnit = casterUnit;
params->spellId = spellId;
params->item = item;
params->guid = guid;
@@ -218,18 +216,6 @@ namespace Nampower {
}
}
void setSelectionTarget(uint64_t target) {
auto dataStore = CDataStore();
uint32_t opcode = 317; // CMSG_SET_SELECTION
dataStore.Put(opcode);
dataStore.Put(target);
dataStore.Finalize();
auto const clientServicesSend = reinterpret_cast<ClientServices_SendT>(Offsets::ClientServices_Send);
clientServicesSend(&dataStore);
}
bool
Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, uintptr_t *item,
std::uint64_t guid) {
@@ -257,14 +243,14 @@ namespace Nampower {
auto const isSpecialSpell = SpellIsAttackTradeskillOrEnchant(spell);
auto const currentTargetGuid = game::GetCurrentTargetGuid();
if(spellIsChanneling) {
if (spellIsChanneling) {
auto casterGuid = game::UnitGetGuid(casterUnit);
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
// check that locked target guid matches our unit target guid
auto unitTargetGuid = game::UnitGetTargetGuid(casterUnit);
if (unitTargetGuid != currentTargetGuid) {
DEBUG_LOG("Updating selection target to " << currentTargetGuid << " from " << unitTargetGuid);
setSelectionTarget(currentTargetGuid);
SetSelectionTarget(currentTargetGuid);
}
}
}
@@ -299,7 +285,8 @@ namespace Nampower {
itemId = game::GetItemId((game::CGItem_C *) item);
}
DEBUG_LOG("Attempt cast " << spellName << " item " << item << " on guid " << guid << " target " << currentTargetGuid
DEBUG_LOG("Attempt cast " << spellName << " item " << item << " on guid " << guid << " target "
<< currentTargetGuid
<< ", time since last cast " << currentTime - gLastCastData.startTimeMs);
// clear cooldown queue if we are casting a spell
@@ -522,7 +509,7 @@ namespace Nampower {
// otherwise can break cooldown in the client and cause unnecessary errors
if (castTime == 0 || spell->Attributes & game::SPELL_ATTR_DISABLED_WHILE_ACTIVE) {
// check if spam protection is enabled
if(gUserSettings.spamProtectionEnabled) {
if (gUserSettings.spamProtectionEnabled) {
auto castParams = gCastHistory.findNewestWaitingForServerSpellId(spellId);
if (castParams &&
currentTime - castParams->castStartTimeMs < 500) {
@@ -643,7 +630,7 @@ namespace Nampower {
TriggerSpellQueuedEvent(ON_SWING_QUEUE_POPPED, gLastOnSwingCastParams.spellId);
Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.playerUnit,
Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.casterUnit,
gLastOnSwingCastParams.spellId,
gLastOnSwingCastParams.item, gLastOnSwingCastParams.guid);
gCastData.onSwingQueued = false;
-1
View File
@@ -277,7 +277,6 @@ namespace Nampower {
return spellDelayed(opCode, packet);
}
int CastResultHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) {
auto const castResultHandler = detour->GetTrampolineT<PacketHandlerT>();
+1 -1
View File
@@ -71,7 +71,7 @@ struct CastSpellParams {
/* Original cast spell function arguments */
uint64_t castId;
uint32_t *playerUnit;
uint32_t *casterUnit;
uint32_t spellId;
uintptr_t *item;
uint64_t guid;
+110
View File
@@ -0,0 +1,110 @@
//
// Created by pmacc on 1/8/2025.
//
#include "unit_fields.hpp"
#include "game.hpp"
namespace Nampower {
// Hash tables for O(1) field lookups
std::unordered_map<std::string, size_t> unitFieldsFieldMap;
std::unordered_map<std::string, size_t> unitFieldsArrayFieldMap;
static bool unitFieldMapsInitialized = false;
// UnitFields field descriptors
const FieldDescriptor unitFieldsFields[] = {
{"charm", offsetof(game::UnitFields, charm), FieldType::UINT64},
{"summon", offsetof(game::UnitFields, summon), FieldType::UINT64},
{"charmedBy", offsetof(game::UnitFields, charmedBy), FieldType::UINT64},
{"summonedBy", offsetof(game::UnitFields, summonedBy), FieldType::UINT64},
{"createdBy", offsetof(game::UnitFields, createdBy), FieldType::UINT64},
{"target", offsetof(game::UnitFields, target), FieldType::UINT64},
{"persuaded", offsetof(game::UnitFields, persuaded), FieldType::UINT64},
{"channelObject", offsetof(game::UnitFields, channelObject), FieldType::UINT64},
{"health", offsetof(game::UnitFields, health), FieldType::UINT32},
{"power1", offsetof(game::UnitFields, power1), FieldType::UINT32},
{"power2", offsetof(game::UnitFields, power2), FieldType::UINT32},
{"power3", offsetof(game::UnitFields, power3), FieldType::UINT32},
{"power4", offsetof(game::UnitFields, power4), FieldType::UINT32},
{"power5", offsetof(game::UnitFields, power5), FieldType::UINT32},
{"maxHealth", offsetof(game::UnitFields, maxHealth), FieldType::UINT32},
{"maxPower1", offsetof(game::UnitFields, maxPower1), FieldType::UINT32},
{"maxPower2", offsetof(game::UnitFields, maxPower2), FieldType::UINT32},
{"maxPower3", offsetof(game::UnitFields, maxPower3), FieldType::UINT32},
{"maxPower4", offsetof(game::UnitFields, maxPower4), FieldType::UINT32},
{"maxPower5", offsetof(game::UnitFields, maxPower5), FieldType::UINT32},
{"level", offsetof(game::UnitFields, level), FieldType::UINT32},
{"factionTemplate", offsetof(game::UnitFields, factionTemplate), FieldType::UINT32},
{"bytes0", offsetof(game::UnitFields, bytes0), FieldType::UINT32},
{"flags", offsetof(game::UnitFields, flags), FieldType::UINT32},
{"auraState", offsetof(game::UnitFields, auraState), FieldType::UINT32},
{"baseAttackTime", offsetof(game::UnitFields, baseAttackTime), FieldType::UINT32},
{"offhandAttackTime", offsetof(game::UnitFields, offhandAttackTime), FieldType::UINT32},
{"rangedAttackTime", offsetof(game::UnitFields, rangedAttackTime), FieldType::UINT32},
{"boundingRadius", offsetof(game::UnitFields, boundingRadius), FieldType::FLOAT},
{"combatReach", offsetof(game::UnitFields, combatReach), FieldType::FLOAT},
{"displayId", offsetof(game::UnitFields, displayId), FieldType::UINT32},
{"nativeDisplayId", offsetof(game::UnitFields, nativeDisplayId), FieldType::UINT32},
{"mountDisplayId", offsetof(game::UnitFields, mountDisplayId), FieldType::UINT32},
{"minDamage", offsetof(game::UnitFields, minDamage), FieldType::FLOAT},
{"maxDamage", offsetof(game::UnitFields, maxDamage), FieldType::FLOAT},
{"minOffhandDamage", offsetof(game::UnitFields, minOffhandDamage), FieldType::FLOAT},
{"maxOffhandDamage", offsetof(game::UnitFields, maxOffhandDamage), FieldType::FLOAT},
{"bytes1", offsetof(game::UnitFields, bytes1), FieldType::UINT32},
{"petNumber", offsetof(game::UnitFields, petNumber), FieldType::UINT32},
{"petNameTimestamp", offsetof(game::UnitFields, petNameTimestamp), FieldType::UINT32},
{"petExperience", offsetof(game::UnitFields, petExperience), FieldType::UINT32},
{"petNextLevelExp", offsetof(game::UnitFields, petNextLevelExp), FieldType::UINT32},
{"dynamicFlags", offsetof(game::UnitFields, dynamicFlags), FieldType::UINT32},
{"channelSpell", offsetof(game::UnitFields, channelSpell), FieldType::UINT32},
{"modCastSpeed", offsetof(game::UnitFields, modCastSpeed), FieldType::FLOAT},
{"createdBySpell", offsetof(game::UnitFields, createdBySpell), FieldType::UINT32},
{"npcFlags", offsetof(game::UnitFields, npcFlags), FieldType::UINT32},
{"npcEmoteState", offsetof(game::UnitFields, npcEmoteState), FieldType::UINT32},
{"trainingPoints", offsetof(game::UnitFields, trainingPoints), FieldType::UINT32},
{"stat0", offsetof(game::UnitFields, stat0), FieldType::UINT32},
{"stat1", offsetof(game::UnitFields, stat1), FieldType::UINT32},
{"stat2", offsetof(game::UnitFields, stat2), FieldType::UINT32},
{"stat3", offsetof(game::UnitFields, stat3), FieldType::UINT32},
{"stat4", offsetof(game::UnitFields, stat4), FieldType::UINT32},
{"baseMana", offsetof(game::UnitFields, baseMana), FieldType::UINT32},
{"baseHealth", offsetof(game::UnitFields, baseHealth), FieldType::UINT32},
{"bytes2", offsetof(game::UnitFields, bytes2), FieldType::UINT32},
{"attackPower", offsetof(game::UnitFields, attackPower), FieldType::UINT32},
{"attackPowerMods", offsetof(game::UnitFields, attackPowerMods), FieldType::UINT32},
{"attackPowerMultiplier", offsetof(game::UnitFields, attackPowerMultiplier), FieldType::FLOAT},
{"rangedAttackPower", offsetof(game::UnitFields, rangedAttackPower), FieldType::UINT32},
{"rangedAttackPowerMods", offsetof(game::UnitFields, rangedAttackPowerMods), FieldType::UINT32},
{"rangedAttackPowerMultiplier", offsetof(game::UnitFields, rangedAttackPowerMultiplier), FieldType::FLOAT},
{"minRangedDamage", offsetof(game::UnitFields, minRangedDamage), FieldType::FLOAT},
{"maxRangedDamage", offsetof(game::UnitFields, maxRangedDamage), FieldType::FLOAT},
};
const size_t unitFieldsFieldsCount = sizeof(unitFieldsFields) / sizeof(unitFieldsFields[0]);
const ArrayFieldDescriptor unitFieldsArrayFields[] = {
{"virtualItemDisplay", offsetof(game::UnitFields, virtualItemDisplay), FieldType::UINT32, 3},
{"virtualItemInfo", offsetof(game::UnitFields, virtualItemInfo), FieldType::UINT32, 6},
{"aura", offsetof(game::UnitFields, aura), FieldType::UINT32, 48},
{"auraFlags", offsetof(game::UnitFields, auraFlags), FieldType::UINT32, 6},
{"auraLevels", offsetof(game::UnitFields, auraLevels), FieldType::UINT32, 12},
{"auraApplications", offsetof(game::UnitFields, auraApplications), FieldType::UINT32, 12},
{"resistances", offsetof(game::UnitFields, resistances), FieldType::UINT32, 7},
{"powerCostModifier", offsetof(game::UnitFields, powerCostModifier), FieldType::FLOAT, 7},
{"powerCostMultiplier", offsetof(game::UnitFields, powerCostMultiplier), FieldType::FLOAT, 7},
};
const size_t unitFieldsArrayFieldsCount = sizeof(unitFieldsArrayFields) / sizeof(unitFieldsArrayFields[0]);
void InitializeUnitFieldMaps() {
if (unitFieldMapsInitialized) return;
// Build hash maps for O(1) lookups
for (size_t i = 0; i < unitFieldsFieldsCount; ++i) {
unitFieldsFieldMap[unitFieldsFields[i].name] = i;
}
for (size_t i = 0; i < unitFieldsArrayFieldsCount; ++i) {
unitFieldsArrayFieldMap[unitFieldsArrayFields[i].name] = i;
}
unitFieldMapsInitialized = true;
}
}
+26
View File
@@ -0,0 +1,26 @@
//
// Created by pmacc on 1/8/2025.
//
#pragma once
#include "main.hpp"
#include "dbc_fields.hpp"
#include <unordered_map>
#include <string>
#include <cstddef>
namespace Nampower {
// UnitFields field descriptors
extern const FieldDescriptor unitFieldsFields[];
extern const size_t unitFieldsFieldsCount;
extern const ArrayFieldDescriptor unitFieldsArrayFields[];
extern const size_t unitFieldsArrayFieldsCount;
// Hash tables for O(1) field lookups
extern std::unordered_map<std::string, size_t> unitFieldsFieldMap;
extern std::unordered_map<std::string, size_t> unitFieldsArrayFieldMap;
// Initialize unit fields maps (call once before using field lookups)
void InitializeUnitFieldMaps();
}