mirror of
https://github.com/brues-code/nampower.git
synced 2026-10-26 14:00:49 +00:00
add logging and disenchant all ability
This commit is contained in:
+63
@@ -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
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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];
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
/* *********************** */
|
/* *********************** */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user