add chat bubble options, store reaction states for greater demons, check if spell is missing from dbc in castspell

This commit is contained in:
avitasia
2026-03-05 15:15:48 -08:00
parent f279bb19f8
commit 58d56178ad
16 changed files with 14725 additions and 13939 deletions
+15
View File
@@ -126,6 +126,10 @@ SET NP_TargetingQueueWindowMs "1000"
- `NP_ChannelLatencyReductionPercentage` - The percentage of your latency to subtract from the end of a channel duration to optimize cast time while hopefully not losing any ticks (more explanation below). Default is 75.
- `NP_NameplateDistance` - The distance in yards to display nameplates. Defaults to whatever was set by the game or vanilla tweaks.
- `NP_ChatBubbleDistance` - Maximum distance in yards for rendering chat bubbles generated by the client chat-bubble hook. Default is 60.
- `NP_ChatBubblesWhisper` - Whether to show chat bubbles for whisper/whisper-inform chat types. 0 to disable, 1 to enable. Default is 0.
- `NP_ChatBubblesRaid` - Whether to show chat bubbles for raid leader/warning chat types. 0 to disable, 1 to enable. Default is 0.
- `NP_ChatBubblesBattleground` - Whether to show chat bubbles for battleground/battleground leader chat types. 0 to disable, 1 to enable. Default is 0.
## Existing Lua Changes
@@ -235,6 +239,17 @@ If you encounter any bugs please report them in the issues tab. Please include
## FAQ & Additional Info
### OnChatMessage Patch
Nampower applies an in-memory patch at `0x0049DB9F` to pass the appropriate unit guid needed by the client to display chat bubbles for following message types:
Handled `CHAT_COMMAND_ID` values:
- `0x02` = `CHAT_CMD_RAID`
- `0x06` = `CHAT_CMD_WHISPER`
- `0x57` = `CHAT_CMD_RAID_LEADER`
- `0x58` = `CHAT_CMD_RAID_WARNING`
- `0x5C` = `CHAT_CMD_BATTLEGROUND`
- `0x5D` = `CHAT_CMD_BATTLEGROUND_LEADER`
### How does queuing work?
Trying to cast a spell within the appropriate window before your current spell finishes will queue your new spell.
The spell will be cast as soon as possible after the current spell finishes.
+14136 -13819
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -14,6 +14,8 @@ set(SOURCE_FILES
game.cpp
main.hpp
main.cpp
chat.hpp
chat.cpp
offsets.hpp
helper.cpp
helper.hpp
+171
View File
@@ -0,0 +1,171 @@
#include "chat.hpp"
#include "logging.hpp"
#include "offsets.hpp"
#include <cstdint>
#include <cstring>
#include <windows.h>
namespace Nampower {
// Server-side context for why this patch exists:
// The packet builder sends senderGuid twice for a subset of message types:
// case CHAT_MSG_SAY:
// case CHAT_MSG_PARTY:
// case CHAT_MSG_YELL:
// data << ObjectGuid(senderGuid);
// data << ObjectGuid(senderGuid);
// break;
//
// The second senderGuid is used by the client chat-bubble path. Many other chat
// message types only send senderGuid once:
// default:
// data << ObjectGuid(senderGuid);
// break;
//
// For those single-guid messages, chat bubbles typically do not appear unless we
// mirror the expected second value at this callsite.
__declspec(naked) void OnChatMessagePatchFn() {
// Handle selected chat types:
// 0x02 = CHAT_CMD_RAID
// 0x06 = CHAT_CMD_WHISPER
// 0x57 = CHAT_CMD_RAID_LEADER
// 0x58 = CHAT_CMD_RAID_WARNING
// 0x5C = CHAT_CMD_BATTLEGROUND
// 0x5D = CHAT_CMD_BATTLEGROUND_LEADER
__asm {
mov ecx, dword ptr [ebp - 0x8]
mov edx, dword ptr [ebp - 0xC]
movzx eax, byte ptr [ebp + 0xF]
cmp al, 0x2
je patch_set
cmp al, 0x6
je patch_set
cmp al, 0x57
je patch_set
cmp al, 0x58
je patch_set
cmp al, 0x5C
je patch_set
cmp al, 0x5D
jne patch_done
patch_set:
mov dword ptr [ebp - 0x34], ecx
mov dword ptr [ebp - 0x38], edx
patch_done:
ret
}
}
void ApplyOnChatMessagePatch() {
auto *patchAddr = reinterpret_cast<std::uint8_t *>(
static_cast<std::uintptr_t>(Offsets::OnChatMessagePatchCallSite));
auto const patchTarget = reinterpret_cast<std::uintptr_t>(&OnChatMessagePatchFn);
auto const rel32 = static_cast<std::int32_t>(
patchTarget - (reinterpret_cast<std::uintptr_t>(patchAddr) + 5));
DWORD oldProtect = 0;
if (!VirtualProtect(patchAddr, 6, PAGE_EXECUTE_READWRITE, &oldProtect)) {
DEBUG_LOG("Failed to apply OnChatMessage patch: VirtualProtect RW failed");
return;
}
patchAddr[0] = 0xE8;
std::memcpy(patchAddr + 1, &rel32, sizeof(rel32));
patchAddr[5] = 0x90;
DWORD restoreProtect = 0;
VirtualProtect(patchAddr, 6, oldProtect, &restoreProtect);
FlushInstructionCache(GetCurrentProcess(), patchAddr, 6);
DEBUG_LOG("Applied OnChatMessage patch at 0x" << std::hex
<< static_cast<std::uint32_t>(Offsets::OnChatMessagePatchCallSite)
<< std::dec);
}
void CGUnit_C_AddChatBubbleHook(hadesmem::PatchDetourBase *detour,
uintptr_t *unitPtr,
void *dummy_edx,
game::CHAT_COMMAND_ID chatType,
char *msg) {
const char *bubbleEnabledCvar = nullptr;
switch (chatType) {
case game::CHAT_CMD_SAY:
case game::CHAT_CMD_YELL:
bubbleEnabledCvar = "ChatBubbles";
break;
case game::CHAT_CMD_PARTY:
bubbleEnabledCvar = "ChatBubblesParty";
break;
case game::CHAT_CMD_WHISPER:
case game::CHAT_CMD_WHISPER_INFORM:
bubbleEnabledCvar = "NP_ChatBubblesWhisper";
break;
case game::CHAT_CMD_RAID_LEADER:
case game::CHAT_CMD_RAID_WARNING:
bubbleEnabledCvar = "NP_ChatBubblesRaid";
break;
case game::CHAT_CMD_BATTLEGROUND:
case game::CHAT_CMD_BATTLEGROUND_LEADER:
bubbleEnabledCvar = "NP_ChatBubblesBattleground";
break;
default:
break;
}
if (bubbleEnabledCvar) {
// Check if the bubble cvar is enabled.
auto const cvarLookup = hadesmem::detail::AliasCast<CVarLookupT>(Offsets::CVarLookup);
auto *cvar = cvarLookup(bubbleEnabledCvar);
if (!cvar || cvar->m_intValue == 0) return;
// Get active player object.
uint64_t playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto *playerObj = game::GetObjectPtr(playerGuid);
if (!playerObj) return;
auto unitPos = game::UnitGetPosition(unitPtr);
auto playerPos = game::UnitGetPosition(playerObj);
// Distance check against NP_ChatBubbleDistance (cached as squared float).
float distLimitSq = *reinterpret_cast<float *>(Offsets::ChatBubbleDistanceSq);
float dx = playerPos.x - unitPos.x;
float dy = playerPos.y - unitPos.y;
float dz = playerPos.z - unitPos.z;
if (dx * dx + dy * dy + dz * dz > distLimitSq) return;
// Get chat color.
uint32_t color = 0;
auto const getChatColor = hadesmem::detail::AliasCast<CGChat_GetChatColorT>(Offsets::CGChat_GetChatColor);
getChatColor(&color, chatType);
// Remove any existing bubble on this unit.
auto const removeBubble = hadesmem::detail::AliasCast<CGUnit_RemoveChatBubbleT>(Offsets::CGUnit_RemoveChatBubble);
removeBubble(unitPtr, nullptr);
// Get a new bubble frame from the current frame.
auto *currentFrame = *reinterpret_cast<void **>(Offsets::CurrentFramePointer);
if (!currentFrame) return;
auto const getNewBubble = hadesmem::detail::AliasCast<CGChatBubbleFrame_GetNewChatBubbleT>(Offsets::CGChatBubbleFrame_GetNewChatBubble);
uintptr_t *bubbleFrame = getNewBubble(currentFrame);
if (!bubbleFrame) return;
unitPtr[0x399] = reinterpret_cast<uintptr_t>(bubbleFrame); // store bubble frame pointer in unit
// Read unit GUID from CGObject::m_obj->m_guid.
uint64_t unitGuid = game::UnitGetGuid(unitPtr);
if (unitGuid == 0) return;
// Initialize the bubble with the unit GUID, message, and color.
auto const initBubble = hadesmem::detail::AliasCast<CGChatBubbleFrame_InitializeT>(Offsets::CGChatBubbleFrame_Initialize);
initBubble(bubbleFrame, &unitGuid, msg, &color);
} else {
// Just call original.
auto const orig = detour->GetTrampolineT<CGUnit_C_AddChatBubbleT>();
orig(unitPtr, dummy_edx, chatType, msg);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "main.hpp"
namespace Nampower {
void CGUnit_C_AddChatBubbleHook(hadesmem::PatchDetourBase *detour,
uintptr_t *unitPtr,
void *dummy_edx,
game::CHAT_COMMAND_ID chatType,
char *msg);
void ApplyOnChatMessagePatch();
}
+8
View File
@@ -171,6 +171,14 @@ namespace game {
return *reinterpret_cast<uint64_t *>(Offsets::LockedTargetGuid);
}
C3Vector UnitGetPosition(uintptr_t *unit) {
using GetPositionT = C3Vector* (__fastcall *)(void *thisptr, void *dummy_edx, C3Vector *outPos);
auto getPos = reinterpret_cast<GetPositionT>((*reinterpret_cast<void ***>(unit))[5]); // vtable[0x14/4]
C3Vector pos{};
getPos(unit, nullptr, &pos);
return pos;
}
uint64_t UnitGetGuid(uintptr_t *unit) {
if (!unit) {
return 0;
+182 -82
View File
@@ -1082,59 +1082,54 @@ namespace game {
MAX_SPELL_TARGETS
};
enum SpellMissInfo
{
SPELL_MISS_NONE = 0,
SPELL_MISS_MISS = 1,
SPELL_MISS_RESIST = 2,
SPELL_MISS_DODGE = 3,
SPELL_MISS_PARRY = 4,
SPELL_MISS_BLOCK = 5,
SPELL_MISS_EVADE = 6,
SPELL_MISS_IMMUNE = 7,
SPELL_MISS_IMMUNE2 = 8,
SPELL_MISS_DEFLECT = 9,
SPELL_MISS_ABSORB = 10,
SPELL_MISS_REFLECT = 11
enum SpellMissInfo {
SPELL_MISS_NONE = 0,
SPELL_MISS_MISS = 1,
SPELL_MISS_RESIST = 2,
SPELL_MISS_DODGE = 3,
SPELL_MISS_PARRY = 4,
SPELL_MISS_BLOCK = 5,
SPELL_MISS_EVADE = 6,
SPELL_MISS_IMMUNE = 7,
SPELL_MISS_IMMUNE2 = 8,
SPELL_MISS_DEFLECT = 9,
SPELL_MISS_ABSORB = 10,
SPELL_MISS_REFLECT = 11
};
enum SpellHitType
{
SPELL_HIT_TYPE_CRIT_DEBUG = 0x01,
SPELL_HIT_TYPE_CRIT = 0x02,
SPELL_HIT_TYPE_HIT_DEBUG = 0x04,
SPELL_HIT_TYPE_SPLIT = 0x08,
SPELL_HIT_TYPE_VICTIM_IS_ATTACKER = 0x10,
SPELL_HIT_TYPE_ATTACK_TABLE_DEBUG = 0x20
enum SpellHitType {
SPELL_HIT_TYPE_CRIT_DEBUG = 0x01,
SPELL_HIT_TYPE_CRIT = 0x02,
SPELL_HIT_TYPE_HIT_DEBUG = 0x04,
SPELL_HIT_TYPE_SPLIT = 0x08,
SPELL_HIT_TYPE_VICTIM_IS_ATTACKER = 0x10,
SPELL_HIT_TYPE_ATTACK_TABLE_DEBUG = 0x20
};
enum SpellDmgClass
{
SPELL_DAMAGE_CLASS_NONE = 0,
SPELL_DAMAGE_CLASS_MAGIC = 1,
SPELL_DAMAGE_CLASS_MELEE = 2,
SPELL_DAMAGE_CLASS_RANGED = 3
enum SpellDmgClass {
SPELL_DAMAGE_CLASS_NONE = 0,
SPELL_DAMAGE_CLASS_MAGIC = 1,
SPELL_DAMAGE_CLASS_MELEE = 2,
SPELL_DAMAGE_CLASS_RANGED = 3
};
enum SpellPreventionType
{
SPELL_PREVENTION_TYPE_NONE = 0,
SPELL_PREVENTION_TYPE_SILENCE = 1,
SPELL_PREVENTION_TYPE_PACIFY = 2
enum SpellPreventionType {
SPELL_PREVENTION_TYPE_NONE = 0,
SPELL_PREVENTION_TYPE_SILENCE = 1,
SPELL_PREVENTION_TYPE_PACIFY = 2
};
enum SpellCastFlags
{
CAST_FLAG_NONE = 0x00000000,
CAST_FLAG_HIDDEN_COMBATLOG = 0x00000001, // hide in combat log?
CAST_FLAG_UNKNOWN2 = 0x00000002,
CAST_FLAG_UNKNOWN3 = 0x00000004,
CAST_FLAG_UNKNOWN4 = 0x00000008,
CAST_FLAG_UNKNOWN5 = 0x00000010,
CAST_FLAG_AMMO = 0x00000020, // Projectiles visual
CAST_FLAG_UNKNOWN7 = 0x00000040, // !0x41 mask used to call CGTradeSkillInfo::DoRecast
CAST_FLAG_UNKNOWN8 = 0x00000080,
CAST_FLAG_UNKNOWN9 = 0x00000100,
enum SpellCastFlags {
CAST_FLAG_NONE = 0x00000000,
CAST_FLAG_HIDDEN_COMBATLOG = 0x00000001, // hide in combat log?
CAST_FLAG_UNKNOWN2 = 0x00000002,
CAST_FLAG_UNKNOWN3 = 0x00000004,
CAST_FLAG_UNKNOWN4 = 0x00000008,
CAST_FLAG_UNKNOWN5 = 0x00000010,
CAST_FLAG_AMMO = 0x00000020, // Projectiles visual
CAST_FLAG_UNKNOWN7 = 0x00000040, // !0x41 mask used to call CGTradeSkillInfo::DoRecast
CAST_FLAG_UNKNOWN8 = 0x00000080,
CAST_FLAG_UNKNOWN9 = 0x00000100,
};
// SpellEntry::Targets
@@ -1160,16 +1155,15 @@ namespace game {
};
constexpr uint32_t UnitActionButtonAction(uint32_t x) { return x & 0x00FFFFFFu; }
constexpr uint32_t UnitActionButtonType(uint32_t x) { return (x & 0xFF000000u) >> 24; }
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 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 {
@@ -1666,9 +1660,108 @@ namespace game {
KEY_DOWN = 598,
KEY_UP = 599,
UNIT_CASTEVENT=600 // superwow
UNIT_CASTEVENT = 600 // superwow
};
typedef enum CHAT_COMMAND_ID {
CHAT_CMD_ADDON = 0xFFFFFFFF,
CHAT_CMD_SAY = 0x00,
CHAT_CMD_PARTY = 0x01,
CHAT_CMD_RAID = 0x02,
CHAT_CMD_GUILD = 0x03,
CHAT_CMD_OFFICER = 0x04,
CHAT_CMD_YELL = 0x05,
CHAT_CMD_WHISPER = 0x06,
CHAT_CMD_WHISPER_INFORM = 0x07,
CHAT_CMD_EMOTE = 0x08,
CHAT_CMD_TEXT_EMOTE = 0x09,
CHAT_CMD_SYSTEM = 0x0A,
CHAT_CMD_MONSTER_SAY = 0x0B,
CHAT_CMD_MONSTER_YELL = 0x0C,
CHAT_CMD_MONSTER_EMOTE = 0x0D,
CHAT_CMD_CHANNEL = 0x0E,
CHAT_CMD_CHANNEL_JOIN = 0x0F,
CHAT_CMD_CHANNEL_LEAVE = 0x10,
CHAT_CMD_CHANNEL_LIST = 0x11,
CHAT_CMD_CHANNEL_NOTICE = 0x12,
CHAT_CMD_CHANNEL_NOTICE_USER = 0x13,
CHAT_CMD_AFK = 0x14,
CHAT_CMD_DND = 0x15,
CHAT_CMD_IGNORED = 0x16,
CHAT_CMD_SKILL = 0x17,
CHAT_CMD_LOOT = 0x18,
CHAT_CMD_COMBAT_MISC_INFO = 0x19,
CHAT_CMD_MONSTER_WHISPER = 0x1A,
CHAT_CMD_COMBAT_SELF_HITS = 0x1B,
CHAT_CMD_COMBAT_SELF_MISSES = 0x1C,
CHAT_CMD_COMBAT_PET_HITS = 0x1D,
CHAT_CMD_COMBAT_PET_MISSES = 0x1E,
CHAT_CMD_COMBAT_PARTY_HITS = 0x1F,
CHAT_CMD_COMBAT_PARTY_MISSES = 0x20,
CHAT_CMD_COMBAT_FRIENDLYPLAYER_HITS = 0x21,
CHAT_CMD_COMBAT_FRIENDLYPLAYER_MISSES = 0x22,
CHAT_CMD_COMBAT_HOSTILEPLAYER_HITS = 0x23,
CHAT_CMD_COMBAT_HOSTILEPLAYER_MISSES = 0x24,
CHAT_CMD_COMBAT_CREATURE_VS_SELF_HITS = 0x25,
CHAT_CMD_COMBAT_CREATURE_VS_SELF_MISSES = 0x26,
CHAT_CMD_COMBAT_CREATURE_VS_PARTY_HITS = 0x27,
CHAT_CMD_COMBAT_CREATURE_VS_PARTY_MISSES = 0x28,
CHAT_CMD_COMBAT_CREATURE_VS_CREATURE_HITS = 0x29,
CHAT_CMD_COMBAT_CREATURE_VS_CREATURE_MISSES = 0x2A,
CHAT_CMD_COMBAT_FRIENDLY_DEATH = 0x2B,
CHAT_CMD_COMBAT_HOSTILE_DEATH = 0x2C,
CHAT_CMD_COMBAT_XP_GAIN = 0x2D,
CHAT_CMD_SPELL_SELF_DAMAGE = 0x2E,
CHAT_CMD_SPELL_SELF_BUFF = 0x2F,
CHAT_CMD_SPELL_PET_DAMAGE = 0x30,
CHAT_CMD_SPELL_PET_BUFF = 0x31,
CHAT_CMD_SPELL_PARTY_DAMAGE = 0x32,
CHAT_CMD_SPELL_PARTY_BUFF = 0x33,
CHAT_CMD_SPELL_FRIENDLYPLAYER_DAMAGE = 0x34,
CHAT_CMD_SPELL_FRIENDLYPLAYER_BUFF = 0x35,
CHAT_CMD_SPELL_HOSTILEPLAYER_DAMAGE = 0x36,
CHAT_CMD_SPELL_HOSTILEPLAYER_BUFF = 0x37,
CHAT_CMD_SPELL_CREATURE_VS_SELF_DAMAGE = 0x38,
CHAT_CMD_SPELL_CREATURE_VS_SELF_BUFF = 0x39,
CHAT_CMD_SPELL_CREATURE_VS_PARTY_DAMAGE = 0x3A,
CHAT_CMD_SPELL_CREATURE_VS_PARTY_BUFF = 0x3B,
CHAT_CMD_SPELL_CREATURE_VS_CREATURE_DAMAGE = 0x3C,
CHAT_CMD_SPELL_CREATURE_VS_CREATURE_BUFF = 0x3D,
CHAT_CMD_SPELL_TRADESKILLS = 0x3E,
CHAT_CMD_SPELL_DAMAGESHIELDS_ON_SELF = 0x3F,
CHAT_CMD_SPELL_DAMAGESHIELDS_ON_OTHERS = 0x40,
CHAT_CMD_SPELL_AURA_GONE_SELF = 0x41,
CHAT_CMD_SPELL_AURA_GONE_PARTY = 0x42,
CHAT_CMD_SPELL_AURA_GONE_OTHER = 0x43,
CHAT_CMD_SPELL_ITEM_ENCHANTMENTS = 0x44,
CHAT_CMD_SPELL_BREAK_AURA = 0x45,
CHAT_CMD_SPELL_PERIODIC_SELF_DAMAGE = 0x46,
CHAT_CMD_SPELL_PERIODIC_SELF_BUFFS = 0x47,
CHAT_CMD_SPELL_PERIODIC_PARTY_DAMAGE = 0x48,
CHAT_CMD_SPELL_PERIODIC_PARTY_BUFFS = 0x49,
CHAT_CMD_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE = 0x4A,
CHAT_CMD_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS = 0x4B,
CHAT_CMD_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE = 0x4C,
CHAT_CMD_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS = 0x4D,
CHAT_CMD_SPELL_PERIODIC_CREATURE_DAMAGE = 0x4E,
CHAT_CMD_SPELL_PERIODIC_CREATURE_BUFFS = 0x4F,
CHAT_CMD_SPELL_FAILED_LOCALPLAYER = 0x50,
CHAT_CMD_COMBAT_HONOR_GAIN = 0x51,
CHAT_CMD_BG_SYSTEM_NEUTRAL = 0x52,
CHAT_CMD_BG_SYSTEM_ALLIANCE = 0x53,
CHAT_CMD_BG_SYSTEM_HORDE = 0x54,
CHAT_CMD_COMBAT_FACTION_CHANGE = 0x55,
CHAT_CMD_MONEY = 0x56,
CHAT_CMD_RAID_LEADER = 0x57,
CHAT_CMD_RAID_WARNING = 0x58,
CHAT_CMD_RAID_BOSS_WHISPER = 0x59, // appears as whisper, yet name is not in client
CHAT_CMD_RAID_BOSS_EMOTE = 0x5A,
CHAT_CMD_FILTERED = 0x5B,
CHAT_CMD_BATTLEGROUND = 0x5C,
CHAT_CMD_BATTLEGROUND_LEADER = 0x5D,
CHAT_CMD_HARDCORE = 0x5E,
} CHAT_COMMAND_ID;
enum TypeMask {
TYPEMASK_OBJECT = 0x1,
TYPEMASK_ITEM = 0x2,
@@ -1824,36 +1917,36 @@ namespace game {
constexpr uint32_t OBJECT_END = 6; // ObjectFields: guid(2) + type + entry + scaleX + padding
enum ECorpseFields {
CORPSE_FIELD_OWNER = OBJECT_END + 0x00,
CORPSE_FIELD_FACING = OBJECT_END + 0x02,
CORPSE_FIELD_POS_X = OBJECT_END + 0x03,
CORPSE_FIELD_POS_Y = OBJECT_END + 0x04,
CORPSE_FIELD_POS_Z = OBJECT_END + 0x05,
CORPSE_FIELD_DISPLAY_ID = OBJECT_END + 0x06,
CORPSE_FIELD_ITEM = OBJECT_END + 0x07, // 19
CORPSE_FIELD_BYTES_1 = OBJECT_END + 0x1A,
CORPSE_FIELD_BYTES_2 = OBJECT_END + 0x1B,
CORPSE_FIELD_GUILD = OBJECT_END + 0x1C,
CORPSE_FIELD_FLAGS = OBJECT_END + 0x1D,
CORPSE_FIELD_DYNAMIC_FLAGS = OBJECT_END + 0x1E,
CORPSE_FIELD_PAD = OBJECT_END + 0x1F,
CORPSE_END = OBJECT_END + 0x20,
CORPSE_FIELD_OWNER = OBJECT_END + 0x00,
CORPSE_FIELD_FACING = OBJECT_END + 0x02,
CORPSE_FIELD_POS_X = OBJECT_END + 0x03,
CORPSE_FIELD_POS_Y = OBJECT_END + 0x04,
CORPSE_FIELD_POS_Z = OBJECT_END + 0x05,
CORPSE_FIELD_DISPLAY_ID = OBJECT_END + 0x06,
CORPSE_FIELD_ITEM = OBJECT_END + 0x07, // 19
CORPSE_FIELD_BYTES_1 = OBJECT_END + 0x1A,
CORPSE_FIELD_BYTES_2 = OBJECT_END + 0x1B,
CORPSE_FIELD_GUILD = OBJECT_END + 0x1C,
CORPSE_FIELD_FLAGS = OBJECT_END + 0x1D,
CORPSE_FIELD_DYNAMIC_FLAGS = OBJECT_END + 0x1E,
CORPSE_FIELD_PAD = OBJECT_END + 0x1F,
CORPSE_END = OBJECT_END + 0x20,
};
typedef struct CorpseFields {
uint64_t owner; // Size:2
float facing; // Size:1
float posX; // Size:1
float posY; // Size:1
float posZ; // Size:1
uint32_t displayId; // Size:1
uint32_t items[19]; // Size:19
uint32_t bytes1; // Size:1
uint32_t bytes2; // Size:1
uint32_t guild; // Size:1
uint32_t flags; // Size:1
uint32_t dynamicFlags; // Size:1
uint32_t pad; // Size:1
uint64_t owner; // Size:2
float facing; // Size:1
float posX; // Size:1
float posY; // Size:1
float posZ; // Size:1
uint32_t displayId; // Size:1
uint32_t items[19]; // Size:19
uint32_t bytes1; // Size:1
uint32_t bytes2; // Size:1
uint32_t guild; // Size:1
uint32_t flags; // Size:1
uint32_t dynamicFlags; // Size:1
uint32_t pad; // Size:1
} CorpseFields;
typedef struct UnitFields {
@@ -1954,6 +2047,7 @@ namespace game {
const char *GetSpellName(uint32_t spellId);
std::uint64_t ClntObjMgrGetActivePlayerGuid();
UnitFields *GetActivePlayerUnitFields();
std::uint64_t GetCurrentTargetGuid();
@@ -1962,16 +2056,22 @@ namespace game {
uint64_t UnitGetGuid(uintptr_t *unit);
C3Vector UnitGetPosition(uintptr_t *unit);
char *UnitGetName(uintptr_t *unit);
bool UnitCanAttackUnit(uintptr_t *unit1, uintptr_t *unit2);
uint64_t UnitGetTargetGuid(uintptr_t *unit);
uint64_t UnitGetOwnerGuid(uintptr_t *unit);
uint64_t UnitGetPetGuid(uintptr_t *unit);
uint64_t UnitGetOwnerGuidForGuid(uint64_t guid);
uint64_t UnitGetTargetGuidForGuid(uint64_t guid);
uint64_t UnitGetPetGuidForGuid(uint64_t guid);
bool UnitIsPvpFlagged(uintptr_t *unit);
+15 -2
View File
@@ -406,12 +406,25 @@ namespace Nampower {
}
float GetNameplateDistance() {
auto const distanceSquared = *reinterpret_cast<float *>(Offsets::NameplateDistance);
auto const distanceSquared = *reinterpret_cast<float *>(Offsets::NameplateDistanceSq);
return sqrtf(distanceSquared);
}
void SetNameplateDistance(float distance) {
*reinterpret_cast<float *>(Offsets::NameplateDistance) = distance * distance;
*reinterpret_cast<float *>(Offsets::NameplateDistanceSq) = distance * distance;
}
float GetChatBubbleDistance() {
auto const distanceSquared = *reinterpret_cast<float *>(Offsets::ChatBubbleDistanceSq);
return sqrtf(distanceSquared);
}
void SetChatBubbleDistance(float distance) {
auto *addr = reinterpret_cast<float *>(Offsets::ChatBubbleDistanceSq);
DWORD oldProtect;
VirtualProtect(addr, sizeof(float), PAGE_EXECUTE_READWRITE, &oldProtect);
*addr = distance * distance;
VirtualProtect(addr, sizeof(float), oldProtect, &oldProtect);
}
bool IsTargetingTerrainSpell() {
+3 -1
View File
@@ -88,9 +88,11 @@ namespace Nampower {
uint64_t GetUnitGuidFromString(const char *unitToken);
float GetNameplateDistance();
void SetNameplateDistance(float distance);
float GetChatBubbleDistance();
void SetChatBubbleDistance(float distance);
// Lua table helper functions
inline void PushTableValue(uintptr_t *luaState, char *key, uint32_t value) {
lua_pushstring(luaState, key);
+64
View File
@@ -34,6 +34,7 @@
#include "spellevents.hpp"
#include "spellcast.hpp"
#include "misc_scripts.hpp"
#include "chat.hpp"
#include "file_scripts.hpp"
#include "spell_scripts.hpp"
#include "player_scripts.hpp"
@@ -159,6 +160,7 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<GetSpellSlotFromLuaT> > gGetSpellSlotFromLuaDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCSimpleFrame_GetNameDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_AddChatBubbleT> > gCGUnit_C_AddChatBubbleDetour;
std::unique_ptr<hadesmem::PatchDetour<CSimpleTop_OnKeyDownT> > gCSimpleTop_OnKeyDownDetour;
std::unique_ptr<hadesmem::PatchDetour<CSimpleTop_OnKeyUpT> > gCSimpleTop_OnKeyUpDetour;
std::unique_ptr<hadesmem::PatchDetour<GetGUIDFromNameT> > gGetGUIDFromNameDetour;
@@ -167,6 +169,7 @@ namespace Nampower {
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<CGPetInfo_SendPetActionT> > gCGPetInfo_SendPetActionDetour;
std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour;
std::unique_ptr<hadesmem::PatchDetour<LoadScriptFunctionsT> > gGlueLoadScriptFunctionsDetour;
@@ -770,6 +773,10 @@ namespace Nampower {
SetNameplateDistance(distance);
DEBUG_LOG(
"Set NP_NameplateDistance to " << distance);
} else if (strcmp(cvar, "NP_ChatBubbleDistance") == 0) {
gUserSettings.chatBubbleDistance = static_cast<uint32_t>(atoi(value));
SetChatBubbleDistance(static_cast<float>(gUserSettings.chatBubbleDistance));
DEBUG_LOG("Set NP_ChatBubbleDistance to " << gUserSettings.chatBubbleDistance);
}
}
@@ -1265,6 +1272,16 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_InfernalAutocastData[] = "NP_InfernalAutocastData";
CVarRegister(NP_InfernalAutocastData, // 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
@@ -1285,6 +1302,46 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_ChatBubbleDistance[] = "NP_ChatBubbleDistance";
CVarRegister(NP_ChatBubbleDistance, // name
nullptr, // help
0, // unk1
std::to_string(GetChatBubbleDistance()).c_str(), // use game's value as default
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_ChatBubblesWhisper[] = "NP_ChatBubblesWhisper";
CVarRegister(NP_ChatBubblesWhisper, // name
nullptr, // help
0, // unk1
"0", // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_ChatBubblesRaid[] = "NP_ChatBubblesRaid";
CVarRegister(NP_ChatBubblesRaid, // name
nullptr, // help
0, // unk1
"0", // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_ChatBubblesBattleground[] = "NP_ChatBubblesBattleground";
CVarRegister(NP_ChatBubblesBattleground, // name
nullptr, // help
0, // unk1
"0", // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
// update from cvars
loadUserVar("NP_QueueCastTimeSpells");
loadUserVar("NP_QueueInstantSpells");
@@ -1338,6 +1395,8 @@ namespace Nampower {
loadUserVar("NP_ChannelLatencyReductionPercentage");
loadUserVar("NP_NameplateDistance");
loadUserVar("NP_ChatBubbleDistance");
loadUserVar("NP_ChatBubblesBattleground");
gBufferTimeMs = gUserSettings.minBufferTimeMs;
}
@@ -1513,6 +1572,8 @@ namespace Nampower {
&AttackRoundInfo_ReadPacketHook);
gCSimpleFrame_GetNameDetour = createHook<LuaScriptT>(process, Offsets::CSimpleFrame_GetName,
&CSimpleFrame_GetNameHook);
gCGUnit_C_AddChatBubbleDetour = createHook<CGUnit_C_AddChatBubbleT>(
process, Offsets::CGUnit_C_AddChatBubble, &CGUnit_C_AddChatBubbleHook);
gCSimpleTop_OnKeyDownDetour = createHook<CSimpleTop_OnKeyDownT>(
process, Offsets::CSimpleTop_OnKeyDown, &CSimpleTop_OnKeyDownHook);
gCSimpleTop_OnKeyUpDetour = createHook<CSimpleTop_OnKeyUpT>(
@@ -1525,6 +1586,8 @@ namespace Nampower {
process, Offsets::TogglePetSlotAutocast, &TogglePetSlotAutocastHook);
gCGPetInfo_GetPetSpellActionDetour = createHook<CGPetInfo_GetPetSpellActionT>(
process, Offsets::CGPetInfo_GetPetSpellAction, &CGPetInfo_GetPetSpellActionHook);
gCGPetInfo_SendPetActionDetour = createHook<CGPetInfo_SendPetActionT>(
process, Offsets::CGPetInfo_SendPetAction, &CGPetInfo_SendPetActionHook);
gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>(
process, Offsets::CGGameUI_ShowCombatFeedback, &CGGameUI_ShowCombatFeedbackHook);
gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::Player_LoadScriptFunctions,
@@ -1535,6 +1598,7 @@ namespace Nampower {
&FrameScript_CreateEventsHook);
gSetEventCountDetour = createHook<FramescriptSetEventCountT>(process, Offsets::Framescript_SetEventCount,
&Framescript_SetEventCountHook);
ApplyOnChatMessagePatch();
DEBUG_LOG("=== All hooks initialized successfully ===");
}
+7 -1
View File
@@ -26,7 +26,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 3;
constexpr uint32_t MINOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 3;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
@@ -90,6 +90,12 @@ namespace Nampower {
using Spell_C_HandleSpriteClickT = bool (__fastcall *)(game::CSpriteClickEvent *event);
using Spell_C_HandleTerrainClickT = uint32_t (__fastcall *)(game::CTerrainClickEvent *event);
using CGWorldFrame_OnLayerTrackTerrainT = void (__fastcall *)(void *thisptr, int dummy_edx, int param_1);
using CGUnit_C_AddChatBubbleT = void (__fastcall *)(uintptr_t *thisptr, void *dummy_edx, game::CHAT_COMMAND_ID chatType, char *msg);
using CGChat_GetChatColorT = void (__fastcall *)(uint32_t *color, game::CHAT_COMMAND_ID chatType);
using CGUnit_RemoveChatBubbleT = void (__fastcall *)(void *thisptr, void *dummy_edx);
using CGChatBubbleFrame_GetNewChatBubbleT = uintptr_t *(__thiscall *)(void *thisptr);
using CGChatBubbleFrame_InitializeT = void (__thiscall *)(uintptr_t *thisptr, uint64_t *unitGuid, char *msg, uint32_t *color);
using GetUnitPositionT = game::C3Vector* (__fastcall *)(void *thisptr, void *dummy_edx, game::C3Vector *outPos);
using CSimpleTop_OnKeyDownT = bool (__fastcall *)(EVENT_DATA_KEY *param_1, int param_2);
using CSimpleTop_OnKeyUpT = bool (__fastcall *)(EVENT_DATA_KEY *param_1, int param_2);
using IsModifierKeyDownT = uint32_t (__fastcall *)(int param_1);
+11 -1
View File
@@ -59,7 +59,8 @@ enum class Offsets : std::uint32_t {
GetSpellSlotFromLua = 0X004B3EC0,
OsGetAsyncTimeMs = 0X0042B790,
ChannelTargetGuid = 0xC4D980,
NameplateDistance = 0xC4D988, // float containing the distance squared ready to be pythagorean'd
NameplateDistanceSq = 0xC4D988, // float containing the distance squared ready to be pythagorean'd
ChatBubbleDistanceSq = 0x00806798,
DBCacheGetRecord = 0X0055BA30,
InvQuestionMark = 0X00847FE4,
@@ -225,6 +226,13 @@ enum class Offsets : std::uint32_t {
CGBuffBar_UpdateDuration = 0x004E4390,
GetBuffByIndex = 0X004E4430,
CGUnit_C_AddChatBubble = 0x00608ac0,
CGChat_GetChatColor = 0x0049e990,
CGUnit_RemoveChatBubble = 0x00608c00,
CGChatBubbleFrame_GetNewChatBubble = 0x004b1870,
CGChatBubbleFrame_Initialize = 0x004b1600,
OnChatMessagePatchCallSite = 0x0049DB9F,
CurrentFramePointer = 0x00b4b2bc,
CGUnit_C_ClearCastingSpell = 0x0060d040,
CGUnit_C_ClearSpellEffect = 0x00614150,
CGUnit_C_GetEquippedItemAtSlot = 0x005f0d60,
@@ -272,6 +280,8 @@ enum class Offsets : std::uint32_t {
CGPetInfo_SetPet = 0x004bc7e0,
TogglePetSlotAutocast = 0x004bcbb0,
CGPetInfo_GetPetSpellAction = 0x004bd190,
CGPetInfo_SendPetAction = 0x004bd1d0,
PetActionBarSlots = 0x00b71438,
PetReactionMode = 0x00b71468,
ActivePetGuid = 0x00b714a0,
};
+88 -31
View File
@@ -17,8 +17,13 @@
namespace Nampower {
constexpr uint32_t kAutocastEnabledType = 0xC1;
constexpr uint32_t kAutocastDisabledType = 0x81;
constexpr uint32_t kReactStateAggressive = 0x06000002; // REACT_AGGRESSIVE = 2
constexpr uint32_t kReactStateDefensive = 0x06000001; // REACT_DEFENSIVE = 1
constexpr uint32_t kReactStatePassive = 0x06000000; // REACT_PASSIVE = 0
constexpr uint32_t kFelguardDisplayId = 7970;
constexpr uint32_t kDoomguardDisplayId = 1912;
// constexpr uint32_t kDoomguardDisplayId = 4449; // imp for testing
constexpr uint32_t kInfernalDisplayId = 169;
uint32_t gPendingToggleAutocastSlot = 0;
uint32_t GetActivePetDisplayId() {
@@ -47,6 +52,9 @@ namespace Nampower {
if (displayId == kDoomguardDisplayId) {
return "NP_DoomguardAutocastData";
}
if (displayId == kInfernalDisplayId) {
return "NP_InfernalAutocastData";
}
return nullptr;
}
@@ -99,13 +107,9 @@ namespace Nampower {
}
}
void SaveGreaterDemonAutocastPreferences() {
SaveGreaterDemonAutocastPreferences(0, 0);
}
void SaveGreaterDemonAutocastPreferences(uint32_t spellSlot, uint32_t actionData) {
void SaveGreaterDemonPreferences(uint32_t spellSlot, uint32_t actionData) {
uint32_t displayId = GetActivePetDisplayId();
if (displayId != kFelguardDisplayId && displayId != kDoomguardDisplayId) {
if (displayId != kFelguardDisplayId && displayId != kDoomguardDisplayId && displayId != kInfernalDisplayId) {
return;
}
@@ -123,12 +127,20 @@ namespace Nampower {
uint32_t actionId = game::UnitActionButtonAction(actionData);
uint32_t type = game::UnitActionButtonType(actionData);
if (spellSlot >= 3 && spellSlot <= 7) {
if (spellSlot >= 3 && spellSlot <= 6) {
if (type == kAutocastEnabledType) {
autocastData[spellSlot] = actionData;
} else if (type == kAutocastDisabledType) {
autocastData.erase(spellSlot);
}
} else if (spellSlot >= 7 && spellSlot <= 9) {
// React states are mutually exclusive — clear all three, save the selected one
autocastData.erase(7);
autocastData.erase(8);
autocastData.erase(9);
autocastData[spellSlot] = actionData;
} else {
return;
}
std::string serialized = SerializeAutocastData(autocastData);
@@ -140,8 +152,8 @@ namespace Nampower {
LuaCall(lua.c_str());
DEBUG_LOG("Saved " << cvarName << "=" << serialized
<< " (slot=" << spellSlot << ", actionData=" << actionData
<< ", actionId=" << actionId << ", type=0x" << std::hex << type << std::dec << ")");
<< " (slot=" << spellSlot << ", actionData=" << actionData
<< ", actionId=" << actionId << ", type=0x" << std::hex << type << std::dec << ")");
}
void RestoreGreaterDemonAutocastPreferences() {
@@ -178,41 +190,57 @@ namespace Nampower {
}
constexpr uint32_t CMSG_PET_SET_ACTION = 372;
constexpr uint32_t CMSG_PET_ACTION = 373;
uint32_t packetsSent = 0;
for (auto const &entry: autocastData) {
uint32_t spellSlot = entry.first;
uint32_t actionData = entry.second;
if (spellSlot < 3 || spellSlot > 7) {
if (spellSlot < 3 || spellSlot > 9) {
continue;
}
auto *petActionBarSlots = reinterpret_cast<uint32_t *>(Offsets::PetActionBarSlots);
if (petActionBarSlots) {
petActionBarSlots[spellSlot] = actionData;
petActionBarSlots[spellSlot] = actionData;
if (spellSlot >= 7) {
// React state: replicate SetPetReactionMode — write react state into lower byte of PetReactionMode
auto *reactionMode = reinterpret_cast<uint32_t *>(Offsets::PetReactionMode);
uint32_t reactState = game::UnitActionButtonAction(actionData);
*reactionMode = (*reactionMode & 0xffffff00u) | reactState;
// Also notify the server: CMSG_PET_ACTION (petGuid, actionData, targetGuid)
CDataStore dataStore;
dataStore.Put(CMSG_PET_ACTION);
dataStore.Put(petGuid);
dataStore.Put(actionData);
dataStore.Put(uint64_t(0)); // targetGuid = 0
dataStore.Finalize();
clientServicesSend(&dataStore);
packetsSent++;
} else {
// Autocast: CMSG_PET_SET_ACTION (petGuid, slot, actionData)
CDataStore dataStore;
dataStore.Put(CMSG_PET_SET_ACTION);
dataStore.Put(petGuid);
dataStore.Put(spellSlot);
dataStore.Put(actionData);
dataStore.Finalize();
clientServicesSend(&dataStore);
packetsSent++;
}
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);
}
signalEvent(game::PET_BAR_UPDATE);
DEBUG_LOG("Restored greater demon autocast for displayId=" << displayId
<< " petGuid=" << petGuid
<< " cvar=" << cvarName
<< " packetsSent=" << packetsSent);
<< " petGuid=" << petGuid
<< " cvar=" << cvarName
<< " packetsSent=" << packetsSent);
}
void CGPetInfo_SetPetHook(hadesmem::PatchDetourBase *detour, void *thisptr, void *dummy_edx,
@@ -247,14 +275,43 @@ namespace Nampower {
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);
SaveGreaterDemonPreferences(spellSlot, actionData);
}
}
void CGPetInfo_SendPetActionHook(hadesmem::PatchDetourBase *detour, uint32_t *action, uint64_t *target) {
auto const orig = detour->GetTrampolineT<CGPetInfo_SendPetActionT>();
orig(action, target);
if (!action) return;
auto actionData = action[0];
auto const activePetGuid = *reinterpret_cast<uint64_t *>(Offsets::ActivePetGuid);
if (!gUserSettings.preserveGreaterDemonAutocast || activePetGuid == 0) return;
auto spellSlot = 0;
switch (actionData) {
case kReactStateAggressive:
spellSlot = 7;
break;
case kReactStateDefensive:
spellSlot = 8;
break;
case kReactStatePassive:
spellSlot = 9;
break;
default:
return;
}
DEBUG_LOG("Saving greater demon react state slot=" << spellSlot << " actionData=" << actionData);
SaveGreaterDemonPreferences(spellSlot, actionData);
}
}
+3 -2
View File
@@ -10,12 +10,13 @@ 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);
using CGPetInfo_SendPetActionT = void (__fastcall *)(uint32_t *action, uint64_t *target);
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 CGPetInfo_SendPetActionHook(hadesmem::PatchDetourBase *detour, uint32_t *action, uint64_t *target);
void SaveGreaterDemonAutocastPreferences();
void SaveGreaterDemonAutocastPreferences(uint32_t spellSlot, uint32_t actionData);
void SaveGreaterDemonPreferences(uint32_t spellSlot, uint32_t actionData);
void RestoreGreaterDemonAutocastPreferences();
}
+5
View File
@@ -470,6 +470,11 @@ namespace Nampower {
}
auto const spell = game::GetSpellInfo(spellId);
if (!spell) {
DEBUG_LOG("Spell info not found for spell id " << spellId);
auto const castSpell = detour->GetTrampolineT<Spell_C_CastSpellT>();
return castSpell(casterUnit, spellId, item, guid);
}
auto const spellIsOnSwing = SpellIsOnSwing(spell);
auto const spellName = game::GetSpellName(spellId);
auto currentTime = GetTime();
+1
View File
@@ -56,6 +56,7 @@ struct UserSettings {
uint32_t nonGcdBufferTimeMs = 100;
int32_t channelLatencyReductionPercentage = 75;
uint32_t chatBubbleDistance = 60;
};
enum CastType {