refactor and make DisenchantAll default to ignoring soulbound

This commit is contained in:
avitasia
2025-12-26 15:10:20 -08:00
parent d9e764181c
commit 84520d1c20
9 changed files with 8750 additions and 8719 deletions
+11 -7
View File
@@ -930,13 +930,15 @@ Will stop channeling early on the next tick if you have queue channeling spells
### Utility Functions ### Utility Functions
#### DisenchantAll(itemIdOrName) or DisenchantAll(quality) #### DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])
Automatically disenchants items in your inventory. Can disenchant a specific item by ID/name, or all weapons and armor of a specified 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 ⚠️** **⚠️ WARNING ⚠️**
**THIS FUNCTION WILL AUTOMATICALLY DISENCHANT ITEMS WITHOUT CONFIRMATION!** **THIS FUNCTION WILL AUTOMATICALLY DISENCHANT ITEMS WITHOUT CONFIRMATION!**
- **Use at your own risk** - there is no undo for disenchanting - **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 - Only disenchants items from **player inventory bags (0-4)** - does NOT touch bank items
- **Quest items are ALWAYS protected** regardless of settings
- **Soulbound items are protected by default** (can be overridden with optional parameter)
- Make sure you have the Disenchant spell and the items are disenchantable before using - Make sure you have the Disenchant spell and the items are disenchantable before using
- **Always double-check your bags** before running this command - **Always double-check your bags** before running this command
@@ -946,12 +948,14 @@ Automatically disenchants items in your inventory. Can disenchant a specific ite
- `itemIdOrName` (number|string): Item ID (number) or item name (string) - `itemIdOrName` (number|string): Item ID (number) or item name (string)
- Disenchants all copies of the specified item found in your bags - Disenchants all copies of the specified item found in your bags
- Works on any disenchantable item type (weapons, armor, etc.) - Works on any disenchantable item type (weapons, armor, etc.)
- `includeSoulbound` (number, optional): Pass any non-zero value (e.g., `1`) to include soulbound items (defaults to `0`)
**Mode 2: Disenchant by Quality** *(weapons and armor only)* **Mode 2: Disenchant by Quality** *(weapons and armor only)*
- `quality` (string): Must be either: - `quality` (string): Must be either:
- `"greens"` - Disenchants all uncommon (green) quality weapons and armor - `"greens"` - Disenchants all uncommon (green) quality weapons and armor
- `"blues"` - Disenchants all rare (blue) quality weapons and armor - `"blues"` - Disenchants all rare (blue) quality weapons and armor
- Only affects **weapons** (class 2) and **armor** (class 4) - Only affects **weapons** (class 2) and **armor** (class 4)
- `includeSoulbound` (number, optional): Pass any non-zero value (e.g., `1`) to include soulbound items (defaults to `0`)
**Returns:** **Returns:**
- `1` if the first disenchant succeeded - `1` if the first disenchant succeeded
@@ -966,17 +970,17 @@ Automatically disenchants items in your inventory. Can disenchant a specific ite
**Examples:** **Examples:**
```lua ```lua
-- Disenchant all green weapons and armor in your bags -- Disenchant all green weapons and armor in your bags (excluding soulbound)
DisenchantAll("greens") DisenchantAll("greens")
-- Disenchant all blue weapons and armor in your bags -- Disenchant all blue weapons and armor including soulbound items
DisenchantAll("blues") DisenchantAll("blues", 1)
-- Disenchant a specific item by ID -- Disenchant a specific item by ID (excluding soulbound)
DisenchantAll(12345) DisenchantAll(12345)
-- Disenchant a specific item by name -- Disenchant a specific item by name including soulbound items
DisenchantAll("Glowing Brightwood Staff") DisenchantAll("Glowing Brightwood Staff", 1)
``` ```
**Important Notes:** **Important Notes:**
+8690 -8687
View File
File diff suppressed because it is too large Load Diff
+14 -1
View File
@@ -217,7 +217,7 @@ namespace Nampower {
return result; return result;
} }
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality) { PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound) {
PlayerItemSearchResult result{}; PlayerItemSearchResult result{};
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot); auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
@@ -231,6 +231,9 @@ namespace Nampower {
auto inventory = game::GetPlayerInventoryPtr(playerUnit); auto inventory = game::GetPlayerInventoryPtr(playerUnit);
auto const itemIsQuestOrSoulbound = reinterpret_cast<CGItem_C_ItemIsQuestOrSoulboundT>(
Offsets::CGItem_C_ItemIsQuestOrSoulbound);
auto matchesItem = [&](game::CGItem_C *item) { auto matchesItem = [&](game::CGItem_C *item) {
if (!item) { if (!item) {
return false; return false;
@@ -242,6 +245,16 @@ namespace Nampower {
return false; return false;
} }
// Always skip quest items (check by class or startQuestID)
if (itemStats->m_class == game::ITEM_CLASS_QUEST || itemStats->m_startQuestID > 0) {
return false;
}
// Skip soulbound items only if includeSoulbound is false
if (!includeSoulbound && itemIsQuestOrSoulbound(item)) {
return false;
}
// Check if item is weapon or armor // Check if item is weapon or armor
if (itemStats->m_class != game::ITEM_CLASS_WEAPON && if (itemStats->m_class != game::ITEM_CLASS_WEAPON &&
itemStats->m_class != game::ITEM_CLASS_ARMOR) { itemStats->m_class != game::ITEM_CLASS_ARMOR) {
+2 -1
View File
@@ -13,6 +13,7 @@ namespace Nampower {
using DBCache_ItemCacheDBGetRowT = uint32_t * (__thiscall *)(void *this_ptr, uint32_t itemId, uint64_t *guid, TooltipItemStatsCallbackT callback, uintptr_t *userData, bool requestIfMissing); using DBCache_ItemCacheDBGetRowT = uint32_t * (__thiscall *)(void *this_ptr, uint32_t itemId, uint64_t *guid, TooltipItemStatsCallbackT callback, uintptr_t *userData, bool requestIfMissing);
using GetInventoryArtT = char * (__fastcall *)(uint32_t displayId); using GetInventoryArtT = char * (__fastcall *)(uint32_t displayId);
using CGItem_C_ItemIsQuestOrSoulboundT = uint32_t (__fastcall *)(game::CGItem_C *item);
constexpr int32_t EQUIPPED_BAG_INDEX = -3; constexpr int32_t EQUIPPED_BAG_INDEX = -3;
constexpr int32_t BANK_BAG_INDEX = -1; constexpr int32_t BANK_BAG_INDEX = -1;
@@ -100,7 +101,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); PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound = false);
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr); uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr);
} }
+8
View File
@@ -66,6 +66,7 @@ namespace Nampower {
uint32_t gDisenchantItemId = 0; uint32_t gDisenchantItemId = 0;
int32_t gDisenchantQuality = -1; // -1 = unset int32_t gDisenchantQuality = -1; // -1 = unset
bool gDisenchantIncludeSoulbound = false;
uint32_t gNextDisenchantTimeMs = 0; uint32_t gNextDisenchantTimeMs = 0;
bool gForceQueueCast; bool gForceQueueCast;
@@ -370,6 +371,13 @@ namespace Nampower {
gCastData.targetingSpellId = 0; gCastData.targetingSpellId = 0;
} }
void ResetDisenchantState() {
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gDisenchantIncludeSoulbound = false;
gNextDisenchantTimeMs = 0;
}
void checkForStopChanneling() { void checkForStopChanneling() {
if (gUserSettings.queueChannelingSpells && IsNonSwingSpellQueued()) { if (gUserSettings.queueChannelingSpells && IsNonSwingSpellQueued()) {
// for channels just end channeling // for channels just end channeling
+5 -1
View File
@@ -30,7 +30,7 @@ namespace Nampower {
constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer constexpr uint32_t 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 = 21; constexpr uint32_t MINOR_VERSION = 22;
constexpr uint32_t PATCH_VERSION = 0; constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_REGISTRYINDEX = -10000;
@@ -44,6 +44,7 @@ namespace Nampower {
extern uint32_t gDisenchantItemId; extern uint32_t gDisenchantItemId;
extern int32_t gDisenchantQuality; extern int32_t gDisenchantQuality;
extern bool gDisenchantIncludeSoulbound;
extern uint32_t gNextDisenchantTimeMs; extern uint32_t gNextDisenchantTimeMs;
extern bool gForceQueueCast; extern bool gForceQueueCast;
@@ -203,6 +204,8 @@ namespace Nampower {
void ClearQueuedSpells(); void ClearQueuedSpells();
void ResetDisenchantState();
bool processQueues(); bool processQueues();
uint32_t EffectiveCastEndMs(); uint32_t EffectiveCastEndMs();
@@ -213,4 +216,5 @@ namespace Nampower {
void SetAttackTarget(uint64_t target); void SetAttackTarget(uint64_t target);
} }
+17 -18
View File
@@ -614,6 +614,7 @@ namespace Nampower {
bool TryDisenchant() { bool TryDisenchant() {
DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId); DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId);
if (gDisenchantQuality < 0 && gDisenchantItemId == 0) { if (gDisenchantQuality < 0 && gDisenchantItemId == 0) {
ResetDisenchantState();
return false; return false;
} }
@@ -624,15 +625,13 @@ namespace Nampower {
itemSearchResult = FindPlayerItem(gDisenchantItemId, nullptr); itemSearchResult = FindPlayerItem(gDisenchantItemId, nullptr);
} else { } else {
// Quality-based disenchanting (weapons and armor only) // Quality-based disenchanting (weapons and armor only)
itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality); itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality, gDisenchantIncludeSoulbound);
} }
if (!itemSearchResult.found()) { if (!itemSearchResult.found()) {
// Item not found, stop disenchanting // Item not found, stop disenchanting
DEBUG_LOG("No disenchantable item found, stopping disenchant"); DEBUG_LOG("No disenchantable item found, stopping disenchant");
gDisenchantItemId = 0; ResetDisenchantState();
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false; return false;
} }
@@ -641,9 +640,7 @@ namespace Nampower {
if (!item || !item->object.m_obj) { if (!item || !item->object.m_obj) {
// Invalid item, stop disenchanting // Invalid item, stop disenchanting
DEBUG_LOG("Invalid item, stopping disenchant"); DEBUG_LOG("Invalid item, stopping disenchant");
gDisenchantItemId = 0; ResetDisenchantState();
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false; return false;
} }
uint64_t itemGuid = item->object.m_obj->m_guid; uint64_t itemGuid = item->object.m_obj->m_guid;
@@ -655,9 +652,7 @@ namespace Nampower {
if (!playerUnit) { if (!playerUnit) {
// No player unit, stop disenchanting // No player unit, stop disenchanting
DEBUG_LOG("No player unit, stopping disenchant"); DEBUG_LOG("No player unit, stopping disenchant");
gDisenchantItemId = 0; ResetDisenchantState();
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false; return false;
} }
@@ -672,9 +667,7 @@ namespace Nampower {
if (!success) { if (!success) {
// Cast failed, stop disenchanting // Cast failed, stop disenchanting
DEBUG_LOG("Cast failed, stopping disenchant"); DEBUG_LOG("Cast failed, stopping disenchant");
gDisenchantItemId = 0; ResetDisenchantState();
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
return false; return false;
} }
@@ -685,9 +678,10 @@ namespace Nampower {
luaState = GetLuaStatePtr(); luaState = GetLuaStatePtr();
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) { if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: DisenchantAll(itemIdOrName) or DisenchantAll(quality)\n" lua_error(luaState, "Usage: DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])\n"
"quality: \"greens\" for uncommon, \"blues\" for rare\n" "quality: \"greens\" for uncommon, \"blues\" for rare\n"
"itemIdOrName: item ID (number) or item name (string)"); "itemIdOrName: item ID (number) or item name (string)\n"
"includeSoulbound: optional number - pass 1 to include soulbound items (defaults to 0)");
return 0; return 0;
} }
@@ -707,9 +701,8 @@ namespace Nampower {
return 0; return 0;
} }
// Reset both modes // Reset all modes
gDisenchantItemId = 0; ResetDisenchantState();
gDisenchantQuality = -1;
// Check number first (lua_isstring will also match numbers) // Check number first (lua_isstring will also match numbers)
if (lua_isnumber(luaState, 1)) { if (lua_isnumber(luaState, 1)) {
@@ -743,6 +736,12 @@ namespace Nampower {
} }
} }
// Parse optional includeSoulbound parameter (defaults to false)
// Any non-zero integer enables it
if (lua_isnumber(luaState, 2)) {
gDisenchantIncludeSoulbound = (lua_tonumber(luaState, 2) != 0);
}
// Try the first disenchant // Try the first disenchant
bool success = TryDisenchant(); bool success = TryDisenchant();
+1
View File
@@ -206,6 +206,7 @@ enum class Offsets : std::uint32_t {
CGItem_C_Use = 0x005D8D00, CGItem_C_Use = 0x005D8D00,
CGItem_C_GetInventoryArt = 0x005D88B0, CGItem_C_GetInventoryArt = 0x005D88B0,
CGItem_C_ItemIsQuestOrSoulbound = 0x005DA2C0,
DBCache_ItemCacheDBGetRow = 0x0055BA30, DBCache_ItemCacheDBGetRow = 0x0055BA30,
+1 -3
View File
@@ -55,9 +55,7 @@ namespace Nampower {
// Check for Disenchant (13262) failure and stop the disenchant loop // Check for Disenchant (13262) failure and stop the disenchant loop
if (spellId == 13262) { if (spellId == 13262) {
DEBUG_LOG("Disenchant spell failed with result " << int(spellResult) << ", stopping disenchant loop"); DEBUG_LOG("Disenchant spell failed with result " << int(spellResult) << ", stopping disenchant loop");
gDisenchantItemId = 0; ResetDisenchantState();
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