mirror of
https://github.com/brues-code/nampower.git
synced 2026-10-09 15:10:01 +00:00
improvements to disenchant all and cast retry logic
This commit is contained in:
+9758
-9735
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ set(SOURCE_FILES
|
||||
cdatastore.hpp
|
||||
cdatastore.cpp
|
||||
castqueue.h
|
||||
cooldown.hpp
|
||||
game.hpp
|
||||
game.cpp
|
||||
main.hpp
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Cooldown-related types and functions
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Nampower {
|
||||
struct CooldownDetail {
|
||||
bool isOnCooldown = false;
|
||||
uint32_t cooldownRemainingMs = 0;
|
||||
|
||||
bool itemHasActiveSpell = false;
|
||||
uint32_t itemId = 0;
|
||||
uint32_t itemActiveSpellId = 0;
|
||||
|
||||
uint32_t individualStartMs = 0;
|
||||
uint32_t individualDurationMs = 0;
|
||||
uint32_t individualRemainingMs = 0;
|
||||
bool isOnIndividualCooldown = false;
|
||||
|
||||
uint32_t categoryId = 0;
|
||||
uint32_t categoryStartMs = 0;
|
||||
uint32_t categoryDurationMs = 0;
|
||||
uint32_t categoryRemainingMs = 0;
|
||||
bool isOnCategoryCooldown = false;
|
||||
|
||||
uint32_t gcdCategoryId = 0;
|
||||
uint32_t gcdCategoryStartMs = 0;
|
||||
uint32_t gcdCategoryDurationMs = 0;
|
||||
uint32_t gcdCategoryRemainingMs = 0;
|
||||
bool isOnGcdCategoryCooldown = false;
|
||||
};
|
||||
|
||||
CooldownDetail GetItemCooldownDetail(uint32_t itemId);
|
||||
}
|
||||
@@ -8,36 +8,12 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "cooldown.hpp"
|
||||
|
||||
namespace Nampower {
|
||||
// Reusable table reference to reduce memory allocations
|
||||
static int cooldownDetailTableRef = LUA_REFNIL;
|
||||
|
||||
struct CooldownDetail {
|
||||
bool isOnCooldown = false;
|
||||
uint32_t cooldownRemainingMs = 0;
|
||||
|
||||
bool itemHasActiveSpell = false;
|
||||
uint32_t itemId = 0;
|
||||
uint32_t itemActiveSpellId = 0;
|
||||
|
||||
uint32_t individualStartMs = 0;
|
||||
uint32_t individualDurationMs = 0;
|
||||
uint32_t individualRemainingMs = 0;
|
||||
bool isOnIndividualCooldown = false;
|
||||
|
||||
uint32_t categoryId = 0;
|
||||
uint32_t categoryStartMs = 0;
|
||||
uint32_t categoryDurationMs = 0;
|
||||
uint32_t categoryRemainingMs = 0;
|
||||
bool isOnCategoryCooldown = false;
|
||||
|
||||
uint32_t gcdCategoryId = 0;
|
||||
uint32_t gcdCategoryStartMs = 0;
|
||||
uint32_t gcdCategoryDurationMs = 0;
|
||||
uint32_t gcdCategoryRemainingMs = 0;
|
||||
bool isOnGcdCategoryCooldown = false;
|
||||
};
|
||||
|
||||
game::SpellHistoryEntry *GetSpellHistoryHead() {
|
||||
auto const spellHistoryAddr = static_cast<uintptr_t>(Offsets::SpellHistories);
|
||||
|
||||
|
||||
+13
-11
@@ -217,7 +217,7 @@ namespace Nampower {
|
||||
return result;
|
||||
}
|
||||
|
||||
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound) {
|
||||
PlayerItemSearchResult FindPlayerDisenchantItem(uint32_t qualityBitmask, bool includeSoulbound) {
|
||||
PlayerItemSearchResult result{};
|
||||
|
||||
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
|
||||
@@ -261,22 +261,24 @@ namespace Nampower {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemStats->m_quality != quality) {
|
||||
// Check if item quality matches the bitmask
|
||||
uint32_t qualityBit = 0;
|
||||
if (itemStats->m_quality == game::ITEM_QUALITY_UNCOMMON) {
|
||||
qualityBit = 0x01; // Green
|
||||
} else if (itemStats->m_quality == game::ITEM_QUALITY_RARE) {
|
||||
qualityBit = 0x02; // Blue
|
||||
} else if (itemStats->m_quality == game::ITEM_QUALITY_EPIC) {
|
||||
qualityBit = 0x04; // Purple
|
||||
}
|
||||
|
||||
if ((qualityBitmask & qualityBit) == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Skip equipped gear slots (0-18) - only search bags
|
||||
|
||||
for (uint32_t slot = 23; slot <= 38; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ namespace Nampower {
|
||||
void CacheItemNameToId(const char *itemName, uint32_t itemId);
|
||||
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName);
|
||||
PlayerItemSearchResult FindPlayerItem(uint32_t searchItemId, const char *searchItemName);
|
||||
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound = false);
|
||||
PlayerItemSearchResult FindPlayerDisenchantItem(uint32_t qualityBitmask, bool includeSoulbound = false);
|
||||
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr);
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -65,7 +65,7 @@ namespace Nampower {
|
||||
uint32_t gBufferTimeMs; // adjusts dynamically depending on errors
|
||||
|
||||
uint32_t gDisenchantItemId = 0;
|
||||
int32_t gDisenchantQuality = -1; // -1 = unset
|
||||
uint32_t gDisenchantQuality = 0; // 0 = unset
|
||||
bool gDisenchantIncludeSoulbound = false;
|
||||
uint32_t gNextDisenchantTimeMs = 0;
|
||||
|
||||
@@ -373,7 +373,7 @@ namespace Nampower {
|
||||
|
||||
void ResetDisenchantState() {
|
||||
gDisenchantItemId = 0;
|
||||
gDisenchantQuality = -1;
|
||||
gDisenchantQuality = 0;
|
||||
gDisenchantIncludeSoulbound = false;
|
||||
gNextDisenchantTimeMs = 0;
|
||||
}
|
||||
@@ -534,7 +534,7 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
// Check for disenchant all
|
||||
if (gDisenchantItemId != 0 || gDisenchantQuality != -1) {
|
||||
if (gDisenchantItemId != 0 || gDisenchantQuality != 0) {
|
||||
auto currentTime = GetTime();
|
||||
|
||||
// Try to disenchant when timer has elapsed
|
||||
|
||||
+7
-2
@@ -29,8 +29,13 @@ namespace Nampower {
|
||||
constexpr uint32_t BUFFER_INCREASE_FREQUENCY = 5000; // time in ms between changes to raise buffer
|
||||
constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer
|
||||
|
||||
// Disenchant quality bitmasks
|
||||
constexpr uint32_t DISENCHANT_QUALITY_GREEN = 0x01; // Uncommon
|
||||
constexpr uint32_t DISENCHANT_QUALITY_BLUE = 0x02; // Rare
|
||||
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
|
||||
|
||||
constexpr uint32_t MAJOR_VERSION = 2;
|
||||
constexpr uint32_t MINOR_VERSION = 22;
|
||||
constexpr uint32_t MINOR_VERSION = 23;
|
||||
constexpr uint32_t PATCH_VERSION = 0;
|
||||
|
||||
constexpr int32_t LUA_REGISTRYINDEX = -10000;
|
||||
@@ -43,7 +48,7 @@ namespace Nampower {
|
||||
extern uint32_t gBufferTimeMs; // adjusts dynamically depending on errors
|
||||
|
||||
extern uint32_t gDisenchantItemId;
|
||||
extern int32_t gDisenchantQuality;
|
||||
extern uint32_t gDisenchantQuality;
|
||||
extern bool gDisenchantIncludeSoulbound;
|
||||
extern uint32_t gNextDisenchantTimeMs;
|
||||
|
||||
|
||||
@@ -613,7 +613,7 @@ namespace Nampower {
|
||||
|
||||
bool TryDisenchant() {
|
||||
DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId);
|
||||
if (gDisenchantQuality < 0 && gDisenchantItemId == 0) {
|
||||
if (gDisenchantQuality == 0 && gDisenchantItemId == 0) {
|
||||
ResetDisenchantState();
|
||||
return false;
|
||||
}
|
||||
@@ -631,6 +631,7 @@ namespace Nampower {
|
||||
if (!itemSearchResult.found()) {
|
||||
// Item not found, stop disenchanting
|
||||
DEBUG_LOG("No disenchantable item found, stopping disenchant");
|
||||
LuaCall("DEFAULT_CHAT_FRAME:AddMessage(\"No more items to disenchant.\")");
|
||||
ResetDisenchantState();
|
||||
return false;
|
||||
}
|
||||
@@ -656,6 +657,14 @@ namespace Nampower {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Display which item is being disenchanted
|
||||
char luaCode[256];
|
||||
snprintf(luaCode, sizeof(luaCode),
|
||||
"local itemLink = GetContainerItemLink(%d, %d); "
|
||||
"if itemLink then DEFAULT_CHAT_FRAME:AddMessage(\"Disenchanting \" .. itemLink .. \" move during cast to cancel.\") end",
|
||||
itemSearchResult.bagIndex, itemSearchResult.slot + 1); // Lua slots are 1-indexed
|
||||
LuaCall(luaCode);
|
||||
|
||||
// Set timer for next disenchant attempt (5 seconds to allow time for inventory to update)
|
||||
auto currentTime = GetTime();
|
||||
gNextDisenchantTimeMs = currentTime + 5000;
|
||||
@@ -667,6 +676,7 @@ namespace Nampower {
|
||||
if (!success) {
|
||||
// Cast failed, stop disenchanting
|
||||
DEBUG_LOG("Cast failed, stopping disenchant");
|
||||
LuaCall("DEFAULT_CHAT_FRAME:AddMessage(\"Disenchant interrupted or failed.\")");
|
||||
ResetDisenchantState();
|
||||
return false;
|
||||
}
|
||||
@@ -679,7 +689,7 @@ namespace Nampower {
|
||||
|
||||
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
|
||||
lua_error(luaState, "Usage: DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])\n"
|
||||
"quality: \"greens\" for uncommon, \"blues\" for rare\n"
|
||||
"quality: \"greens\", \"blues\", \"purples\", or combinations like \"greens|blues\" or \"greens|blues|purples\"\n"
|
||||
"itemIdOrName: item ID (number) or item name (string)\n"
|
||||
"includeSoulbound: optional number - pass 1 to include soulbound items (defaults to 0)");
|
||||
return 0;
|
||||
@@ -711,10 +721,36 @@ namespace Nampower {
|
||||
} 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
|
||||
|
||||
// Parse quality combinations (e.g., "greens", "blues|purples", "greens|blues|purples")
|
||||
bool isQualityString = false;
|
||||
uint32_t qualityBitmask = 0;
|
||||
|
||||
// Make a mutable copy of the string for tokenizing
|
||||
std::string paramStr(param);
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
|
||||
while (end != std::string::npos) {
|
||||
end = paramStr.find('|', start);
|
||||
std::string token = paramStr.substr(start, (end == std::string::npos) ? std::string::npos : end - start);
|
||||
|
||||
if (_stricmp(token.c_str(), "greens") == 0) {
|
||||
qualityBitmask |= DISENCHANT_QUALITY_GREEN;
|
||||
isQualityString = true;
|
||||
} else if (_stricmp(token.c_str(), "blues") == 0) {
|
||||
qualityBitmask |= DISENCHANT_QUALITY_BLUE;
|
||||
isQualityString = true;
|
||||
} else if (_stricmp(token.c_str(), "purples") == 0) {
|
||||
qualityBitmask |= DISENCHANT_QUALITY_PURPLE;
|
||||
isQualityString = true;
|
||||
}
|
||||
|
||||
start = (end == std::string::npos) ? std::string::npos : end + 1;
|
||||
}
|
||||
|
||||
if (isQualityString) {
|
||||
gDisenchantQuality = qualityBitmask;
|
||||
} else {
|
||||
// It's an item name
|
||||
uint32_t cachedItemId = GetItemIdFromCache(param);
|
||||
|
||||
+12
-3
@@ -452,6 +452,9 @@ namespace Nampower {
|
||||
// it is possible when spamming to attempt to cast before queues are processed
|
||||
processQueues();
|
||||
|
||||
// clear flag to avoid canceling spell cast due to cooldown
|
||||
gCastData.ignoreCancelDueToCooldown = false;
|
||||
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = (playerGuid > 0) ? game::GetObjectPtr(playerGuid) : nullptr;
|
||||
|
||||
@@ -843,7 +846,7 @@ namespace Nampower {
|
||||
// haven't gotten spell result from the previous cast yet, probably due to latency.
|
||||
// simulate a cancel to clear the cast bar but only when there should be a cast time
|
||||
// mining/herbing have cast time but aren't on Gcd, don't cancel them
|
||||
if (!ret && gLastCastData.castTimeMs > 0 && gLastCastData.wasOnGcd) {
|
||||
if (!ret && gLastCastData.castTimeMs > 0 && gLastCastData.wasOnGcd && !gCastData.ignoreCancelDueToCooldown) {
|
||||
if (*reinterpret_cast<int *>(Offsets::SpellIsTargeting) == 0 && !gCastData.pendingOnSwingCast &&
|
||||
!IsSpellOnCooldown(spellId)) {
|
||||
DEBUG_LOG("Canceling spell cast due to previous spell having cast time of "
|
||||
@@ -1016,8 +1019,6 @@ namespace Nampower {
|
||||
if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1026,14 @@ namespace Nampower {
|
||||
void
|
||||
CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyServer,
|
||||
game::SpellCastResult reason) {
|
||||
// avoid canceling spell cast if it failed due to cooldown
|
||||
if (gCastData.ignoreCancelDueToCooldown &&
|
||||
(reason == game::SpellCastResult::SPELL_FAILED_NOT_READY ||
|
||||
reason == game::SpellCastResult::SPELL_FAILED_ITEM_NOT_READY)) {
|
||||
DEBUG_LOG("Ignoring cancel spell cast due to cooldown, reason:" << int(reason));
|
||||
return;
|
||||
}
|
||||
|
||||
// triggered by us, reset the cast bar
|
||||
if (notifyServer) {
|
||||
ResetCastFlags();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "logging.hpp"
|
||||
#include "spellcast.hpp"
|
||||
#include "helper.hpp"
|
||||
#include "cooldown.hpp"
|
||||
|
||||
namespace Nampower {
|
||||
uint32_t lastCastResultTimeMs;
|
||||
@@ -88,6 +89,8 @@ namespace Nampower {
|
||||
if (castParams) {
|
||||
// mark as failed so it can be cast again
|
||||
castParams->castResult = CastResult::SERVER_FAILURE;
|
||||
// set flag to avoid canceling spell cast
|
||||
gCastData.ignoreCancelDueToCooldown = true;
|
||||
}
|
||||
|
||||
// check if we should do cooldown queuing
|
||||
@@ -120,6 +123,21 @@ namespace Nampower {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (spellResult == game::SpellCastResult::SPELL_FAILED_ITEM_NOT_READY) {
|
||||
// check if item is just on cooldown still
|
||||
auto castParams = gCastHistory.findSpellId(spellId);
|
||||
if (castParams && castParams->item) {
|
||||
auto const itemId = game::GetItemId(castParams->item);
|
||||
auto const itemCooldown = GetItemCooldownDetail(itemId);
|
||||
if (itemCooldown.cooldownRemainingMs > 0) {
|
||||
// mark as failed so it can be cast again
|
||||
castParams->castResult = CastResult::SERVER_FAILURE;
|
||||
// set flag to avoid canceling spell cast
|
||||
gCastData.ignoreCancelDueToCooldown = true;
|
||||
DEBUG_LOG("Item " << itemId << " is still on cooldown " << itemCooldown.cooldownRemainingMs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gUserSettings.retryServerRejectedSpells) {
|
||||
|
||||
@@ -133,6 +133,7 @@ struct CastData {
|
||||
uint32_t numRetries;
|
||||
|
||||
bool cancellingSpell;
|
||||
bool ignoreCancelDueToCooldown;
|
||||
|
||||
bool channeling;
|
||||
bool cancelChannelNextTick;
|
||||
|
||||
Reference in New Issue
Block a user