mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-28 01:36:03 +00:00
add ability to save autocasts for greater demons
add CastSpellNoQueue default to current target in SPELL_CAST_EVENT
This commit is contained in:
@@ -93,6 +93,10 @@ SET NP_TargetingQueueWindowMs "1000"
|
||||
|
||||
- `NP_SpamProtectionEnabled` - Whether to enable spam protection functionality that blocks spamming spells while waiting for the server to respond to your initial cast due to issues spamming can cause. 0 to disable, 1 to enable. Default is 1.
|
||||
|
||||
- `NP_PreserveGreaterDemonAutocast` - Whether to remember and restore Felguard/Doomguard autocast preferences when swapping or resummoning those greater demons. 0 to disable, 1 to enable. Default is 1.
|
||||
- `NP_FelguardAutocastData` - Saved Felguard autocast state data used by `NP_PreserveGreaterDemonAutocast` (managed automatically).
|
||||
- `NP_DoomguardAutocastData` - Saved Doomguard autocast state data used by `NP_PreserveGreaterDemonAutocast` (managed automatically).
|
||||
|
||||
- `NP_PreventMountingWhenBuffCapped` - Whether to prevent mounting when you have 32 buffs (buff capped) and are not already mounted. This prevents the issue where you mount but cannot dismount because the mount aura fails to apply due to the buff cap. When blocked, displays an error message. 0 to disable, 1 to enable. Default is 1.
|
||||
|
||||
- `NP_EnableAuraCastEvents` - Whether to enable AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER events. 0 to disable, 1 to enable. Default is 0.
|
||||
@@ -184,7 +188,7 @@ For complete documentation of all custom Lua functions added by Nampower, see **
|
||||
|
||||
This includes functions for:
|
||||
- Spell/item/unit information (GetItemStats, GetSpellRec, GetUnitData, etc.)
|
||||
- Spell casting and queuing (QueueSpellByName, QueueScript, etc.)
|
||||
- Spell casting and queuing (QueueSpellByName, QueueScript, CastSpellByNameNoQueue, CastSpellNoQueue, etc.)
|
||||
- Cast information (GetCastInfo, GetCurrentCastingInfo)
|
||||
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables
|
||||
- Inventory helpers (GetTrinketCooldown, GetTrinkets, GetAmmo)
|
||||
|
||||
+14
@@ -39,6 +39,7 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
|
||||
- [Spell Casting and Queuing](#spell-casting-and-queuing)
|
||||
- [QueueSpellByName](#queuespellbynamespellname)
|
||||
- [CastSpellByNameNoQueue](#castspellbynamenoqueuespellname)
|
||||
- [CastSpellNoQueue](#castspellnoqueuespellid-spellbook--unit)
|
||||
- [QueueScript](#queuescriptscript-priority)
|
||||
- [IsSpellInRange](#isspellinrangespellname-target-or-isspellinrangespellid-target)
|
||||
- [IsSpellUsable](#isspellusablespellname-or-isspellusablespellid)
|
||||
@@ -775,6 +776,19 @@ The vanilla `CastSpellByName` is enhanced to accept a unit token string (e.g. "m
|
||||
#### CastSpellByNameNoQueue(spellName, [onSelfOrUnit])
|
||||
Will force a spell cast to never queue even if your settings would normally queue. Can be used to fix addons that don't work with queued spells. Supports the same unit string parameter as `CastSpellByName`.
|
||||
|
||||
#### CastSpellNoQueue(spellId, spellBook [, unit])
|
||||
Same as `CastSpellByNameNoQueue` but takes a numeric spell ID and spellbook type instead of a spell name. `spellBook` is `spell` for player spells and `pet` for pet spells. Accepts the same spell slot formats as the enhanced `CastSpell` (numeric slot, spell name string, or `"spellId:123"` prefix).
|
||||
|
||||
The optional `unit` parameter accepts any unit token (e.g. `"target"`, `"mouseover"`, `"focus"`, a GUID string) to cast on a specific unit. The original CastSpell doesn't support this as it is registered with only 2 arguments currently.
|
||||
|
||||
```lua
|
||||
-- Cast spell ID 133 (Fireball) on mouseover without queuing
|
||||
CastSpellNoQueue(133, 0, "mouseover")
|
||||
|
||||
-- Cast by slot without a unit token (uses current target)
|
||||
CastSpellNoQueue(5, 0)
|
||||
```
|
||||
|
||||
#### QueueScript(script, [priority])
|
||||
Queues any arbitrary script using the same logic as a regular spell using NP_SpellQueueWindowMs as the window. If no spell is being cast and you are not on the gcd the script will be run immediately.
|
||||
|
||||
|
||||
+13612
-13239
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,8 @@ set(SOURCE_FILES
|
||||
autoattack.cpp
|
||||
player_scripts.hpp
|
||||
player_scripts.cpp
|
||||
pet.hpp
|
||||
pet.cpp
|
||||
unit.hpp
|
||||
unit.cpp
|
||||
)
|
||||
|
||||
@@ -34,6 +34,21 @@
|
||||
|
||||
namespace game {
|
||||
#pragma pack(push, 1)
|
||||
struct CVar {
|
||||
uint8_t unk0[0x1C];
|
||||
uint32_t m_flags;
|
||||
char *m_stringValue;
|
||||
float m_floatValue;
|
||||
int32_t m_intValue;
|
||||
int32_t m_modified;
|
||||
char *m_defaultValue;
|
||||
char *m_resetValue;
|
||||
char *m_latchedValue;
|
||||
uint8_t unk3C[0x80];
|
||||
void *m_callback;
|
||||
void *m_arg;
|
||||
};
|
||||
|
||||
enum ItemClass {
|
||||
ITEM_CLASS_CONSUMABLE = 0,
|
||||
ITEM_CLASS_CONTAINER = 1,
|
||||
@@ -1144,6 +1159,19 @@ namespace game {
|
||||
TARGET_FLAG_UNK2 = 0x00010000, // pguid, not used in any spells (can be set dynamically)
|
||||
};
|
||||
|
||||
constexpr uint32_t UnitActionButtonAction(uint32_t x) { return x & 0x00FFFFFFu; }
|
||||
constexpr uint32_t UnitActionButtonType(uint32_t x) { return (x & 0xFF000000u) >> 24; }
|
||||
|
||||
enum ActiveStates : uint8_t
|
||||
{
|
||||
ACT_DECIDE = 0x00, // custom
|
||||
ACT_PASSIVE = 0x01, // 0x01 - passive
|
||||
ACT_REACTION = 0x06, // 0x02 | 0x04
|
||||
ACT_COMMAND = 0x07, // 0x01 | 0x02 | 0x04
|
||||
ACT_DISABLED = 0x81, // 0x80 - castable
|
||||
ACT_ENABLED = 0xC1, // 0x40 | 0x80 - auto cast + castable
|
||||
};
|
||||
|
||||
enum Events : std::uint32_t {
|
||||
UNIT_PET = 0u,
|
||||
UNIT_PET_02 = 2u,
|
||||
|
||||
+61
-5
@@ -46,6 +46,7 @@
|
||||
#include "lua_refs.hpp"
|
||||
#include "autoattack.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "pet.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -162,6 +163,9 @@ namespace Nampower {
|
||||
std::unique_ptr<hadesmem::PatchDetour<GetGUIDFromNameT> > gGetGUIDFromNameDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCastSpellByNameDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<SendUnitSignalT> > gSendUnitSignalDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<CGPetInfo_SetPetT> > gCGPetInfo_SetPetDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<TogglePetSlotAutocastT> > gTogglePetSlotAutocastDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<CGPetInfo_GetPetSpellActionT> > gCGPetInfo_GetPetSpellActionDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour;
|
||||
|
||||
// Flags for one-time initialization
|
||||
@@ -687,6 +691,9 @@ namespace Nampower {
|
||||
} else if (strcmp(cvar, "NP_SpamProtectionEnabled") == 0) {
|
||||
gUserSettings.spamProtectionEnabled = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_SpamProtectionEnabled to " << gUserSettings.spamProtectionEnabled);
|
||||
} else if (strcmp(cvar, "NP_PreserveGreaterDemonAutocast") == 0) {
|
||||
gUserSettings.preserveGreaterDemonAutocast = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_PreserveGreaterDemonAutocast to " << gUserSettings.preserveGreaterDemonAutocast);
|
||||
} else if (strcmp(cvar, "NP_EnableUnitEventsPet") == 0) {
|
||||
gUserSettings.enableUnitEventsPet = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_EnableUnitEventsPet to " << gUserSettings.enableUnitEventsPet);
|
||||
@@ -780,19 +787,28 @@ namespace Nampower {
|
||||
return cvarSetOrig(luaState);
|
||||
}
|
||||
|
||||
int *getCvar(const char *cvar) {
|
||||
int *GetIntCVar(const char *cvar) {
|
||||
auto const cvarLookup = hadesmem::detail::AliasCast<CVarLookupT>(Offsets::CVarLookup);
|
||||
uintptr_t *cvarPtr = cvarLookup(cvar);
|
||||
auto *cvarPtr = cvarLookup(cvar);
|
||||
|
||||
if (cvarPtr) {
|
||||
return reinterpret_cast<int *>(cvarPtr +
|
||||
10); // get intValue from CVar which is consistent, strValue more complicated
|
||||
return &cvarPtr->m_intValue;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *GetStringCvar(const char *cvar) {
|
||||
auto const cvarLookup = hadesmem::detail::AliasCast<CVarLookupT>(Offsets::CVarLookup);
|
||||
auto *cvarPtr = cvarLookup(cvar);
|
||||
if (!cvarPtr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cvarPtr->m_stringValue;
|
||||
}
|
||||
|
||||
void loadUserVar(const char *cvar) {
|
||||
int *value = getCvar(cvar);
|
||||
int *value = GetIntCVar(cvar);
|
||||
if (value) {
|
||||
updateFromCvar(cvar, std::to_string(*value).c_str());
|
||||
} else {
|
||||
@@ -1216,6 +1232,36 @@ namespace Nampower {
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_PreserveGreaterDemonAutocast[] = "NP_PreserveGreaterDemonAutocast";
|
||||
CVarRegister(NP_PreserveGreaterDemonAutocast, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
gUserSettings.preserveGreaterDemonAutocast ? defaultTrue : defaultFalse, // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_FelguardAutocastData[] = "NP_FelguardAutocastData";
|
||||
CVarRegister(NP_FelguardAutocastData, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
"", // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_DoomguardAutocastData[] = "NP_DoomguardAutocastData";
|
||||
CVarRegister(NP_DoomguardAutocastData, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
"", // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_ChannelLatencyReductionPercentage[] = "NP_ChannelLatencyReductionPercentage";
|
||||
CVarRegister(NP_ChannelLatencyReductionPercentage, // name
|
||||
nullptr, // help
|
||||
@@ -1259,6 +1305,7 @@ namespace Nampower {
|
||||
loadUserVar("NP_DoubleCastToEndChannelEarly");
|
||||
|
||||
loadUserVar("NP_SpamProtectionEnabled");
|
||||
loadUserVar("NP_PreserveGreaterDemonAutocast");
|
||||
|
||||
loadUserVar("NP_EnableUnitEventsPet");
|
||||
loadUserVar("NP_EnableUnitEventsParty");
|
||||
@@ -1469,6 +1516,12 @@ namespace Nampower {
|
||||
process, Offsets::CSimpleTop_OnKeyUp, &CSimpleTop_OnKeyUpHook);
|
||||
gSendUnitSignalDetour = createHook<SendUnitSignalT>(process, Offsets::SendUnitSignal,
|
||||
&SendUnitSignalHook);
|
||||
gCGPetInfo_SetPetDetour = createHook<CGPetInfo_SetPetT>(process, Offsets::CGPetInfo_SetPet,
|
||||
&CGPetInfo_SetPetHook);
|
||||
gTogglePetSlotAutocastDetour = createHook<TogglePetSlotAutocastT>(
|
||||
process, Offsets::TogglePetSlotAutocast, &TogglePetSlotAutocastHook);
|
||||
gCGPetInfo_GetPetSpellActionDetour = createHook<CGPetInfo_GetPetSpellActionT>(
|
||||
process, Offsets::CGPetInfo_GetPetSpellAction, &CGPetInfo_GetPetSpellActionHook);
|
||||
gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>(
|
||||
process, Offsets::CGGameUI_ShowCombatFeedback, &CGGameUI_ShowCombatFeedbackHook);
|
||||
gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::LoadScriptFunctions,
|
||||
@@ -1697,6 +1750,9 @@ namespace Nampower {
|
||||
char castSpellByNameNoQueue[] = "CastSpellByNameNoQueue";
|
||||
RegisterLuaFunction(castSpellByNameNoQueue, reinterpret_cast<uintptr_t *>(Script_CastSpellByNameNoQueue));
|
||||
|
||||
char castSpellNoQueue[] = "CastSpellNoQueue";
|
||||
RegisterLuaFunction(castSpellNoQueue, reinterpret_cast<uintptr_t *>(Script_CastSpellNoQueue));
|
||||
|
||||
char queueScript[] = "QueueScript";
|
||||
RegisterLuaFunction(queueScript, reinterpret_cast<uintptr_t *>(Script_QueueScript));
|
||||
|
||||
|
||||
+7
-3
@@ -7,6 +7,7 @@
|
||||
#include <hadesmem/patcher.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "game.hpp"
|
||||
#include "types.h"
|
||||
@@ -25,7 +26,7 @@ namespace Nampower {
|
||||
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
|
||||
|
||||
constexpr uint32_t MAJOR_VERSION = 3;
|
||||
constexpr uint32_t MINOR_VERSION = 0;
|
||||
constexpr uint32_t MINOR_VERSION = 1;
|
||||
constexpr uint32_t PATCH_VERSION = 0;
|
||||
|
||||
constexpr int32_t LUA_REGISTRYINDEX = -10000;
|
||||
@@ -112,7 +113,7 @@ namespace Nampower {
|
||||
using CGCharacterInfo_UseItemT = void (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint32_t itemSlot, uint64_t *targetGuid);
|
||||
|
||||
using GetSpellSlotAndTypeT = int32_t (__fastcall *)(const char *, uint32_t *);
|
||||
using GetSpellSlotFromLuaT = uint32_t (__fastcall *)(int param_1, uint32_t *slot, uint32_t *type);
|
||||
using GetSpellSlotFromLuaT = uint32_t (__fastcall *)(uintptr_t *luaState, uint32_t *slot, uint32_t *type);
|
||||
using GetTimeMsT = uint64_t (__stdcall *)();
|
||||
|
||||
using GetClientConnectionT = uintptr_t *(__stdcall *)();
|
||||
@@ -178,7 +179,7 @@ namespace Nampower {
|
||||
|
||||
using TargetUnitT = void (__fastcall *)(uint64_t *guid);
|
||||
|
||||
using CVarLookupT = uintptr_t *(__fastcall *)(const char *);
|
||||
using CVarLookupT = game::CVar *(__fastcall *)(const char *);
|
||||
using SetCVarT = int (__fastcall *)(uintptr_t *luaPtr);
|
||||
using CVarRegisterT = int *(__fastcall *)(char *name, char *help, int unk1, const char *defaultValuePtr,
|
||||
void *callbackPtr,
|
||||
@@ -226,5 +227,8 @@ namespace Nampower {
|
||||
|
||||
void SetAttackTarget(uint64_t target);
|
||||
|
||||
const char *GetStringCvar(const char *cvar);
|
||||
|
||||
void loadUserVar(const char *cvar);
|
||||
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ enum class Offsets : std::uint32_t {
|
||||
|
||||
// Existing script functions
|
||||
GetGUIDFromName = 0X00515970,
|
||||
Script_CastSpell = 0x004B42F0,
|
||||
Script_CastSpellByName = 0x004B4AB0,
|
||||
Script_SpellTargetUnit = 0x006E6D90,
|
||||
Script_SetCVar = 0x00488C10,
|
||||
@@ -262,4 +263,10 @@ enum class Offsets : std::uint32_t {
|
||||
XMLNode_GetAttributeByName = 0x006f2cf0,
|
||||
|
||||
RaidTargetMarkGuidBase = 0xB71368,
|
||||
|
||||
CGPetInfo_SetPet = 0x004bc7e0,
|
||||
TogglePetSlotAutocast = 0x004bcbb0,
|
||||
CGPetInfo_GetPetSpellAction = 0x004bd190,
|
||||
PetActionBarSlots = 0x00b71438,
|
||||
ActivePetGuid = 0x00b714a0,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// Created by pmacc on 3/1/2026.
|
||||
//
|
||||
|
||||
#include "pet.hpp"
|
||||
|
||||
#include "game.hpp"
|
||||
#include "helper.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "offsets.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace Nampower {
|
||||
constexpr uint32_t kAutocastEnabledType = 0xC1;
|
||||
constexpr uint32_t kAutocastDisabledType = 0x81;
|
||||
constexpr uint32_t kFelguardDisplayId = 7970;
|
||||
constexpr uint32_t kDoomguardDisplayId = 1912;
|
||||
uint32_t gPendingToggleAutocastSlot = 0;
|
||||
|
||||
uint32_t GetActivePetDisplayId() {
|
||||
auto const activePetGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
|
||||
if (activePetGuid == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto *petUnit = game::GetObjectPtr(activePetGuid);
|
||||
if (!petUnit) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto *unitFields = *reinterpret_cast<game::UnitFields **>(petUnit + 68);
|
||||
if (!unitFields) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return unitFields->displayId;
|
||||
}
|
||||
|
||||
const char *GetAutocastCvarNameForDisplayId(uint32_t displayId) {
|
||||
if (displayId == kFelguardDisplayId) {
|
||||
return "NP_FelguardAutocastData";
|
||||
}
|
||||
if (displayId == kDoomguardDisplayId) {
|
||||
return "NP_DoomguardAutocastData";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string SerializeAutocastData(const std::map<uint32_t, uint32_t> &autocastData) {
|
||||
std::ostringstream out;
|
||||
bool first = true;
|
||||
for (auto const &entry: autocastData) {
|
||||
if (!first) {
|
||||
out << ",";
|
||||
}
|
||||
out << entry.first << ":" << entry.second;
|
||||
first = false;
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void ParseAutocastData(const std::string &serialized, std::map<uint32_t, uint32_t> &autocastData) {
|
||||
autocastData.clear();
|
||||
if (serialized.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
while (start < serialized.size()) {
|
||||
size_t end = serialized.find(',', start);
|
||||
if (end == std::string::npos) {
|
||||
end = serialized.size();
|
||||
}
|
||||
|
||||
std::string token = serialized.substr(start, end - start);
|
||||
size_t colonPos = token.find(':');
|
||||
if (colonPos != std::string::npos && colonPos > 0 && colonPos + 1 < token.size()) {
|
||||
std::string slotStr = token.substr(0, colonPos);
|
||||
std::string actionStr = token.substr(colonPos + 1);
|
||||
|
||||
char *slotEnd = nullptr;
|
||||
char *actionEnd = nullptr;
|
||||
unsigned long slotValue = std::strtoul(slotStr.c_str(), &slotEnd, 10);
|
||||
unsigned long actionValue = std::strtoul(actionStr.c_str(), &actionEnd, 10);
|
||||
|
||||
if (slotEnd && *slotEnd == '\0' &&
|
||||
actionEnd && *actionEnd == '\0' &&
|
||||
slotValue <= 0xFFFFFFFFul &&
|
||||
actionValue <= 0xFFFFFFFFul) {
|
||||
autocastData[static_cast<uint32_t>(slotValue)] = static_cast<uint32_t>(actionValue);
|
||||
}
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveGreaterDemonAutocastPreferences() {
|
||||
SaveGreaterDemonAutocastPreferences(0, 0);
|
||||
}
|
||||
|
||||
void SaveGreaterDemonAutocastPreferences(uint32_t spellSlot, uint32_t actionData) {
|
||||
uint32_t displayId = GetActivePetDisplayId();
|
||||
if (displayId != kFelguardDisplayId && displayId != kDoomguardDisplayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *cvarName = GetAutocastCvarNameForDisplayId(displayId);
|
||||
if (!cvarName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *cvarDataPtr = GetStringCvar(cvarName);
|
||||
std::string cvarData = cvarDataPtr ? cvarDataPtr : "";
|
||||
|
||||
std::map<uint32_t, uint32_t> autocastData;
|
||||
ParseAutocastData(cvarData, autocastData);
|
||||
|
||||
uint32_t actionId = game::UnitActionButtonAction(actionData);
|
||||
uint32_t type = game::UnitActionButtonType(actionData);
|
||||
|
||||
if (spellSlot >= 3 && spellSlot <= 7) {
|
||||
if (type == kAutocastEnabledType) {
|
||||
autocastData[spellSlot] = actionData;
|
||||
} else if (type == kAutocastDisabledType) {
|
||||
autocastData.erase(spellSlot);
|
||||
}
|
||||
}
|
||||
|
||||
std::string serialized = SerializeAutocastData(autocastData);
|
||||
std::string lua = "SetCVar(\"";
|
||||
lua += cvarName;
|
||||
lua += "\",\"";
|
||||
lua += serialized;
|
||||
lua += "\")";
|
||||
LuaCall(lua.c_str());
|
||||
|
||||
DEBUG_LOG("Saved " << cvarName << "=" << serialized
|
||||
<< " (slot=" << spellSlot << ", actionData=" << actionData
|
||||
<< ", actionId=" << actionId << ", type=0x" << std::hex << type << std::dec << ")");
|
||||
}
|
||||
|
||||
void RestoreGreaterDemonAutocastPreferences() {
|
||||
uint32_t displayId = GetActivePetDisplayId();
|
||||
const char *cvarName = GetAutocastCvarNameForDisplayId(displayId);
|
||||
|
||||
if (!cvarName) {
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG("Restoring greater demon autocast for displayId=" << displayId << " using cvar " << cvarName);
|
||||
|
||||
const char *cvarDataPtr = GetStringCvar(cvarName);
|
||||
std::string cvarData = cvarDataPtr ? cvarDataPtr : "";
|
||||
|
||||
if (cvarData.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t> autocastData;
|
||||
ParseAutocastData(cvarData, autocastData);
|
||||
if (autocastData.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const petGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
|
||||
if (petGuid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const clientServicesSend = reinterpret_cast<ClientServices_SendT>(Offsets::ClientServices_Send);
|
||||
if (!clientServicesSend) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr uint32_t CMSG_PET_SET_ACTION = 372;
|
||||
uint32_t packetsSent = 0;
|
||||
|
||||
for (auto const &entry: autocastData) {
|
||||
uint32_t spellSlot = entry.first;
|
||||
uint32_t actionData = entry.second;
|
||||
|
||||
if (spellSlot < 3 || spellSlot > 7) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *petActionBarSlots = reinterpret_cast<uint32_t *>(Offsets::PetActionBarSlots);
|
||||
if (petActionBarSlots) {
|
||||
petActionBarSlots[spellSlot] = actionData;
|
||||
}
|
||||
|
||||
CDataStore dataStore;
|
||||
dataStore.Put(CMSG_PET_SET_ACTION);
|
||||
dataStore.Put(petGuid);
|
||||
dataStore.Put(spellSlot);
|
||||
dataStore.Put(actionData);
|
||||
dataStore.Finalize();
|
||||
|
||||
clientServicesSend(&dataStore);
|
||||
packetsSent++;
|
||||
}
|
||||
|
||||
auto const signalEvent = reinterpret_cast<SignalEventT>(Offsets::SignalEvent);
|
||||
if (signalEvent) {
|
||||
signalEvent(game::PET_BAR_UPDATE);
|
||||
}
|
||||
|
||||
DEBUG_LOG("Restored greater demon autocast for displayId=" << displayId
|
||||
<< " petGuid=" << petGuid
|
||||
<< " cvar=" << cvarName
|
||||
<< " packetsSent=" << packetsSent);
|
||||
}
|
||||
|
||||
void CGPetInfo_SetPetHook(hadesmem::PatchDetourBase *detour, void *thisptr, void *dummy_edx,
|
||||
uint32_t guid1, uint32_t guid2) {
|
||||
auto activePetGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
|
||||
|
||||
auto const origSetPet = detour->GetTrampolineT<CGPetInfo_SetPetT>();
|
||||
origSetPet(thisptr, dummy_edx, guid1, guid2);
|
||||
|
||||
uint64_t newPetGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
|
||||
if (activePetGuid != newPetGuid) {
|
||||
DEBUG_LOG("Active pet guid changed from " << activePetGuid << " to " << newPetGuid);
|
||||
|
||||
if (gUserSettings.preserveGreaterDemonAutocast && newPetGuid != 0) {
|
||||
RestoreGreaterDemonAutocastPreferences();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TogglePetSlotAutocastHook(hadesmem::PatchDetourBase *detour, uint32_t slot) {
|
||||
auto const orig = detour->GetTrampolineT<TogglePetSlotAutocastT>();
|
||||
gPendingToggleAutocastSlot = slot;
|
||||
orig(slot);
|
||||
gPendingToggleAutocastSlot = 0;
|
||||
}
|
||||
|
||||
void CGPetInfo_GetPetSpellActionHook(hadesmem::PatchDetourBase *detour, uint32_t *action) {
|
||||
auto const orig = detour->GetTrampolineT<CGPetInfo_GetPetSpellActionT>();
|
||||
orig(action);
|
||||
|
||||
if (!action) return;
|
||||
uint32_t spellSlot = gPendingToggleAutocastSlot;
|
||||
|
||||
uint32_t actionData = action[0];
|
||||
uint32_t actionId = game::UnitActionButtonAction(actionData);
|
||||
uint32_t type = game::UnitActionButtonType(actionData);
|
||||
|
||||
// save autocast preferences
|
||||
auto activePetGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
|
||||
if (gUserSettings.preserveGreaterDemonAutocast && activePetGuid != 0) {
|
||||
DEBUG_LOG("Saving greater demon autocast preferences for slot " << spellSlot);
|
||||
SaveGreaterDemonAutocastPreferences(spellSlot, actionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by pmacc on 3/1/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "main.hpp"
|
||||
|
||||
namespace Nampower {
|
||||
using CGPetInfo_SetPetT = void (__fastcall *)(void *thisptr, void *dummy_edx, uint32_t guid1, uint32_t guid2);
|
||||
using TogglePetSlotAutocastT = void (__fastcall *)(uint32_t slot);
|
||||
using CGPetInfo_GetPetSpellActionT = void (__fastcall *)(uint32_t *action);
|
||||
|
||||
void CGPetInfo_SetPetHook(hadesmem::PatchDetourBase *detour, void *thisptr, void *dummy_edx, uint32_t guid1, uint32_t guid2);
|
||||
void TogglePetSlotAutocastHook(hadesmem::PatchDetourBase *detour, uint32_t slot);
|
||||
void CGPetInfo_GetPetSpellActionHook(hadesmem::PatchDetourBase *detour, uint32_t *action);
|
||||
|
||||
void SaveGreaterDemonAutocastPreferences();
|
||||
void SaveGreaterDemonAutocastPreferences(uint32_t spellSlot, uint32_t actionData);
|
||||
void RestoreGreaterDemonAutocastPreferences();
|
||||
}
|
||||
@@ -64,6 +64,40 @@ namespace Nampower {
|
||||
return castSpellByName(luaState);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Script_CastSpellNoQueue(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr();
|
||||
|
||||
// If 3rd arg is a unit token, resolve target and call directly
|
||||
if (lua_isstring(luaState, 3)) {
|
||||
uint32_t spellSlot = 0, bookType = 0;
|
||||
auto const getSpellSlotFromLua = reinterpret_cast<GetSpellSlotFromLuaT>(Offsets::GetSpellSlotFromLua);
|
||||
if (!getSpellSlotFromLua(luaState, &spellSlot, &bookType)) {
|
||||
lua_error(luaState, "Invalid spell slot in CastSpellNoQueue");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto const unitToken = lua_tostring(luaState, 3);
|
||||
auto const targetGuid = GetUnitGuidFromString(unitToken);
|
||||
|
||||
DEBUG_LOG("CastSpellNoQueue with unit token");
|
||||
gNoQueueCast = true;
|
||||
auto const castSpell = reinterpret_cast<CGSpellBook_CastSpellT>(Offsets::CGSpellBook_CastSpell);
|
||||
castSpell(spellSlot, bookType, targetGuid);
|
||||
gNoQueueCast = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEBUG_LOG("Casting next spell without queuing");
|
||||
gNoQueueCast = true;
|
||||
auto const scriptCastSpell = reinterpret_cast<LuaScriptT>(Offsets::Script_CastSpell);
|
||||
auto result = scriptCastSpell(luaState);
|
||||
gNoQueueCast = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Script_QueueSpellByName(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
||||
|
||||
@@ -536,9 +570,8 @@ namespace Nampower {
|
||||
return 3;
|
||||
}
|
||||
|
||||
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type) {
|
||||
// Get Lua state pointer
|
||||
uintptr_t *luaState = GetLuaStatePtr();
|
||||
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState, uint32_t *slot, uint32_t *type) {
|
||||
luaState = GetLuaStatePtr();
|
||||
|
||||
// Check if first parameter is not a number
|
||||
if (lua_isnumber(luaState, 1) == 0) {
|
||||
@@ -598,7 +631,7 @@ namespace Nampower {
|
||||
} else {
|
||||
auto const getSpellSlotFromLua = detour->GetTrampolineT<GetSpellSlotFromLuaT>();
|
||||
// Fall back to original function
|
||||
return getSpellSlotFromLua(param_1, slot, type);
|
||||
return getSpellSlotFromLua(luaState, slot, type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
namespace Nampower {
|
||||
uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState);
|
||||
|
||||
uint32_t Script_CastSpellNoQueue(uintptr_t *luaState);
|
||||
|
||||
uint32_t Script_CastSpellByNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
|
||||
|
||||
uint32_t Script_QueueSpellByName(uintptr_t *luaState);
|
||||
@@ -36,5 +38,5 @@ namespace Nampower {
|
||||
|
||||
uint32_t Script_GetSpellDuration(uintptr_t *luaState);
|
||||
|
||||
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type);
|
||||
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState, uint32_t *slot, uint32_t *type);
|
||||
}
|
||||
|
||||
@@ -328,6 +328,11 @@ namespace Nampower {
|
||||
void
|
||||
TriggerSpellCastEvent(bool result, uint32_t spellId, CastType castType, std::uint64_t guid, uint32_t itemId) {
|
||||
static char format[] = "%d%d%d%s%d";
|
||||
|
||||
if (!guid) {
|
||||
// default to current target
|
||||
guid = game::GetCurrentTargetGuid();
|
||||
}
|
||||
char *guidStr = ConvertGuidToString(guid);
|
||||
|
||||
((int (__cdecl *)(int, char *, uint32_t, uint32_t, uint32_t, char *, uint32_t)) Offsets::SignalEventParam)(
|
||||
|
||||
@@ -29,6 +29,7 @@ struct UserSettings {
|
||||
|
||||
bool spamProtectionEnabled = true;
|
||||
bool enableAuraCastEvents = false;
|
||||
bool preserveGreaterDemonAutocast = true;
|
||||
|
||||
bool enableUnitEventsPet = true;
|
||||
bool enableUnitEventsParty = true;
|
||||
|
||||
Reference in New Issue
Block a user