mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-30 18:50:00 +00:00
423 lines
16 KiB
C++
423 lines
16 KiB
C++
//
|
|
// Created by pmacc on 9/21/2024.
|
|
//
|
|
|
|
#include "helper.hpp"
|
|
#include "offsets.hpp"
|
|
#include "main.hpp"
|
|
#include "dbc_fields.hpp"
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <unordered_map>
|
|
#include <string>
|
|
|
|
namespace Nampower {
|
|
// Lua function pointers
|
|
lua_errorT lua_error = reinterpret_cast<lua_errorT>(Offsets::lua_error);
|
|
lua_gettopT lua_gettop = reinterpret_cast<lua_gettopT>(Offsets::lua_gettop);
|
|
lua_settopT lua_settop = reinterpret_cast<lua_settopT>(Offsets::lua_settop);
|
|
lua_typeT lua_type = reinterpret_cast<lua_typeT>(Offsets::lua_type);
|
|
lua_isstringT lua_isstring = reinterpret_cast<lua_isstringT>(Offsets::lua_isstring);
|
|
lua_isnumberT lua_isnumber = reinterpret_cast<lua_isnumberT>(Offsets::lua_isnumber);
|
|
lua_toflagT lua_toflag = reinterpret_cast<lua_toflagT>(Offsets::lua_toflag);
|
|
lua_tostringT lua_tostring = reinterpret_cast<lua_tostringT>(Offsets::lua_tostring);
|
|
lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber);
|
|
lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
|
|
lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring);
|
|
lua_pushbooleanT lua_pushboolean = reinterpret_cast<lua_pushbooleanT>(Offsets::lua_pushboolean);
|
|
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);
|
|
luaL_refT luaL_ref = reinterpret_cast<luaL_refT>(Offsets::luaL_ref);
|
|
lua_touserdataT lua_touserdata = reinterpret_cast<lua_touserdataT>(Offsets::lua_touserdata);
|
|
lua_rawgetiT lua_rawgeti = reinterpret_cast<lua_rawgetiT>(Offsets::lua_rawgeti);
|
|
luaL_unrefT luaL_unref = reinterpret_cast<luaL_unrefT>(Offsets::luaL_unref);
|
|
|
|
|
|
int32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) {
|
|
struct CachedEntry {
|
|
int32_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 *>(static_cast<uint32_t>(Offsets::CGSpellBook_mKnownSpells) + slot * 4);
|
|
} else if (type == 1) {
|
|
spellId = *reinterpret_cast<uint32_t *>(static_cast<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->Name[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;
|
|
int32_t spellSlot = GetSpellSlotAndTypeForName(spellName, &bookType);
|
|
|
|
uint32_t spellId = 0;
|
|
if (spellSlot >= 0 && spellSlot < 1024) {
|
|
if (bookType == 0) {
|
|
spellId = *reinterpret_cast<uint32_t *>(static_cast<uint32_t>(Offsets::CGSpellBook_mKnownSpells) +
|
|
spellSlot * 4);
|
|
} else {
|
|
spellId = *reinterpret_cast<uint32_t *>(static_cast<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 *>(static_cast<uint32_t>(Offsets::CGSpellBook_mKnownSpells) + slot * 4);
|
|
} else {
|
|
slotSpellId = *reinterpret_cast<uint32_t *>(static_cast<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
|
|
return false;
|
|
}
|
|
|
|
return spell->StartRecoveryCategory == 133;
|
|
}
|
|
|
|
bool SpellIsChanneling(const game::SpellRec *spell) {
|
|
return spell->AttributesEx & game::SPELL_ATTR_EX_IS_CHANNELED ||
|
|
spell->AttributesEx & game::SPELL_ATTR_EX_IS_SELF_CHANNELED;
|
|
}
|
|
|
|
bool SpellIsTargeting(const game::SpellRec *spell) {
|
|
return spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_SOURCE_LOCATION ||
|
|
spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION;
|
|
}
|
|
|
|
bool SpellIsOnSwing(const game::SpellRec *spell) {
|
|
return spell->Attributes & game::SPELL_ATTR_ON_NEXT_SWING_1;
|
|
}
|
|
|
|
bool SpellIsAttackTradeskillOrEnchant(const game::SpellRec *spell) {
|
|
return (
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ATTACK ||
|
|
spell->Attributes & game::SpellAttributes::SPELL_ATTR_TRADESPELL ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_TRADE_SKILL ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_TRANS_DOOR ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ENCHANT_ITEM ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_CREATE_ITEM ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_OPEN_LOCK ||
|
|
spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_OPEN_LOCK_ITEM);
|
|
}
|
|
|
|
bool SpellIsMounting(const game::SpellRec *spell) {
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (spell->Effect[i] == game::SPELL_EFFECT_APPLY_AURA && spell->EffectApplyAuraName[i] == 78) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// if the spell is off cooldown, this will return the gcd, otherwise the cooldown
|
|
uint32_t GetGcdOrCooldownForSpell(uint32_t spellId) {
|
|
uint32_t duration;
|
|
uint64_t startTime;
|
|
uint32_t enable;
|
|
|
|
auto const getSpellCooldown = reinterpret_cast<Spell_C_GetSpellCooldownT>(Offsets::Spell_C_GetSpellCooldown);
|
|
getSpellCooldown(spellId, 0, &duration, &startTime, &enable);
|
|
|
|
if (spellId == 51714 && duration == 1500) {
|
|
// power overwhelming gcd removed but client not updated
|
|
return 0;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
uint32_t GetRemainingGcdOrCooldownForSpell(uint32_t spellId) {
|
|
uint32_t duration;
|
|
uint64_t startTime;
|
|
uint32_t enable;
|
|
|
|
auto const getSpellCooldown = reinterpret_cast<Spell_C_GetSpellCooldownT>(Offsets::Spell_C_GetSpellCooldown);
|
|
getSpellCooldown(spellId, 0, &duration, &startTime, &enable);
|
|
startTime = startTime & 0XFFFFFFFF; // only look at same bits that lua does
|
|
|
|
if (startTime != 0) {
|
|
auto currentLuaTime = GetWowTimeMs() & 0XFFFFFFFF;
|
|
auto remaining = (startTime + duration) - currentLuaTime;
|
|
return uint32_t(remaining);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t GetRemainingCooldownForSpell(uint32_t spellId) {
|
|
uint32_t duration;
|
|
uint64_t startTime;
|
|
uint32_t enable;
|
|
|
|
auto const getSpellCooldown = reinterpret_cast<Spell_C_GetSpellCooldownT>(Offsets::Spell_C_GetSpellCooldown);
|
|
getSpellCooldown(spellId, 0, &duration, &startTime, &enable);
|
|
|
|
startTime = startTime & 0XFFFFFFFF; // only look at same bits that lua does
|
|
|
|
// ignore gcd cooldown by looking for duration > 1.5
|
|
if (startTime != 0 && duration > 1.5) {
|
|
auto currentLuaTime = GetWowTimeMs() & 0XFFFFFFFF;
|
|
auto remaining = (startTime + duration) - currentLuaTime;
|
|
return uint32_t(remaining);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool IsSpellOnCooldown(uint32_t spellId) {
|
|
uint32_t duration;
|
|
uint64_t startTime;
|
|
uint32_t enable;
|
|
|
|
auto const getSpellCooldown = reinterpret_cast<Spell_C_GetSpellCooldownT>(Offsets::Spell_C_GetSpellCooldown);
|
|
getSpellCooldown(spellId, 0, &duration, &startTime, &enable);
|
|
|
|
return startTime != 0 && duration > 1.5;
|
|
}
|
|
|
|
const char kNilGuidStr[] = "0x0000000000000000";
|
|
|
|
void FreeGuidString(char *str) {
|
|
if (str != kNilGuidStr) {
|
|
delete[] str;
|
|
}
|
|
}
|
|
|
|
char *ConvertGuidToString(uint64_t guid) {
|
|
if (!guid) {
|
|
return const_cast<char *>(kNilGuidStr);
|
|
}
|
|
char *guidStr = new char[21]; // 2 for 0x prefix, 18 for the number, and 1 for '\0'
|
|
std::snprintf(guidStr, 21, "0x%016llX", static_cast<unsigned long long>(guid));
|
|
return guidStr;
|
|
}
|
|
|
|
void PushGuidString(uintptr_t *luaState, uint64_t guid) {
|
|
if (!guid) {
|
|
lua_pushstring(luaState, const_cast<char *>(kNilGuidStr));
|
|
return;
|
|
}
|
|
char guidStr[21] = {0};
|
|
std::snprintf(guidStr, sizeof(guidStr), "0x%016llX", static_cast<unsigned long long>(guid));
|
|
lua_pushstring(luaState, guidStr);
|
|
}
|
|
|
|
bool IsReadableMemory(const void *ptr, size_t size) {
|
|
if (!ptr || size == 0) {
|
|
return false;
|
|
}
|
|
|
|
auto current = reinterpret_cast<uintptr_t>(ptr);
|
|
auto remaining = size;
|
|
while (remaining > 0) {
|
|
MEMORY_BASIC_INFORMATION mbi = {};
|
|
if (VirtualQuery(reinterpret_cast<LPCVOID>(current), &mbi, sizeof(mbi)) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (mbi.State != MEM_COMMIT) {
|
|
return false;
|
|
}
|
|
|
|
if ((mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
|
|
return false;
|
|
}
|
|
|
|
auto regionEnd = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
|
|
if (regionEnd <= current) {
|
|
return false;
|
|
}
|
|
|
|
auto chunk = static_cast<size_t>(regionEnd - current);
|
|
if (chunk > remaining) {
|
|
chunk = remaining;
|
|
}
|
|
|
|
remaining -= chunk;
|
|
current += chunk;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SafeReadMemory(const void *address, void *out, size_t size) {
|
|
if (!out || size == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (!IsReadableMemory(address, size)) {
|
|
return false;
|
|
}
|
|
|
|
std::memcpy(out, address, size);
|
|
return true;
|
|
}
|
|
|
|
uint64_t GetUnitGuidFromString(const char *unitToken) {
|
|
if (!unitToken) {
|
|
return 0;
|
|
}
|
|
|
|
if (unitToken[0] == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
// Resolve through the shared unit-token parser first so suffix forms
|
|
// (owner/target/pet, mark1-mark8, nested forms) work consistently.
|
|
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
|
|
const uint64_t resolvedGuid = getGUIDFromName(unitToken);
|
|
if (resolvedGuid != 0) {
|
|
return resolvedGuid;
|
|
}
|
|
|
|
// Fallback: strict raw-hex parse when resolver returns 0.
|
|
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
|
|
char *endPtr = nullptr;
|
|
const auto parsed = std::strtoull(unitToken, &endPtr, 16);
|
|
if (endPtr && *endPtr == '\0') {
|
|
return static_cast<uint64_t>(parsed);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex) {
|
|
if (lua_isnumber(luaState, paramIndex)) {
|
|
// Parameter is a GUID number
|
|
return static_cast<uint64_t>(lua_tonumber(luaState, paramIndex));
|
|
} else if (lua_isstring(luaState, paramIndex)) {
|
|
// Parameter is a unit token or GUID string
|
|
const char *unitToken = lua_tostring(luaState, paramIndex);
|
|
return GetUnitGuidFromString(unitToken);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
float GetNameplateDistance() {
|
|
auto const distanceSquared = *reinterpret_cast<float *>(Offsets::NameplateDistance);
|
|
return sqrtf(distanceSquared);
|
|
}
|
|
|
|
void SetNameplateDistance(float distance) {
|
|
*reinterpret_cast<float *>(Offsets::NameplateDistance) = distance * distance;
|
|
}
|
|
|
|
bool IsTargetingTerrainSpell() {
|
|
const auto s_needTargets = *reinterpret_cast<uint32_t *>(Offsets::SpellNeedsTargets);
|
|
return (s_needTargets & game::SpellCastTargetFlags::TARGET_FLAG_SOURCE_LOCATION) ||
|
|
(s_needTargets & game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION);
|
|
}
|
|
}
|