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
#### 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.
**⚠️ 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
- **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
- **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)
- Disenchants all copies of the specified item found in your bags
- 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)*
- `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)
- `includeSoulbound` (number, optional): Pass any non-zero value (e.g., `1`) to include soulbound items (defaults to `0`)
**Returns:**
- `1` if the first disenchant succeeded
@@ -966,17 +970,17 @@ Automatically disenchants items in your inventory. Can disenchant a specific ite
**Examples:**
```lua
-- Disenchant all green weapons and armor in your bags
-- Disenchant all green weapons and armor in your bags (excluding soulbound)
DisenchantAll("greens")
-- Disenchant all blue weapons and armor in your bags
DisenchantAll("blues")
-- Disenchant all blue weapons and armor including soulbound items
DisenchantAll("blues", 1)
-- Disenchant a specific item by ID
-- Disenchant a specific item by ID (excluding soulbound)
DisenchantAll(12345)
-- Disenchant a specific item by name
DisenchantAll("Glowing Brightwood Staff")
-- Disenchant a specific item by name including soulbound items
DisenchantAll("Glowing Brightwood Staff", 1)
```
**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;
}
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality) {
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound) {
PlayerItemSearchResult result{};
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
@@ -231,6 +231,9 @@ namespace Nampower {
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
auto const itemIsQuestOrSoulbound = reinterpret_cast<CGItem_C_ItemIsQuestOrSoulboundT>(
Offsets::CGItem_C_ItemIsQuestOrSoulbound);
auto matchesItem = [&](game::CGItem_C *item) {
if (!item) {
return false;
@@ -242,6 +245,16 @@ namespace Nampower {
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
if (itemStats->m_class != game::ITEM_CLASS_WEAPON &&
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 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 BANK_BAG_INDEX = -1;
@@ -100,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);
PlayerItemSearchResult FindPlayerDisenchantItem(int32_t quality, bool includeSoulbound = false);
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr);
}
+8
View File
@@ -66,6 +66,7 @@ namespace Nampower {
uint32_t gDisenchantItemId = 0;
int32_t gDisenchantQuality = -1; // -1 = unset
bool gDisenchantIncludeSoulbound = false;
uint32_t gNextDisenchantTimeMs = 0;
bool gForceQueueCast;
@@ -370,6 +371,13 @@ namespace Nampower {
gCastData.targetingSpellId = 0;
}
void ResetDisenchantState() {
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gDisenchantIncludeSoulbound = false;
gNextDisenchantTimeMs = 0;
}
void checkForStopChanneling() {
if (gUserSettings.queueChannelingSpells && IsNonSwingSpellQueued()) {
// 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 MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 21;
constexpr uint32_t MINOR_VERSION = 22;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
@@ -44,6 +44,7 @@ namespace Nampower {
extern uint32_t gDisenchantItemId;
extern int32_t gDisenchantQuality;
extern bool gDisenchantIncludeSoulbound;
extern uint32_t gNextDisenchantTimeMs;
extern bool gForceQueueCast;
@@ -203,6 +204,8 @@ namespace Nampower {
void ClearQueuedSpells();
void ResetDisenchantState();
bool processQueues();
uint32_t EffectiveCastEndMs();
@@ -213,4 +216,5 @@ namespace Nampower {
void SetAttackTarget(uint64_t target);
}
+17 -18
View File
@@ -614,6 +614,7 @@ namespace Nampower {
bool TryDisenchant() {
DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId);
if (gDisenchantQuality < 0 && gDisenchantItemId == 0) {
ResetDisenchantState();
return false;
}
@@ -624,15 +625,13 @@ namespace Nampower {
itemSearchResult = FindPlayerItem(gDisenchantItemId, nullptr);
} else {
// Quality-based disenchanting (weapons and armor only)
itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality);
itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality, gDisenchantIncludeSoulbound);
}
if (!itemSearchResult.found()) {
// Item not found, stop disenchanting
DEBUG_LOG("No disenchantable item found, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
ResetDisenchantState();
return false;
}
@@ -641,9 +640,7 @@ namespace Nampower {
if (!item || !item->object.m_obj) {
// Invalid item, stop disenchanting
DEBUG_LOG("Invalid item, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
ResetDisenchantState();
return false;
}
uint64_t itemGuid = item->object.m_obj->m_guid;
@@ -655,9 +652,7 @@ namespace Nampower {
if (!playerUnit) {
// No player unit, stop disenchanting
DEBUG_LOG("No player unit, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
ResetDisenchantState();
return false;
}
@@ -672,9 +667,7 @@ namespace Nampower {
if (!success) {
// Cast failed, stop disenchanting
DEBUG_LOG("Cast failed, stopping disenchant");
gDisenchantItemId = 0;
gDisenchantQuality = -1;
gNextDisenchantTimeMs = 0;
ResetDisenchantState();
return false;
}
@@ -685,9 +678,10 @@ namespace Nampower {
luaState = GetLuaStatePtr();
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"
"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;
}
@@ -707,9 +701,8 @@ namespace Nampower {
return 0;
}
// Reset both modes
gDisenchantItemId = 0;
gDisenchantQuality = -1;
// Reset all modes
ResetDisenchantState();
// Check number first (lua_isstring will also match numbers)
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
bool success = TryDisenchant();
+1
View File
@@ -206,6 +206,7 @@ enum class Offsets : std::uint32_t {
CGItem_C_Use = 0x005D8D00,
CGItem_C_GetInventoryArt = 0x005D88B0,
CGItem_C_ItemIsQuestOrSoulbound = 0x005DA2C0,
DBCache_ItemCacheDBGetRow = 0x0055BA30,
+2 -4
View File
@@ -55,9 +55,7 @@ namespace Nampower {
// 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;
ResetDisenchantState();
}
// ignore SPELL_FAILED_CANT_DO_THAT_YET for arcane surge gets sent all the time after success
@@ -714,4 +712,4 @@ namespace Nampower {
return 1;
}
}
}