add spam protection cvar, prevent right click pvp attack, attempt to fix/log crashes on launch

This commit is contained in:
avitasia
2025-09-29 10:39:17 -07:00
parent dc38b57122
commit 288fe1c4e8
7 changed files with 238 additions and 47 deletions
+4
View File
@@ -88,8 +88,12 @@ SET NP_TargetingQueueWindowMs "1000"
- `NP_PreventRightClickTargetChange` - Whether to prevent right-clicking from changing your current target when in combat. If you don't have a target right click will still change your target even with this on. This is mainly to prevent accidentally changing targets in combat when trying to adjust your camera. 0 to disable, 1 to enable. Default is 0.
- `NP_PreventRightClickPvPAttack` - Whether to prevent right-clicking on PvP flagged players to avoid accidental PvP attacks. 0 to disable, 1 to enable. Default is 1.
- `NP_DoubleCastToEndChannelEarly` - Whether to allow double casting a spell within 350ms to end channeling on the next tick. Takes into account your ChannelLatencyReductionPercentage. 0 to disable, 1 to enable. Default is 0.
- `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_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.
+36
View File
@@ -152,4 +152,40 @@ namespace game {
return unitFields->target;
}
bool UnitIsPvpFlagged(uintptr_t *unit) {
if (!unit) {
return false;
}
auto *unitFields = *reinterpret_cast<UnitFields **>(unit + 68);
if (unitFields == nullptr) {
// we don't have attribute info.
return false;
}
auto flags = unitFields->flags;
return (flags & UNIT_FLAG_PVP);
}
bool UnitIsInCombat(uintptr_t *unit) {
if (!unit) {
return false;
}
auto *unitFields = *reinterpret_cast<UnitFields **>(unit + 68);
if (unitFields == nullptr) {
// we don't have attribute info.
return false;
}
auto flags = unitFields->flags;
if ((flags & UNIT_FLAG_IN_COMBAT) != 0) {
return true;
} else {
return false;
}
}
}
+42
View File
@@ -1190,6 +1190,45 @@ namespace game {
};//Size=0x0010
enum UnitFlags {
UNIT_FLAG_NONE = 0x00000000,
UNIT_FLAG_UNK_0 = 0x00000001, // Movement checks disabled, likely paired with loss of client control packet.
UNIT_FLAG_SPAWNING = 0x00000002, // not attackable
UNIT_FLAG_DISABLE_MOVE = 0x00000004,
UNIT_FLAG_PLAYER_CONTROLLED = 0x00000008, // players, pets, totems, guardians, companions, charms, any units associated with players
UNIT_FLAG_PET_RENAME = 0x00000010, // Old pet rename: moved to UNIT_FIELD_BYTES_2,2 in TBC+
UNIT_FLAG_PET_ABANDON = 0x00000020, // Old pet abandon: moved to UNIT_FIELD_BYTES_2,2 in TBC+
UNIT_FLAG_UNK_6 = 0x00000040,
UNIT_FLAG_IMMUNE_TO_PLAYER = 0x00000100, // Target is immune to players
UNIT_FLAG_IMMUNE_TO_NPC = 0x00000200, // Target is immune to creatures
UNIT_FLAG_PVP = 0x00001000,
UNIT_FLAG_SILENCED = 0x00002000, // silenced, 2.1.1
UNIT_FLAG_UNK_14 = 0x00004000,
UNIT_FLAG_USE_SWIM_ANIMATION = 0x00008000,
UNIT_FLAG_NON_ATTACKABLE_2 = 0x00010000, // removes attackable icon, if on yourself, cannot assist self but can cast TARGET_UNIT_CASTER spells - added by SPELL_AURA_MOD_UNATTACKABLE
UNIT_FLAG_PACIFIED = 0x00020000,
UNIT_FLAG_STUNNED = 0x00040000, // Unit is a subject to stun, turn and strafe movement disabled
UNIT_FLAG_IN_COMBAT = 0x00080000,
UNIT_FLAG_TAXI_FLIGHT = 0x00100000, // Unit is on taxi, paired with a duplicate loss of client control packet (likely a legacy serverside hack). Disables any spellcasts not allowed in taxi flight client-side.
UNIT_FLAG_CONFUSED = 0x00400000, // Unit is a subject to confused movement, movement checks disabled, paired with loss of client control packet.
UNIT_FLAG_FLEEING = 0x00800000, // Unit is a subject to fleeing movement, movement checks disabled, paired with loss of client control packet.
UNIT_FLAG_POSSESSED = 0x01000000, // Unit is under remote control by another unit, movement checks disabled, paired with loss of client control packet. New master is allowed to use melee attack and can't select this unit via mouse in the world (as if it was own character).
UNIT_FLAG_NOT_SELECTABLE = 0x02000000,
UNIT_FLAG_SKINNABLE = 0x04000000,
UNIT_FLAG_AURAS_VISIBLE = 0x08000000, // magic detect
UNIT_FLAG_SHEATHE = 0x40000000,
UNIT_FLAG_IMMUNE = 0x80000000, // Immune to damage
// [-ZERO] TBC enumerations [?]
UNIT_FLAG_NOT_ATTACKABLE_1 = 0x00000080, // ?? (UNIT_FLAG_PLAYER_CONTROLLED | UNIT_FLAG_NOT_ATTACKABLE_1) is NON_PVP_ATTACKABLE
UNIT_FLAG_LOOTING = 0x00000400, // loot animation
UNIT_FLAG_PET_IN_COMBAT = 0x00000800, // in combat?, 2.0.8
UNIT_FLAG_DISARMED = 0x00200000, // disable melee spells casting..., "Required melee weapon" added to melee spells tooltip.
UNIT_FLAG_UNK_28 = 0x10000000,
UNIT_FLAG_UNK_29 = 0x20000000, // used in Feing Death spell
};
typedef struct UnitFields {
uint64_t charm; // Size:2
uint64_t summon; // Size:2
@@ -1293,4 +1332,7 @@ namespace game {
uint64_t UnitGetTargetGuid(uintptr_t *unit);
bool UnitIsPvpFlagged(uintptr_t *unit);
bool UnitIsInCombat(uintptr_t *unit);
}
+135 -33
View File
@@ -45,6 +45,8 @@
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cstdio>
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE, uint32_t, void *);
@@ -155,8 +157,8 @@ namespace Nampower {
function(code, "Unused");
}
uintptr_t* GetLuaStatePtr() {
typedef uintptr_t* (__fastcall* GETCONTEXT)(void);
uintptr_t *GetLuaStatePtr() {
typedef uintptr_t *(__fastcall *GETCONTEXT)(void);
static auto p_GetContext = reinterpret_cast<GETCONTEXT>(0x7040D0);
return p_GetContext();
}
@@ -478,12 +480,22 @@ namespace Nampower {
// combat check from Script_UnitAffectingCombat
// only prevent right click if in combat
if (unit && ((*(uint32_t *) (*(int32_t *) (unit + 0x110) + 0xa0) >> 0x13 & 1) != 0)) {
if (unit && game::UnitIsInCombat(unit)) {
return 1;
}
}
}
if (gUserSettings.preventRightClickPvPAttack) {
// check if clicking player
auto playerUnit = game::ClntObjMgrObjectPtr(game::TYPEMASK_PLAYER, objectGUID);
if (playerUnit && game::UnitIsPvpFlagged(playerUnit)) {
// check if they have pvp enabled
return 1;
}
}
return onSpriteRightClick(objectGUID);
}
@@ -543,10 +555,18 @@ namespace Nampower {
gUserSettings.preventRightClickTargetChange = atoi(value) != 0;
DEBUG_LOG("Set NP_PreventRightClickTargetChange to " << gUserSettings.preventRightClickTargetChange);
} else if (strcmp(cvar, "NP_PreventRightClickPvPAttack") == 0) {
gUserSettings.preventRightClickPvPAttack = atoi(value) != 0;
DEBUG_LOG("Set NP_PreventRightClickPvPAttack to " << gUserSettings.preventRightClickPvPAttack);
} else if (strcmp(cvar, "NP_DoubleCastToEndChannelEarly") == 0) {
gUserSettings.doubleCastToEndChannelEarly = atoi(value) != 0;
DEBUG_LOG("Set NP_DoubleCastToEndChannelEarly to " << gUserSettings.doubleCastToEndChannelEarly);
} 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_MinBufferTimeMs") == 0) {
gUserSettings.minBufferTimeMs = atoi(value);
DEBUG_LOG("Set NP_MinBufferTimeMs and current buffer to " << gUserSettings.minBufferTimeMs);
@@ -624,19 +644,43 @@ namespace Nampower {
}
}
bool isFileInUse(const char *filename) {
HANDLE file = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION) {
return true;
}
return false;
}
CloseHandle(file);
return false;
}
bool safeRemove(const char *filename) {
if (isFileInUse(filename)) {
return false;
}
return remove(filename) == 0;
}
bool safeRename(const char *oldname, const char *newname) {
if (isFileInUse(oldname) || isFileInUse(newname)) {
return false;
}
return rename(oldname, newname) == 0;
}
void loadConfig() {
gStartTime = static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count());
try {
// remove/rename previous logs
remove("nampower_debug.log.3");
rename("nampower_debug.log.2", "nampower_debug.log.3");
rename("nampower_debug.log.1", "nampower_debug.log.2");
rename("nampower_debug.log", "nampower_debug.log.1");
} catch (...) {
// ignore any exceptions during log rotation
}
// remove/rename previous logs
safeRemove("nampower_debug.log.3");
safeRename("nampower_debug.log.2", "nampower_debug.log.3");
safeRename("nampower_debug.log.1", "nampower_debug.log.2");
safeRename("nampower_debug.log", "nampower_debug.log.1");
// open new log file
@@ -660,9 +704,12 @@ namespace Nampower {
gUserSettings.optimizeBufferUsingPacketTimings = false;
gUserSettings.preventRightClickTargetChange = false;
gUserSettings.preventRightClickPvPAttack = true;
gUserSettings.doubleCastToEndChannelEarly = false;
gUserSettings.spamProtectionEnabled = true;
gUserSettings.minBufferTimeMs = 55; // time in ms to buffer cast to minimize server failure
gUserSettings.nonGcdBufferTimeMs = 100; // time in ms to buffer non-GCD spells to minimize server failure
gUserSettings.maxBufferIncreaseMs = 30;
@@ -885,6 +932,16 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_PreventRightClickPvPAttack[] = "NP_PreventRightClickPvPAttack";
CVarRegister(NP_PreventRightClickPvPAttack, // name
nullptr, // help
0, // unk1
gUserSettings.preventRightClickPvPAttack ? defaultTrue : defaultFalse, // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_DoubleCastToEndChannelEarly[] = "NP_DoubleCastToEndChannelEarly";
CVarRegister(NP_DoubleCastToEndChannelEarly, // name
nullptr, // help
@@ -895,6 +952,16 @@ namespace Nampower {
0, // unk2
0); // unk3
char NP_SpamProtectionEnabled[] = "NP_SpamProtectionEnabled";
CVarRegister(NP_SpamProtectionEnabled, // name
nullptr, // help
0, // unk1
gUserSettings.spamProtectionEnabled ? defaultTrue : defaultFalse, // default value address
nullptr, // callback
1, // category
0, // unk2
0); // unk3
char NP_ChannelLatencyReductionPercentage[] = "NP_ChannelLatencyReductionPercentage";
CVarRegister(NP_ChannelLatencyReductionPercentage, // name
nullptr, // help
@@ -932,8 +999,12 @@ namespace Nampower {
loadUserVar("NP_PreventRightClickTargetChange");
loadUserVar("NP_PreventRightClickPvPAttack");
loadUserVar("NP_DoubleCastToEndChannelEarly");
loadUserVar("NP_SpamProtectionEnabled");
loadUserVar("NP_MinBufferTimeMs");
loadUserVar("NP_NonGcdBufferTimeMs");
loadUserVar("NP_MaxBufferIncreaseMs");
@@ -975,7 +1046,8 @@ namespace Nampower {
// Template function to simplify hook initialization with specific storage
template<typename FuncT, typename HookT>
std::unique_ptr<hadesmem::PatchDetour<FuncT>> createHook(const hadesmem::Process& process, Offsets offset, HookT hookFunc) {
std::unique_ptr<hadesmem::PatchDetour<FuncT>>
createHook(const hadesmem::Process &process, Offsets offset, HookT hookFunc) {
auto const originalFunc = hadesmem::detail::AliasCast<FuncT>(offset);
auto detour = std::make_unique<hadesmem::PatchDetour<FuncT>>(process, originalFunc, hookFunc);
detour->Apply();
@@ -991,30 +1063,50 @@ namespace Nampower {
gCastDetour = createHook<CastSpellT>(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook);
gSendCastDetour = createHook<SendCastT>(process, Offsets::SendCast, &SendCastHook);
gCancelSpellDetour = createHook<CancelSpellT>(process, Offsets::CancelSpell, &CancelSpellHook);
gCastResultHandlerDetour = createHook<PacketHandlerT>(process, Offsets::CastResultHandler, &CastResultHandlerHook);
gSpellStartHandlerDetour = createHook<FastCallPacketHandlerT>(process, Offsets::SpellStartHandler, &SpellStartHandlerHook);
gPeriodicAuraLogHandlerDetour = createHook<FastCallPacketHandlerT>(process, Offsets::PeriodicAuraLogHandler, &PeriodicAuraLogHandlerHook);
gSpellNonMeleeDmgLogHandlerDetour = createHook<FastCallPacketHandlerT>(process, Offsets::SpellNonMeleeDmgLogHandler, &SpellNonMeleeDmgLogHandlerHook);
gSpellChannelStartHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellChannelStartHandler, &SpellChannelStartHandlerHook);
gSpellChannelUpdateHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellChannelUpdateHandler, &SpellChannelUpdateHandlerHook);
gSpellFailedDetour = createHook<Spell_C_SpellFailedT>(process, Offsets::Spell_C_SpellFailed, &Spell_C_SpellFailedHook);
gCastResultHandlerDetour = createHook<PacketHandlerT>(process, Offsets::CastResultHandler,
&CastResultHandlerHook);
gSpellStartHandlerDetour = createHook<FastCallPacketHandlerT>(process, Offsets::SpellStartHandler,
&SpellStartHandlerHook);
gPeriodicAuraLogHandlerDetour = createHook<FastCallPacketHandlerT>(process, Offsets::PeriodicAuraLogHandler,
&PeriodicAuraLogHandlerHook);
gSpellNonMeleeDmgLogHandlerDetour = createHook<FastCallPacketHandlerT>(process,
Offsets::SpellNonMeleeDmgLogHandler,
&SpellNonMeleeDmgLogHandlerHook);
gSpellChannelStartHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellChannelStartHandler,
&SpellChannelStartHandlerHook);
gSpellChannelUpdateHandlerDetour = createHook<PacketHandlerT>(process, Offsets::SpellChannelUpdateHandler,
&SpellChannelUpdateHandlerHook);
gSpellFailedDetour = createHook<Spell_C_SpellFailedT>(process, Offsets::Spell_C_SpellFailed,
&Spell_C_SpellFailedHook);
gSpellGoDetour = createHook<SpellGoT>(process, Offsets::SpellGo, &SpellGoHook);
gSpellDelayedDetour = createHook<PacketHandlerT>(process, Offsets::SpellDelayed, &SpellDelayedHook);
gSpellTargetUnitDetour = createHook<LuaScriptT>(process, Offsets::Script_SpellTargetUnit, &Script_SpellTargetUnitHook);
gSpellStopCastingDetour = createHook<LuaScriptT>(process, Offsets::Script_SpellStopCasting, &Script_SpellStopCastingHook);
gSpell_C_TargetSpellDetour = createHook<Spell_C_TargetSpellT>(process, Offsets::Spell_C_TargetSpell, &Spell_C_TargetSpellHook);
gCastSpellByNameNoQueueDetour = createHook<LuaScriptT>(process, Offsets::Script_CastSpellByNameNoQueue, Script_CastSpellByNameNoQueue);
gQueueSpellByNameDetour = createHook<LuaScriptT>(process, Offsets::Script_QueueSpellByName, Script_QueueSpellByName);
gSpellTargetUnitDetour = createHook<LuaScriptT>(process, Offsets::Script_SpellTargetUnit,
&Script_SpellTargetUnitHook);
gSpellStopCastingDetour = createHook<LuaScriptT>(process, Offsets::Script_SpellStopCasting,
&Script_SpellStopCastingHook);
gSpell_C_TargetSpellDetour = createHook<Spell_C_TargetSpellT>(process, Offsets::Spell_C_TargetSpell,
&Spell_C_TargetSpellHook);
gCastSpellByNameNoQueueDetour = createHook<LuaScriptT>(process, Offsets::Script_CastSpellByNameNoQueue,
Script_CastSpellByNameNoQueue);
gQueueSpellByNameDetour = createHook<LuaScriptT>(process, Offsets::Script_QueueSpellByName,
Script_QueueSpellByName);
qQueueScriptDetour = createHook<LuaScriptT>(process, Offsets::Script_QueueScript, Script_QueueScript);
gIsSpellInRangeDetour = createHook<LuaScriptT>(process, Offsets::Script_IsSpellInRange, Script_IsSpellInRange);
gIsSpellUsableDetour = createHook<LuaScriptT>(process, Offsets::Script_IsSpellUsable, Script_IsSpellUsable);
gGetCurrentCastingInfoDetour = createHook<LuaScriptT>(process, Offsets::Script_GetCurrentCastingInfo, Script_GetCurrentCastingInfo);
gGetSpellIdForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellIdForName, Script_GetSpellIdForName);
gGetSpellNameAndRankForIdDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellNameAndRankForId, Script_GetSpellNameAndRankForId);
gGetSpellSlotAndTypeForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellSlotTypeIdForName, Script_GetSpellSlotTypeIdForName);
gOnSpriteRightClickDetour = createHook<OnSpriteRightClickT>(process, Offsets::OnSpriteRightClick, OnSpriteRightClickHook);
gChannelStopCastingNextTickDetour = createHook<LuaScriptT>(process, Offsets::Script_ChannelStopCastingNextTick, Script_ChannelStopCastingNextTick);
gGetNampowerVersionDetour = createHook<LuaScriptT>(process, Offsets::Script_GetNampowerVersion, Script_GetNampowerVersion);
gGetCurrentCastingInfoDetour = createHook<LuaScriptT>(process, Offsets::Script_GetCurrentCastingInfo,
Script_GetCurrentCastingInfo);
gGetSpellIdForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellIdForName,
Script_GetSpellIdForName);
gGetSpellNameAndRankForIdDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellNameAndRankForId,
Script_GetSpellNameAndRankForId);
gGetSpellSlotAndTypeForNameDetour = createHook<LuaScriptT>(process, Offsets::Script_GetSpellSlotTypeIdForName,
Script_GetSpellSlotTypeIdForName);
gOnSpriteRightClickDetour = createHook<OnSpriteRightClickT>(process, Offsets::OnSpriteRightClick,
OnSpriteRightClickHook);
gChannelStopCastingNextTickDetour = createHook<LuaScriptT>(process, Offsets::Script_ChannelStopCastingNextTick,
Script_ChannelStopCastingNextTick);
gGetNampowerVersionDetour = createHook<LuaScriptT>(process, Offsets::Script_GetNampowerVersion,
Script_GetNampowerVersion);
gGetItemLevelDetour = createHook<LuaScriptT>(process, Offsets::Script_GetItemLevel, Script_GetItemLevel);
gIEndSceneDetour = createHook<ISceneEndT>(process, Offsets::ISceneEndPtr, &ISceneEndHook);
}
@@ -1119,6 +1211,16 @@ namespace Nampower {
}
extern "C" __declspec(dllexport) uint32_t Load() {
Nampower::load();
try {
Nampower::load();
} catch (const std::exception &e) {
std::string errorMsg = "Exception in Nampower::load(): ";
errorMsg += e.what();
MessageBoxA(nullptr, errorMsg.c_str(), "Nampower Error", MB_OK | MB_ICONERROR);
return EXIT_FAILURE;
} catch (...) {
MessageBoxA(nullptr, "Unknown exception in Nampower::load()", "Nampower Error", MB_OK | MB_ICONERROR);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
+5 -4
View File
@@ -30,8 +30,8 @@ namespace Nampower {
constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 10;
constexpr uint32_t PATCH_VERSION = 16;
constexpr uint32_t MINOR_VERSION = 11;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
constexpr int32_t LUA_GLOBALSINDEX = -10001;
@@ -110,7 +110,7 @@ namespace Nampower {
using LuaCallT = void (__fastcall *)(const char *code, const char *unused);
using LuaScriptT = uint32_t (__fastcall *)(uintptr_t *luaState);
using GetGUIDFromNameT = std::uint64_t (__fastcall *)(const char *);
using GetUnitFromNameT = uintptr_t (__fastcall *)(const char *);
using GetUnitFromNameT = uintptr_t *(__fastcall *)(const char *);
using lua_gettableT = void (__fastcall *)(uintptr_t *luaState, int globalsIndex);
using lua_isstringT = bool (__fastcall *)(uintptr_t *, int);
using lua_isnumberT = bool (__fastcall *)(uintptr_t *, int);
@@ -131,7 +131,8 @@ namespace Nampower {
using SpellVisualsInitializeT = void (__stdcall *)(void);
using PlaySpellVisual = void (__stdcall *)(int **param_1, void *param_2, int param_3, void **param_4);
using CGUnit_C_ClearCastingSpellT = void (__thiscall *)(uintptr_t *unit, uint32_t param_1, int param_2, int param_3);
using CGUnit_C_ClearCastingSpellT = void (__thiscall *)(uintptr_t *unit, uint32_t param_1, int param_2,
int param_3);
using CGUnit_C_ClearSpellEffectT = void (__thiscall *)(uintptr_t *unit, uint32_t param_1, int param_2);
using GetBuffByIndexT = uintptr_t *(__fastcall *)(int index);
+13 -10
View File
@@ -521,20 +521,23 @@ namespace Nampower {
// if cast in the last second and still waiting for server result or succeeded
// otherwise can break cooldown in the client and cause unnecessary errors
if (castTime == 0 || spell->Attributes & game::SPELL_ATTR_DISABLED_WHILE_ACTIVE) {
auto castParams = gCastHistory.findNewestWaitingForServerSpellId(spellId);
if (castParams &&
currentTime - castParams->castStartTimeMs < 500) {
DEBUG_LOG("Ignoring " << spellName
<< " cast still waiting for server result for the same spell");
return false;
} else {
castParams = gCastHistory.findNewestSuccessfulSpellId(spellId);
// check if spam protection is enabled
if(gUserSettings.spamProtectionEnabled) {
auto castParams = gCastHistory.findNewestWaitingForServerSpellId(spellId);
if (castParams &&
castParams->guid == guid &&
currentTime - castParams->castStartTimeMs < 500) {
DEBUG_LOG("Ignoring " << spellName
<< " cast recently succeeded for the same spell and target");
<< " cast still waiting for server result for the same spell");
return false;
} else {
castParams = gCastHistory.findNewestSuccessfulSpellId(spellId);
if (castParams &&
castParams->guid == guid &&
currentTime - castParams->castStartTimeMs < 500) {
DEBUG_LOG("Ignoring " << spellName
<< " cast recently succeeded for the same spell and target");
return false;
}
}
}
}
+3
View File
@@ -22,9 +22,12 @@ struct UserSettings {
bool optimizeBufferUsingPacketTimings;
bool preventRightClickTargetChange;
bool preventRightClickPvPAttack;
bool doubleCastToEndChannelEarly;
bool spamProtectionEnabled;
uint32_t spellQueueWindowMs;
uint32_t onSwingBufferCooldownMs;
uint32_t channelQueueWindowMs;