mirror of
https://github.com/brues-code/nampower.git
synced 2026-10-07 06:00:00 +00:00
add chat bubble options, store reaction states for greater demons, check if spell is missing from dbc in castspell
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@ set(SOURCE_FILES
|
||||
game.cpp
|
||||
main.hpp
|
||||
main.cpp
|
||||
chat.hpp
|
||||
chat.cpp
|
||||
offsets.hpp
|
||||
helper.cpp
|
||||
helper.hpp
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -56,6 +56,7 @@ struct UserSettings {
|
||||
uint32_t nonGcdBufferTimeMs = 100;
|
||||
|
||||
int32_t channelLatencyReductionPercentage = 75;
|
||||
uint32_t chatBubbleDistance = 60;
|
||||
};
|
||||
|
||||
enum CastType {
|
||||
|
||||
Reference in New Issue
Block a user