add logging and disenchant all ability

This commit is contained in:
avitasia
2025-12-26 13:04:16 -08:00
parent 960239c4b7
commit bd9049f8bc
15 changed files with 10031 additions and 9664 deletions
+63
View File
@@ -11,6 +11,7 @@ For installation, configuration, and general usage information, see the main [RE
- [Spell Casting and Queuing](#spell-casting-and-queuing) - [Spell Casting and Queuing](#spell-casting-and-queuing)
- [Cast Information](#cast-information) - [Cast Information](#cast-information)
- [Cooldown Information](#cooldown-information) - [Cooldown Information](#cooldown-information)
- [Utility Functions](#utility-functions)
--- ---
## Performance Optimization - Table References ## Performance Optimization - Table References
@@ -892,3 +893,65 @@ should print 90 for atiesh
#### ChannelStopCastingNextTick() #### ChannelStopCastingNextTick()
Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel. Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel.
---
### Utility Functions
#### DisenchantAll(itemIdOrName) or DisenchantAll(quality)
Automatically disenchants items in your inventory. Can disenchant a specific item by ID/name, or all weapons and armor of a specified quality.
**⚠️ WARNING ⚠️**
**THIS FUNCTION WILL AUTOMATICALLY DISENCHANT ITEMS WITHOUT CONFIRMATION!**
- **Use at your own risk** - there is no undo for disenchanting
- Only disenchants items from **player inventory bags (0-4)** - does NOT touch bank items
- Make sure you have the Disenchant spell and the items are disenchantable before using
- **Always double-check your bags** before running this command
**Parameters:**
**Mode 1: Disenchant by Item ID or Name**
- `itemIdOrName` (number|string): Item ID (number) or item name (string)
- Disenchants all copies of the specified item found in your bags
- Works on any disenchantable item type (weapons, armor, etc.)
**Mode 2: Disenchant by Quality** *(weapons and armor only)*
- `quality` (string): Must be either:
- `"greens"` - Disenchants all uncommon (green) quality weapons and armor
- `"blues"` - Disenchants all rare (blue) quality weapons and armor
- Only affects **weapons** (class 2) and **armor** (class 4)
**Returns:**
- `1` if the first disenchant succeeded
- `0` if no matching items were found or the disenchant failed
**Behavior:**
- Searches player inventory bags (0-4) only - **bank items are protected**
- Finds the first matching item in your inventory
- Casts Disenchant spell on that item
- Automatically continues disenchanting matching items every 4 seconds
- Stops when no more matching items are found or an error occurs
**Examples:**
```lua
-- Disenchant all green weapons and armor in your bags
DisenchantAll("greens")
-- Disenchant all blue weapons and armor in your bags
DisenchantAll("blues")
-- Disenchant a specific item by ID
DisenchantAll(12345)
-- Disenchant a specific item by name
DisenchantAll("Glowing Brightwood Staff")
```
**Important Notes:**
- The function runs continuously until all matching items are disenchanted
- Items must be in your inventory bags (0-4), not in bank, keyring, or equipped
- When using quality mode ("greens"/"blues"), only weapons and armor are affected
- When using item ID/name mode, any disenchantable item can be targeted
- Make sure you have enough bag space for the disenchanting materials
- The function will stop if you run out of matching items or if the disenchant spell fails
- **REVIEW YOUR BAGS CAREFULLY BEFORE USE** - disenchanting cannot be undone!
+9687 -9650
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -96,7 +96,7 @@ namespace game {
const SpellRec *GetSpellInfo(uint32_t spellId) { const SpellRec *GetSpellInfo(uint32_t spellId) {
auto const spellDb = reinterpret_cast<WowClientDB<SpellRec> *>(Offsets::SpellDb); auto const spellDb = reinterpret_cast<WowClientDB<SpellRec> *>(Offsets::SpellDb);
if (spellId > spellDb->m_maxId) if (spellId > spellDb->m_maxId || spellId <= 0)
return nullptr; return nullptr;
return spellDb->m_recordsById[spellId]; return spellDb->m_recordsById[spellId];
+11
View File
@@ -336,6 +336,17 @@ namespace game {
int charges; int charges;
}; };
enum ItemQualities
{
ITEM_QUALITY_POOR = 0, // GREY
ITEM_QUALITY_NORMAL = 1, // WHITE
ITEM_QUALITY_UNCOMMON = 2, // GREEN
ITEM_QUALITY_RARE = 3, // BLUE
ITEM_QUALITY_EPIC = 4, // PURPLE
ITEM_QUALITY_LEGENDARY = 5, // ORANGE
ITEM_QUALITY_ARTIFACT = 6 // LIGHT YELLOW
};
struct CGObject { struct CGObject {
void *vftable; void *vftable;
uintptr_t *m_data; uintptr_t *m_data;
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Nampower {
lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber); lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber);
lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber); lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring); lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring);
lua_pushbooleanT lua_pushboolean = reinterpret_cast<lua_pushbooleanT>(Offsets::lua_pushboolean); // lua_pushbooleanT lua_pushboolean = reinterpret_cast<lua_pushbooleanT>(Offsets::lua_pushboolean); doesn't seem to work properly
lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil); lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil);
lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable); lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable);
lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable); lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable);
+85
View File
@@ -217,6 +217,91 @@ namespace Nampower {
return result; return result;
} }
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality) {
PlayerItemSearchResult result{};
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto playerUnit = game::GetObjectPtr(playerGuid);
if (!playerUnit) {
return result;
}
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
auto matchesItem = [&](game::CGItem_C *item) {
if (!item) {
return false;
}
uint32_t itemId = game::GetItemId(item);
auto *itemStats = GetItemStats(itemId);
if (!itemStats) {
return false;
}
// Check if item is weapon or armor
if (itemStats->m_class != game::ITEM_CLASS_WEAPON &&
itemStats->m_class != game::ITEM_CLASS_ARMOR) {
return false;
}
if (itemStats->m_quality != quality) {
return false;
}
return true;
};
for (uint32_t slot = 0; slot <= 18; slot++) {
auto item = getBagItem(inventory, slot);
if (matchesItem(item)) {
result.item = item;
result.bagIndex = EQUIPPED_BAG_INDEX;
result.slot = slot;
return result;
}
}
for (uint32_t slot = 23; slot <= 38; slot++) {
auto item = getBagItem(inventory, slot);
if (matchesItem(item)) {
result.item = item;
result.bagIndex = 0;
result.slot = slot;
return result;
}
}
for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) {
uint64_t containerGuid = getContainerGuid(bagIndex - 1);
if (containerGuid == 0) continue;
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) continue;
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) continue;
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
auto item = getBagItem(bagPtr, slot);
if (matchesItem(item)) {
result.item = item;
result.bagIndex = bagIndex;
result.slot = slot;
return result;
}
}
}
// don't search bank
return result;
}
std::string escapeJsonString(const char *str) { std::string escapeJsonString(const char *str) {
if (!str) return "null"; if (!str) return "null";
+1
View File
@@ -100,6 +100,7 @@ namespace Nampower {
void CacheItemNameToId(const char *itemName, uint32_t itemId); void CacheItemNameToId(const char *itemName, uint32_t itemId);
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName); bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName);
PlayerItemSearchResult FindPlayerItem(uint32_t searchItemId, const char *searchItemName); PlayerItemSearchResult FindPlayerItem(uint32_t searchItemId, const char *searchItemName);
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality);
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr); uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr);
} }
+21 -2
View File
@@ -64,6 +64,10 @@ namespace Nampower {
uint32_t gBufferTimeMs; // adjusts dynamically depending on errors uint32_t gBufferTimeMs; // adjusts dynamically depending on errors
uint32_t gDisenchantItemId = 0;
int32_t gDisenchantQuality = -1; // -1 = unset
uint32_t gNextDisenchantTimeMs = 0;
bool gForceQueueCast; bool gForceQueueCast;
bool gNoQueueCast; bool gNoQueueCast;
bool gQueuesProcessed; bool gQueuesProcessed;
@@ -97,7 +101,7 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<SetCVarT> > gSetCVarDetour; std::unique_ptr<hadesmem::PatchDetour<SetCVarT> > gSetCVarDetour;
std::unique_ptr<hadesmem::PatchDetour<CGSpellBook_CastSpellT> > gCGSpellBook_CastSpellDetour; std::unique_ptr<hadesmem::PatchDetour<CGSpellBook_CastSpellT> > gCGSpellBook_CastSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<CastSpellT> > gCastDetour; std::unique_ptr<hadesmem::PatchDetour<Spell_C_CastSpellT> > gCastDetour;
std::unique_ptr<hadesmem::PatchDetour<SendCastT> > gSendCastDetour; std::unique_ptr<hadesmem::PatchDetour<SendCastT> > gSendCastDetour;
std::unique_ptr<hadesmem::PatchDetour<CancelSpellT> > gCancelSpellDetour; std::unique_ptr<hadesmem::PatchDetour<CancelSpellT> > gCancelSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<SignalEventT> > gSignalEventDetour; std::unique_ptr<hadesmem::PatchDetour<SignalEventT> > gSignalEventDetour;
@@ -520,6 +524,18 @@ namespace Nampower {
// script ran, stop processing // script ran, stop processing
return true; return true;
} }
// Check for disenchant all
if (gDisenchantItemId != 0 || gDisenchantQuality != -1) {
auto currentTime = GetTime();
// Try to disenchant when timer has elapsed
if (gNextDisenchantTimeMs <= currentTime) {
if (TryDisenchant()) {
return true;
}
}
}
} }
gQueuesProcessed = true; gQueuesProcessed = true;
@@ -1121,7 +1137,7 @@ namespace Nampower {
process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook); process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook);
gCGActionBar_UseActionDetour = createHook<CGActionBar_UseActionT>( gCGActionBar_UseActionDetour = createHook<CGActionBar_UseActionT>(
process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook); process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook);
gCastDetour = createHook<CastSpellT>(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook); gCastDetour = createHook<Spell_C_CastSpellT>(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook);
gSendCastDetour = createHook<SendCastT>(process, Offsets::SendCast, &SendCastHook); gSendCastDetour = createHook<SendCastT>(process, Offsets::SendCast, &SendCastHook);
gCancelSpellDetour = createHook<CancelSpellT>(process, Offsets::CancelSpell, &CancelSpellHook); gCancelSpellDetour = createHook<CancelSpellT>(process, Offsets::CancelSpell, &CancelSpellHook);
gCastResultHandlerDetour = createHook<PacketHandlerT>(process, Offsets::CastResultHandler, gCastResultHandlerDetour = createHook<PacketHandlerT>(process, Offsets::CastResultHandler,
@@ -1373,6 +1389,9 @@ namespace Nampower {
char getTrinkets[] = "GetTrinkets"; char getTrinkets[] = "GetTrinkets";
RegisterLuaFunction(getTrinkets, reinterpret_cast<uintptr_t *>(Script_GetTrinkets)); RegisterLuaFunction(getTrinkets, reinterpret_cast<uintptr_t *>(Script_GetTrinkets));
char disenchantAll[] = "DisenchantAll";
RegisterLuaFunction(disenchantAll, reinterpret_cast<uintptr_t *>(Script_DisenchantAll));
} }
void load() { void load() {
+7 -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 BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer
constexpr uint32_t MAJOR_VERSION = 2; constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 20; constexpr uint32_t MINOR_VERSION = 21;
constexpr uint32_t PATCH_VERSION = 1; constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_REGISTRYINDEX = -10000;
constexpr int32_t LUA_GLOBALSINDEX = -10001; constexpr int32_t LUA_GLOBALSINDEX = -10001;
@@ -42,6 +42,10 @@ namespace Nampower {
extern uint32_t gBufferTimeMs; // adjusts dynamically depending on errors extern uint32_t gBufferTimeMs; // adjusts dynamically depending on errors
extern uint32_t gDisenchantItemId;
extern int32_t gDisenchantQuality;
extern uint32_t gNextDisenchantTimeMs;
extern bool gForceQueueCast; extern bool gForceQueueCast;
extern bool gNoQueueCast; extern bool gNoQueueCast;
@@ -70,7 +74,7 @@ namespace Nampower {
using RangeCheckSelectedT = bool (__fastcall *)(uintptr_t *playerUnit, const game::SpellRec *, using RangeCheckSelectedT = bool (__fastcall *)(uintptr_t *playerUnit, const game::SpellRec *,
std::uint64_t targetGuid, char ignoreErrors); std::uint64_t targetGuid, char ignoreErrors);
using CGSpellBook_CastSpellT = void (__fastcall *)(uint32_t spellSlot, int bookType, uint64_t target); using CGSpellBook_CastSpellT = void (__fastcall *)(uint32_t spellSlot, int bookType, uint64_t target);
using CastSpellT = bool (__fastcall *)(uintptr_t *playerUnit, uint32_t spellId, uintptr_t *item, using Spell_C_CastSpellT = bool (__fastcall *)(uintptr_t *playerUnit, uint32_t spellId, game::CGItem_C *item,
std::uint64_t targetGuid); std::uint64_t targetGuid);
using SendCastT = void (__fastcall *)(game::SpellCast *, char unk); using SendCastT = void (__fastcall *)(game::SpellCast *, char unk);
using CancelSpellT = void (__fastcall *)(bool, bool, game::SpellCastResult); using CancelSpellT = void (__fastcall *)(bool, bool, game::SpellCastResult);
+123
View File
@@ -611,4 +611,127 @@ namespace Nampower {
return 0; return 0;
} }
bool TryDisenchant() {
DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId);
if (gDisenchantQuality < 0 && gDisenchantItemId == 0) {
return false;
}
// Find the item in player inventory
PlayerItemSearchResult itemSearchResult;
if (gDisenchantItemId != 0) {
// Item-specific disenchanting
itemSearchResult = FindPlayerItem(gDisenchantItemId, nullptr);
} else {
// Quality-based disenchanting (weapons and armor only)
itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality);
}
if (!itemSearchResult.found()) {
// Item not found, stop disenchanting
DEBUG_LOG("No disenchantable item found, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false;
}
// Get the item GUID
auto item = itemSearchResult.item;
if (!item || !item->object.m_obj) {
// Invalid item, stop disenchanting
DEBUG_LOG("Invalid item, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false;
}
uint64_t itemGuid = item->object.m_obj->m_guid;
// Get player unit for casting
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto playerUnit = game::GetObjectPtr(playerGuid);
if (!playerUnit) {
// No player unit, stop disenchanting
DEBUG_LOG("No player unit, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false;
}
// Set timer for next disenchant attempt (5 seconds to allow time for inventory to update)
auto currentTime = GetTime();
gNextDisenchantTimeMs = currentTime + 5000;
// Cast Disenchant spell (13262) on the item
auto const castSpell = reinterpret_cast<Spell_C_CastSpellT>(Offsets::Spell_C_CastSpell);
bool success = castSpell(playerUnit, 13262, nullptr, itemGuid);
if (!success) {
// Cast failed, stop disenchanting
DEBUG_LOG("Cast failed, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false;
}
return true;
}
uint32_t Script_DisenchantAll(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: DisenchantAll(itemIdOrName) or DisenchantAll(quality)\n"
"quality: \"greens\" for uncommon, \"blues\" for rare\n"
"itemIdOrName: item ID (number) or item name (string)");
return 0;
}
// Reset both modes
gDisenchantItemId = 0;
gDisenchantQuality = -1;
// Check number first (lua_isstring will also match numbers)
if (lua_isnumber(luaState, 1)) {
// It's an item ID
gDisenchantItemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
} else if (lua_isstring(luaState, 1)) {
// It's a string - check if it's quality-based or item name
const char *param = lua_tostring(luaState, 1);
if (_stricmp(param, "greens") == 0) {
gDisenchantQuality = game::ITEM_QUALITY_UNCOMMON; // 2
} else if (_stricmp(param, "blues") == 0) {
gDisenchantQuality = game::ITEM_QUALITY_RARE; // 3
} else {
// It's an item name
uint32_t cachedItemId = GetItemIdFromCache(param);
if (cachedItemId != 0) {
gDisenchantItemId = cachedItemId;
} else {
// Try to find the item to get its ID
auto itemSearchResult = FindPlayerItem(0, param);
if (itemSearchResult.found()) {
gDisenchantItemId = game::GetItemId(itemSearchResult.item);
// Cache the item name for future lookups
CacheItemNameToId(param, gDisenchantItemId);
} else {
// Item not found
lua_pushnumber(luaState, 0);
return 1;
}
}
}
}
// Try the first disenchant
bool success = TryDisenchant();
lua_pushnumber(luaState, success ? 1 : 0);
return 1;
}
} }
+4
View File
@@ -30,5 +30,9 @@ namespace Nampower {
uint32_t Script_StartItemExport(uintptr_t *luaState); uint32_t Script_StartItemExport(uintptr_t *luaState);
bool TryDisenchant();
uint32_t Script_DisenchantAll(uintptr_t *luaState);
bool RunQueuedScript(int priority); bool RunQueuedScript(int priority);
} }
+17 -5
View File
@@ -299,7 +299,7 @@ namespace Nampower {
void SaveCastParams(CastSpellParams *params, void SaveCastParams(CastSpellParams *params,
uint32_t *casterUnit, uint32_t *casterUnit,
uint32_t spellId, uint32_t spellId,
uintptr_t *item, game::CGItem_C *item,
std::uint64_t guid, std::uint64_t guid,
uint32_t gcDCategory, uint32_t gcDCategory,
uint32_t castTimeMs, uint32_t castTimeMs,
@@ -444,7 +444,7 @@ namespace Nampower {
bool bool
Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId,
uintptr_t *item, game::CGItem_C *item,
std::uint64_t guid) { std::uint64_t guid) {
// save the detour to allow quickly calling this hook // save the detour to allow quickly calling this hook
castSpellDetour = detour; castSpellDetour = detour;
@@ -459,7 +459,7 @@ namespace Nampower {
DEBUG_LOG("Ignoring non active player cast of spell " << game::GetSpellName(spellId) << " " << spellId); DEBUG_LOG("Ignoring non active player cast of spell " << game::GetSpellName(spellId) << " " << spellId);
// just call original function if caster is not the active player // just call original function if caster is not the active player
// happens with Doomguard rain of fire // happens with Doomguard rain of fire
auto const castSpell = detour->GetTrampolineT<CastSpellT>(); auto const castSpell = detour->GetTrampolineT<Spell_C_CastSpellT>();
return castSpell(casterUnit, spellId, item, guid); return castSpell(casterUnit, spellId, item, guid);
} }
@@ -478,6 +478,7 @@ namespace Nampower {
if (casterUnit == playerUnit && gUserSettings.preventMountingWhenBuffCapped) { if (casterUnit == playerUnit && gUserSettings.preventMountingWhenBuffCapped) {
// Check for mount spell when player is buff capped // Check for mount spell when player is buff capped
if (SpellIsMounting(spell)) { if (SpellIsMounting(spell)) {
// check if they are not already mounted
if (playerUnit && game::UnitIsBuffCapped(playerUnit) && game::UnitGetMountDisplayId(playerUnit) == 0) { if (playerUnit && game::UnitIsBuffCapped(playerUnit) && game::UnitGetMountDisplayId(playerUnit) == 0) {
DEBUG_LOG("Blocking mount spell " << spellName << " due to buff cap"); DEBUG_LOG("Blocking mount spell " << spellName << " due to buff cap");
@@ -562,7 +563,7 @@ namespace Nampower {
gNextCastId++; gNextCastId++;
// try to cast the spell // try to cast the spell
auto const castSpell = detour->GetTrampolineT<CastSpellT>(); auto const castSpell = detour->GetTrampolineT<Spell_C_CastSpellT>();
auto ret = castSpell(casterUnit, spellId, item, guid); auto ret = castSpell(casterUnit, spellId, item, guid);
TriggerSpellCastEvent(ret, spellId, ON_SWING, guid, itemId); TriggerSpellCastEvent(ret, spellId, ON_SWING, guid, itemId);
@@ -587,7 +588,7 @@ namespace Nampower {
return ret; return ret;
} }
auto const castSpell = detour->GetTrampolineT<CastSpellT>(); auto const castSpell = detour->GetTrampolineT<Spell_C_CastSpellT>();
auto effectiveCastEndMs = EffectiveCastEndMs(); auto effectiveCastEndMs = EffectiveCastEndMs();
auto remainingEffectiveCastTime = (effectiveCastEndMs > currentTime) ? effectiveCastEndMs - currentTime : 0; auto remainingEffectiveCastTime = (effectiveCastEndMs > currentTime) ? effectiveCastEndMs - currentTime : 0;
@@ -877,6 +878,10 @@ namespace Nampower {
} }
bool doesSpellApplyAura(const game::SpellRec *spell) { bool doesSpellApplyAura(const game::SpellRec *spell) {
if (!spell) {
return false;
}
for (unsigned int i: spell->Effect) { for (unsigned int i: spell->Effect) {
switch (i) { switch (i) {
case game::SPELL_EFFECT_APPLY_AURA: case game::SPELL_EFFECT_APPLY_AURA:
@@ -977,6 +982,11 @@ namespace Nampower {
auto const castByActivePlayer = activePlayerGuid == *casterGUID; auto const castByActivePlayer = activePlayerGuid == *casterGUID;
auto const spell = game::GetSpellInfo(spellId); auto const spell = game::GetSpellInfo(spellId);
if (!spell || spell->Id <= 0) {
DEBUG_LOG("Unable to determine spell information in SpellGo for " << spellId);
return;
}
if (castByActivePlayer) { if (castByActivePlayer) {
auto const currentTime = GetTime(); auto const currentTime = GetTime();
// only care about our own casts // only care about our own casts
@@ -1006,6 +1016,8 @@ namespace Nampower {
if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) { if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) {
auto targetGuidVal = targetGUID ? *targetGUID : *casterGUID; auto targetGuidVal = targetGUID ? *targetGUID : *casterGUID;
DEBUG_LOG("Triggering aura cast event for " << game::GetSpellName(spell->Id)
<< " (" << spell->Id << ") caster: " << *casterGUID << " target: " << targetGuidVal);
TriggerAuraCastEvent(spell, *casterGUID, targetGuidVal, activePlayerGuid, castByActivePlayer); TriggerAuraCastEvent(spell, *casterGUID, targetGuidVal, activePlayerGuid, castByActivePlayer);
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ namespace Nampower {
void CGActionBar_UseActionHook(hadesmem::PatchDetourBase *detour, uint32_t actionSlot, int param_2, int param_3); void CGActionBar_UseActionHook(hadesmem::PatchDetourBase *detour, uint32_t actionSlot, int param_2, int param_3);
bool Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, uintptr_t *item, bool Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, game::CGItem_C *item,
std::uint64_t guid); std::uint64_t guid);
void void
+8
View File
@@ -52,6 +52,14 @@ namespace Nampower {
return; return;
} }
// Check for Disenchant (13262) failure and stop the disenchant loop
if (spellId == 13262) {
DEBUG_LOG("Disenchant spell failed with result " << int(spellResult) << ", stopping disenchant loop");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
}
// ignore SPELL_FAILED_CANT_DO_THAT_YET for arcane surge gets sent all the time after success // ignore SPELL_FAILED_CANT_DO_THAT_YET for arcane surge gets sent all the time after success
if (spellResult == game::SpellCastResult::SPELL_FAILED_CANT_DO_THAT_YET && if (spellResult == game::SpellCastResult::SPELL_FAILED_CANT_DO_THAT_YET &&
(spellId == 51933 || (spellId == 51933 ||
+1 -1
View File
@@ -76,7 +76,7 @@ struct CastSpellParams {
uint32_t *casterUnit; uint32_t *casterUnit;
uint32_t spellId; uint32_t spellId;
uintptr_t *item; game::CGItem_C *item;
uint64_t guid; uint64_t guid;
/* *********************** */ /* *********************** */