mirror of
https://github.com/brues-code/nampower.git
synced 2026-10-09 23:20:01 +00:00
add spell start/go/failed/delayed/channel events
This commit is contained in:
@@ -7,6 +7,11 @@ For custom Lua functions, see [SCRIPTS.md](SCRIPTS.md). For general usage inform
|
||||
## Table of Contents
|
||||
- [SPELL_QUEUE_EVENT](#spell_queue_event)
|
||||
- [SPELL_CAST_EVENT](#spell_cast_event)
|
||||
- [SPELL_START_SELF and SPELL_START_OTHER](#spell_start_self-and-spell_start_other)
|
||||
- [SPELL_GO_SELF and SPELL_GO_OTHER](#spell_go_self-and-spell_go_other)
|
||||
- [SPELL_FAILED_SELF and SPELL_FAILED_OTHER](#spell_failed_self-and-spell_failed_other)
|
||||
- [SPELL_DELAYED_SELF and SPELL_DELAYED_OTHER](#spell_delayed_self-and-spell_delayed_other)
|
||||
- [SPELL_CHANNEL_START and SPELL_CHANNEL_UPDATE](#spell_channel_start-and-spell_channel_update)
|
||||
- [SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER](#spell_damage_event_self-and-spell_damage_event_other)
|
||||
- [Buff/Debuff Events](#buffdebuff-events)
|
||||
- [AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER](#aura_cast_on_self-and-aura_cast_on_other)
|
||||
@@ -57,7 +62,7 @@ NampowerSettings:RegisterEvent("SPELL_QUEUE_EVENT", spellQueueEvent)
|
||||
```
|
||||
|
||||
### SPELL_CAST_EVENT
|
||||
Event you can register in game to get updates when you cast spells with some additional information. This will only fire for spells you (and certain pets) initiated.
|
||||
Event you can register in game to get updates when you cast spells with some additional information. This will only fire for spells you (and certain pets) initiated. Triggered when you start casting a spell in the client before it is sent to the server.
|
||||
|
||||
The event is `SPELL_CAST_EVENT` and has 5 parameters:
|
||||
1. int success - 1 if cast succeeded, 0 if failed
|
||||
@@ -91,6 +96,81 @@ Cursive:RegisterEvent("SPELL_CAST_EVENT", function(success, spellId, castType, t
|
||||
end);
|
||||
```
|
||||
|
||||
### SPELL_START_SELF and SPELL_START_OTHER
|
||||
Fire when a spell start packet is received. "Self" fires when the active player is the caster, "Other" fires for any other caster. Triggered by the server to notify that a spell with a cast time has begun.
|
||||
|
||||
These events are gated behind the `NP_EnableSpellStartEvents` CVar (default 0). Set it to `1` to enable.
|
||||
|
||||
Parameters:
|
||||
1. int itemId - the id of the item that triggered the spell, 0 if it wasn't triggered by an item
|
||||
2. int spellId
|
||||
3. string casterGuid - caster guid like "0xF5300000000000A5"
|
||||
4. string targetGuid - target guid like "0xF5300000000000A5" or "0x0000000000000000" if none
|
||||
5. int castFlags
|
||||
6. int castTime - cast time in milliseconds
|
||||
|
||||
### SPELL_GO_SELF and SPELL_GO_OTHER
|
||||
Fire when a spell go packet is received. "Self" fires when the active player is the caster, "Other" fires for any other caster. Triggered by the server to indicate a spell completed casting.
|
||||
|
||||
These events are gated behind the `NP_EnableSpellGoEvents` CVar (default 0). Set it to `1` to enable.
|
||||
|
||||
Parameters:
|
||||
1. int itemId - the id of the item that triggered the spell, 0 if it wasn't triggered by an item
|
||||
2. int spellId
|
||||
3. string casterGuid - caster guid like "0xF5300000000000A5"
|
||||
4. string targetGuid - target guid like "0xF5300000000000A5" or "0x0000000000000000" if none
|
||||
5. int castFlags
|
||||
6. int numTargetsHit
|
||||
7. int numTargetsMissed
|
||||
|
||||
#### CastFlags (bitmask)
|
||||
```
|
||||
CAST_FLAG_NONE = 0 (0x00000000)
|
||||
CAST_FLAG_HIDDEN_COMBATLOG = 1 (0x00000001)
|
||||
CAST_FLAG_UNKNOWN2 = 2 (0x00000002)
|
||||
CAST_FLAG_UNKNOWN3 = 4 (0x00000004)
|
||||
CAST_FLAG_UNKNOWN4 = 8 (0x00000008)
|
||||
CAST_FLAG_UNKNOWN5 = 16 (0x00000010)
|
||||
CAST_FLAG_AMMO = 32 (0x00000020)
|
||||
CAST_FLAG_UNKNOWN7 = 64 (0x00000040)
|
||||
CAST_FLAG_UNKNOWN8 = 128 (0x00000080)
|
||||
CAST_FLAG_UNKNOWN9 = 256 (0x00000100)
|
||||
```
|
||||
|
||||
### SPELL_FAILED_SELF and SPELL_FAILED_OTHER
|
||||
Fire when a spell failure is reported. "Self" is fired from the client spell failure hook, "Other" is fired from the server handler (only includes caster guid and spell id).
|
||||
|
||||
Parameters for `SPELL_FAILED_SELF`:
|
||||
1. int spellId
|
||||
2. int spellResult - SpellCastResult enum value
|
||||
3. int failedByServer - 1 if failed by server, 0 otherwise
|
||||
|
||||
Parameters for `SPELL_FAILED_OTHER`:
|
||||
1. string casterGuid - caster guid like "0xF5300000000000A5"
|
||||
2. int spellId
|
||||
|
||||
### SPELL_DELAYED_SELF and SPELL_DELAYED_OTHER
|
||||
Fire when a spell is delayed, generally due to taking damage. "Self" fires when the active player is affected, "Other" for other players.
|
||||
|
||||
Parameters:
|
||||
1. string casterGuid - caster guid like "0xF5300000000000A5"
|
||||
2. int delayMs - delay in milliseconds
|
||||
|
||||
### SPELL_CHANNEL_START and SPELL_CHANNEL_UPDATE
|
||||
Fire when the active player starts or updates a channeled spell. These are self-only events.
|
||||
|
||||
Channel target guid is read from `ChannelTargetGuid = 0xC4D980` and included in these events.
|
||||
|
||||
Parameters for `SPELL_CHANNEL_START`:
|
||||
1. int spellId
|
||||
2. string targetGuid - target guid like "0xF5300000000000A5" or "0x0000000000000000" if none
|
||||
3. int durationMs - channel duration in milliseconds
|
||||
|
||||
Parameters for `SPELL_CHANNEL_UPDATE`:
|
||||
1. int spellId
|
||||
2. string targetGuid - target guid like "0xF5300000000000A5" or "0x0000000000000000" if none
|
||||
3. int remainingMs - remaining channel time in milliseconds
|
||||
|
||||
### SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER
|
||||
New events you can register in game to get updates whenever spell damage occurs. SPELL_DAMAGE_EVENT_SELF will only trigger for damage you deal, while SPELL_DAMAGE_EVENT_OTHER will only trigger for damage dealt by others.
|
||||
|
||||
|
||||
@@ -99,6 +99,10 @@ SET NP_TargetingQueueWindowMs "1000"
|
||||
|
||||
- `NP_EnableAutoAttackEvents` - Whether to enable AUTO_ATTACK_SELF and AUTO_ATTACK_OTHER events. 0 to disable, 1 to enable. Default is 0.
|
||||
|
||||
- `NP_EnableSpellStartEvents` - Whether to enable SPELL_START_SELF and SPELL_START_OTHER events. 0 to disable, 1 to enable. Default is 0.
|
||||
|
||||
- `NP_EnableSpellGoEvents` - Whether to enable SPELL_GO_SELF and SPELL_GO_OTHER events. 0 to disable, 1 to enable. Default is 0.
|
||||
|
||||
- `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.
|
||||
@@ -145,7 +149,9 @@ For complete documentation of all custom events added by Nampower, see **[EVENTS
|
||||
|
||||
Available events:
|
||||
- SPELL_QUEUE_EVENT - Fires when spells are queued or dequeued
|
||||
- SPELL_CAST_EVENT - Fires when you cast a spell with additional information
|
||||
- SPELL_CAST_EVENT - Fires when you cast a spell with additional information. Triggered when you start casting a spell in the client before it is sent to the server.
|
||||
- SPELL_START_SELF and SPELL_START_OTHER - Triggered by the server to notify that a spell with a cast time has begun.
|
||||
- SPELL_GO_SELF and SPELL_GO_OTHER - Triggered by the server to indicate a spell completed casting.
|
||||
- SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER - Combat damage events
|
||||
- Buff/Debuff Events (BUFF_ADDED_SELF, BUFF_REMOVED_SELF, etc.)
|
||||
- AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER - Aura application events (fires once per aura effect per target; "Self" fires when the aura lands on the active player, including self-cast with no explicit target; includes aura metadata + amplitude/misc + aura cap bitfield for buff/debuff slots). Handles single-target and multi-target (AOE) spells. Some auras don't have spell effects and won't trigger this; use BUFF/DEBUFF gains for those. Set `NP_EnableAuraCastEvents=1` to enable. See EVENTS.md for details on multi-target behavior and known limitations.
|
||||
|
||||
+11116
-11089
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -71,7 +71,7 @@ namespace Nampower {
|
||||
}
|
||||
}
|
||||
|
||||
char format[] = "%s%d%d%d%d";
|
||||
static char format[] = "%s%d%d%d%d";
|
||||
char *guidStr = new char[21]; // 2 for 0x prefix, 18 for the number, and 1 for '\0'
|
||||
if (isSelf) {
|
||||
std::snprintf(guidStr, 21, "0x%016llX", static_cast<unsigned long long>(playerGuid));
|
||||
@@ -134,7 +134,7 @@ namespace Nampower {
|
||||
unitCombatLogUnitDead(guid);
|
||||
|
||||
// Trigger UNIT_DIED event
|
||||
char format[] = "%s";
|
||||
static char format[] = "%s";
|
||||
char *guidStr = new char[21]; // 2 for 0x prefix, 18 for the number, and 1 for '\0'
|
||||
std::snprintf(guidStr, 21, "0x%016llX", static_cast<unsigned long long>(guid));
|
||||
|
||||
|
||||
@@ -1067,6 +1067,61 @@ 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 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 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,
|
||||
};
|
||||
|
||||
// SpellEntry::Targets
|
||||
enum SpellCastTargetFlags {
|
||||
TARGET_FLAG_SELF = 0x00000000,
|
||||
@@ -1478,6 +1533,21 @@ namespace game {
|
||||
|
||||
AUTO_ATTACK_SELF = 562,
|
||||
AUTO_ATTACK_OTHER = 563,
|
||||
|
||||
SPELL_START_SELF = 564,
|
||||
SPELL_START_OTHER = 565,
|
||||
|
||||
SPELL_GO_SELF = 566,
|
||||
SPELL_GO_OTHER = 567,
|
||||
|
||||
SPELL_FAILED_SELF = 568,
|
||||
SPELL_FAILED_OTHER = 569,
|
||||
|
||||
SPELL_DELAYED_SELF = 570,
|
||||
SPELL_DELAYED_OTHER = 571,
|
||||
|
||||
SPELL_CHANNEL_START = 572,
|
||||
SPELL_CHANNEL_UPDATE = 573,
|
||||
};
|
||||
|
||||
enum TypeMask {
|
||||
|
||||
@@ -677,6 +677,12 @@ namespace Nampower {
|
||||
} else if (strcmp(cvar, "NP_EnableAutoAttackEvents") == 0) {
|
||||
gUserSettings.enableAutoAttackEvents = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_EnableAutoAttackEvents to " << gUserSettings.enableAutoAttackEvents);
|
||||
} else if (strcmp(cvar, "NP_EnableSpellStartEvents") == 0) {
|
||||
gUserSettings.enableSpellStartEvents = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_EnableSpellStartEvents to " << gUserSettings.enableSpellStartEvents);
|
||||
} else if (strcmp(cvar, "NP_EnableSpellGoEvents") == 0) {
|
||||
gUserSettings.enableSpellGoEvents = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_EnableSpellGoEvents to " << gUserSettings.enableSpellGoEvents);
|
||||
} else if (strcmp(cvar, "NP_PreventMountingWhenBuffCapped") == 0) {
|
||||
gUserSettings.preventMountingWhenBuffCapped = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_PreventMountingWhenBuffCapped to " << gUserSettings.preventMountingWhenBuffCapped);
|
||||
@@ -1001,6 +1007,26 @@ namespace Nampower {
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_EnableSpellStartEvents[] = "NP_EnableSpellStartEvents";
|
||||
CVarRegister(NP_EnableSpellStartEvents, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
gUserSettings.enableSpellStartEvents ? defaultTrue : defaultFalse, // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_EnableSpellGoEvents[] = "NP_EnableSpellGoEvents";
|
||||
CVarRegister(NP_EnableSpellGoEvents, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
gUserSettings.enableSpellGoEvents ? defaultTrue : defaultFalse, // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_PreventMountingWhenBuffCapped[] = "NP_PreventMountingWhenBuffCapped";
|
||||
CVarRegister(NP_PreventMountingWhenBuffCapped, // name
|
||||
nullptr, // help
|
||||
@@ -1138,6 +1164,8 @@ namespace Nampower {
|
||||
loadUserVar("NP_SpamProtectionEnabled");
|
||||
loadUserVar("NP_EnableAuraCastEvents");
|
||||
loadUserVar("NP_EnableAutoAttackEvents");
|
||||
loadUserVar("NP_EnableSpellStartEvents");
|
||||
loadUserVar("NP_EnableSpellGoEvents");
|
||||
loadUserVar("NP_PreventMountingWhenBuffCapped");
|
||||
|
||||
loadUserVar("NP_MinBufferTimeMs");
|
||||
@@ -1388,6 +1416,36 @@ namespace Nampower {
|
||||
|
||||
char AUTO_ATTACK_OTHER[] = "AUTO_ATTACK_OTHER";
|
||||
addCustomEvent(game::AUTO_ATTACK_OTHER, AUTO_ATTACK_OTHER);
|
||||
|
||||
char SPELL_START_SELF[] = "SPELL_START_SELF";
|
||||
addCustomEvent(game::SPELL_START_SELF, SPELL_START_SELF);
|
||||
|
||||
char SPELL_START_OTHER[] = "SPELL_START_OTHER";
|
||||
addCustomEvent(game::SPELL_START_OTHER, SPELL_START_OTHER);
|
||||
|
||||
char SPELL_GO_SELF[] = "SPELL_GO_SELF";
|
||||
addCustomEvent(game::SPELL_GO_SELF, SPELL_GO_SELF);
|
||||
|
||||
char SPELL_GO_OTHER[] = "SPELL_GO_OTHER";
|
||||
addCustomEvent(game::SPELL_GO_OTHER, SPELL_GO_OTHER);
|
||||
|
||||
char SPELL_FAILED_SELF[] = "SPELL_FAILED_SELF";
|
||||
addCustomEvent(game::SPELL_FAILED_SELF, SPELL_FAILED_SELF);
|
||||
|
||||
char SPELL_FAILED_OTHER[] = "SPELL_FAILED_OTHER";
|
||||
addCustomEvent(game::SPELL_FAILED_OTHER, SPELL_FAILED_OTHER);
|
||||
|
||||
char SPELL_DELAYED_SELF[] = "SPELL_DELAYED_SELF";
|
||||
addCustomEvent(game::SPELL_DELAYED_SELF, SPELL_DELAYED_SELF);
|
||||
|
||||
char SPELL_DELAYED_OTHER[] = "SPELL_DELAYED_OTHER";
|
||||
addCustomEvent(game::SPELL_DELAYED_OTHER, SPELL_DELAYED_OTHER);
|
||||
|
||||
char SPELL_CHANNEL_START[] = "SPELL_CHANNEL_START";
|
||||
addCustomEvent(game::SPELL_CHANNEL_START, SPELL_CHANNEL_START);
|
||||
|
||||
char SPELL_CHANNEL_UPDATE[] = "SPELL_CHANNEL_UPDATE";
|
||||
addCustomEvent(game::SPELL_CHANNEL_UPDATE, SPELL_CHANNEL_UPDATE);
|
||||
}
|
||||
|
||||
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ namespace Nampower {
|
||||
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
|
||||
|
||||
constexpr uint32_t MAJOR_VERSION = 2;
|
||||
constexpr uint32_t MINOR_VERSION = 24;
|
||||
constexpr uint32_t MINOR_VERSION = 25;
|
||||
constexpr uint32_t PATCH_VERSION = 0;
|
||||
|
||||
constexpr int32_t LUA_REGISTRYINDEX = -10000;
|
||||
@@ -92,7 +92,7 @@ namespace Nampower {
|
||||
using EndSceneT = int (__fastcall *)(uintptr_t *unk);
|
||||
using OnSpriteRightClickT = int (__fastcall *)(uint64_t objectGUID);
|
||||
using CGGameUI_TargetT = void (__stdcall *)(uint64_t objectGUID);
|
||||
using Spell_C_SpellFailedT = void (__fastcall *)(uint32_t, game::SpellCastResult, int, int, char unk3);
|
||||
using Spell_C_SpellFailedT = void (__fastcall *)(uint32_t, game::SpellCastResult, int, int, bool);
|
||||
using Spell_C_GetAutoRepeatingSpellT = int (__cdecl *)();
|
||||
using SpellGoT = void (__fastcall *)(uint64_t *, uint64_t *, uint32_t, CDataStore *);
|
||||
using Spell_C_HandleSpriteClickT = bool (__fastcall *)(game::CSpriteClickEvent *event);
|
||||
|
||||
@@ -44,7 +44,7 @@ enum class Offsets : std::uint32_t {
|
||||
SpellDb = 0xC0D780,
|
||||
CursorMode = 0xBE2C4C,
|
||||
CursorType = 0X00BE2C2C,
|
||||
SpellIsTargeting = 0xCECAC0,
|
||||
SpellNeedTargets = 0xCECAC0,
|
||||
CastingItemIdPtr = 0X00CECAB0,
|
||||
CastingSpellId = 0xCECA88,
|
||||
AutoRepeatingSpellId = 0xCEAC30,
|
||||
|
||||
+13
-254
@@ -9,8 +9,6 @@
|
||||
|
||||
namespace Nampower {
|
||||
auto const APPLY_BUFFER_TO_GCD = false; // gcd issue seems fixed for now
|
||||
constexpr int MAX_ALLOWED_SPELL_TARGETS = 100;
|
||||
|
||||
void SetReleaseAction(uint32_t input) {
|
||||
uint32_t activeControl = *reinterpret_cast<uint32_t *>(Offsets::CGInputControlGetActive);
|
||||
|
||||
@@ -274,7 +272,7 @@ namespace Nampower {
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = gLastNormalCastParams.numRetries;
|
||||
Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.casterUnit, gLastNormalCastParams.spellId,
|
||||
gLastNormalCastParams.item, gLastNormalCastParams.guid);
|
||||
gLastNormalCastParams.item, gLastNormalCastParams.guid);
|
||||
gLastCastData.wasQueued = true;
|
||||
} else {
|
||||
DEBUG_LOG("Ignoring queued cast, no spell id");
|
||||
@@ -283,9 +281,9 @@ namespace Nampower {
|
||||
TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId);
|
||||
gCastData.normalSpellQueued = false;
|
||||
gCastData.castingQueuedSpell = false;
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = 0;
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +327,7 @@ namespace Nampower {
|
||||
|
||||
void
|
||||
TriggerSpellCastEvent(bool result, uint32_t spellId, CastType castType, std::uint64_t guid, uint32_t itemId) {
|
||||
char format[] = "%d%d%d%s%d";
|
||||
static char format[] = "%d%d%d%s%d";
|
||||
char *guidStr = new char[21]; // 2 for 0x prefix, 18 for the number, and 1 for '\0'
|
||||
std::snprintf(guidStr, 21, "0x%016llX", static_cast<unsigned long long>(guid));
|
||||
|
||||
@@ -848,8 +846,11 @@ namespace Nampower {
|
||||
// simulate a cancel to clear the cast bar but only when there should be a cast time
|
||||
// mining/herbing have cast time but aren't on Gcd, don't cancel them
|
||||
if (!ret && gLastCastData.castTimeMs > 0 && gLastCastData.wasOnGcd && !gCastData.ignoreCancelDueToCooldown) {
|
||||
if (*reinterpret_cast<int *>(Offsets::SpellIsTargeting) == 0 && !gCastData.pendingOnSwingCast &&
|
||||
!IsSpellOnCooldown(spellId)) {
|
||||
auto inTargetingMode = *reinterpret_cast<int *>(Offsets::SpellNeedTargets);
|
||||
auto spellIsOnCooldown = IsSpellOnCooldown(spellId);
|
||||
if (inTargetingMode == 0 &&
|
||||
!gCastData.pendingOnSwingCast &&
|
||||
!spellIsOnCooldown) {
|
||||
DEBUG_LOG("Canceling spell cast due to previous spell having cast time of "
|
||||
<< gLastCastData.castTimeMs);
|
||||
|
||||
@@ -872,7 +873,9 @@ namespace Nampower {
|
||||
DEBUG_LOG("Retry cast after cancel still failed");
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG("Initial cast failed but not canceling spell cast");
|
||||
DEBUG_LOG("Initial cast failed but not canceling spell cast. inTargetingMode ="
|
||||
<< inTargetingMode << " pendingOnSwingCast =" << gCastData.pendingOnSwingCast
|
||||
<< " spellIsOnCooldown =" << spellIsOnCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,250 +884,6 @@ namespace Nampower {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool doesSpellApplyAura(const game::SpellRec *spell) {
|
||||
if (!spell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i: spell->Effect) {
|
||||
switch (i) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void triggerAuraCastEventOnTarget(const game::SpellRec *spell,
|
||||
int spellEffectIndex,
|
||||
char *casterGuidStr,
|
||||
char *targetGuidStr, // if provided use this instead of creating one
|
||||
uint64_t targetGuid,
|
||||
uint64_t activePlayerGuid,
|
||||
uint32_t effectType,
|
||||
uint32_t duration) {
|
||||
static char format[] = "%d%s%s%d%d%d%d%d%d";
|
||||
|
||||
auto eventToTrigger = game::AURA_CAST_ON_OTHER;
|
||||
if (targetGuid == activePlayerGuid) {
|
||||
eventToTrigger = game::AURA_CAST_ON_SELF;
|
||||
}
|
||||
|
||||
auto auraName = spell->EffectApplyAuraName[spellEffectIndex];
|
||||
auto effectAmplitude = spell->EffectAmplitude[spellEffectIndex];
|
||||
auto effectMiscValue = spell->EffectMiscValue[spellEffectIndex];
|
||||
|
||||
auto *targetUnit = game::ClntObjMgrObjectPtr(
|
||||
static_cast<game::TypeMask>(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), targetGuid);
|
||||
|
||||
bool targetIsBuffCapped = game::UnitIsBuffCapped(targetUnit);
|
||||
bool targetIsDebuffCapped = game::UnitIsDebuffCapped(targetUnit);
|
||||
|
||||
bool createdGuidStr = false;
|
||||
if (!targetGuidStr) {
|
||||
if (targetGuid > 0) {
|
||||
targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
createdGuidStr = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto auraCapStatus = static_cast<uint32_t>(targetIsBuffCapped) |
|
||||
(static_cast<uint32_t>(targetIsDebuffCapped) << 1);
|
||||
|
||||
reinterpret_cast<int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
char *casterGuidStrParam,
|
||||
char *targetGuidStrParam,
|
||||
uint32_t effectParam,
|
||||
uint32_t auraNameParam,
|
||||
uint32_t effectAmplitudeParam,
|
||||
uint32_t effectMiscValueParam,
|
||||
uint32_t durationParam,
|
||||
uint32_t auraCapStatusParam)>(Offsets::SignalEventParam)(
|
||||
eventToTrigger,
|
||||
format,
|
||||
spell->Id,
|
||||
casterGuidStr,
|
||||
targetGuidStr,
|
||||
effectType,
|
||||
auraName,
|
||||
effectAmplitude,
|
||||
effectMiscValue,
|
||||
duration,
|
||||
auraCapStatus);
|
||||
|
||||
if (createdGuidStr) {
|
||||
delete [] targetGuidStr;
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerAuraCastEvent(const game::SpellRec *spell,
|
||||
uint64_t casterGuid,
|
||||
uint64_t targetGuids[MAX_ALLOWED_SPELL_TARGETS],
|
||||
uint8_t numTargets,
|
||||
uint64_t activePlayerGuid,
|
||||
bool castByActivePlayer) {
|
||||
// ignore modifiers if not cast by active player
|
||||
auto duration = game::GetSpellDuration(spell, !castByActivePlayer);
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto const effectType = spell->Effect[i];
|
||||
switch (effectType) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET: {
|
||||
auto const implicitTargetA = spell->EffectImplicitTargetA[i];
|
||||
auto const implicitTargetB = spell->EffectImplicitTargetB[i];
|
||||
|
||||
if (implicitTargetA == game::TARGET_UNIT_CASTER) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, casterGuidStr, casterGuid,
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
break;
|
||||
}
|
||||
|
||||
// handle no targets
|
||||
if (numTargets == 0 || implicitTargetA == game::TARGET_NONE) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, 0, activePlayerGuid,
|
||||
effectType, duration);
|
||||
} else if ((implicitTargetA >= game::TARGET_UNIT_ENEMY_NEAR_CASTER && implicitTargetA <=
|
||||
game::TARGET_UNIT_ENEMY) ||
|
||||
(implicitTargetA >= game::TARGET_PLAYER_NYI && implicitTargetA <=
|
||||
game::TARGET_PLAYER_FRIEND_NYI) ||
|
||||
implicitTargetA == game::TARGET_UNIT_FRIEND ||
|
||||
implicitTargetA == game::TARGET_UNIT ||
|
||||
implicitTargetA == game::TARGET_UNIT_CASTER_MASTER) {
|
||||
// trigger on first target
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[0], activePlayerGuid,
|
||||
effectType, duration);
|
||||
} else if (implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_24 ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_WITHIN_CASTER_RANGE ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_54 ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_24 ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_WITHIN_CASTER_RANGE ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_54) {
|
||||
// trigger on all targets except caster
|
||||
for (int t = 0; t < numTargets; ++t) {
|
||||
if (targetGuids[t] != casterGuid) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[t],
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// trigger on all targets (probably some exceptions but good enough for now)
|
||||
for (int t = 0; t < numTargets; ++t) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[t],
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] casterGuidStr;
|
||||
}
|
||||
|
||||
void
|
||||
SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *itemGUID, uint64_t *casterGUID,
|
||||
uint32_t spellId,
|
||||
CDataStore *spellData) {
|
||||
auto const rpos = spellData->m_read;
|
||||
|
||||
int16_t castFlags;
|
||||
spellData->Get(castFlags);
|
||||
|
||||
uint8_t numTargets;
|
||||
spellData->Get(numTargets);
|
||||
|
||||
static uint64_t targetGuids[MAX_ALLOWED_SPELL_TARGETS]; // don't allocate new array each time
|
||||
// each target corresponds to a spell effect, should never have more than 3
|
||||
for (int i = 0; i < numTargets && i < MAX_ALLOWED_SPELL_TARGETS; ++i) {
|
||||
targetGuids[i] = 0;
|
||||
spellData->Get(targetGuids[i]);
|
||||
}
|
||||
|
||||
// reset read pointer
|
||||
spellData->m_read = rpos;
|
||||
|
||||
|
||||
auto const spellGo = detour->GetTrampolineT<SpellGoT>();
|
||||
// if this wasn't triggered by an item, itemGUID will just be the casterGUID duplicated
|
||||
spellGo(itemGUID, casterGUID, spellId, spellData);
|
||||
|
||||
auto const activePlayerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto const castByActivePlayer = activePlayerGuid == *casterGUID;
|
||||
auto const spell = game::GetSpellInfo(spellId);
|
||||
|
||||
if (!spell || spell->Id <= 0) {
|
||||
DEBUG_LOG("Unable to determine spell information in SpellGo for " << spellId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (castByActivePlayer) {
|
||||
auto const currentTime = GetTime();
|
||||
// only care about our own casts
|
||||
if (!gCastData.channeling) {
|
||||
// check if spell is on swing
|
||||
if (spell->Attributes & game::SPELL_ATTR_ON_NEXT_SWING_1) {
|
||||
gLastCastData.onSwingStartTimeMs = currentTime;
|
||||
|
||||
gCastData.pendingOnSwingCast = false;
|
||||
|
||||
if (gCastData.onSwingQueued) {
|
||||
DEBUG_LOG("On swing spell " << game::GetSpellName(spellId) <<
|
||||
" resolved, casting queued on swing spell "
|
||||
<< game::GetSpellName(gLastOnSwingCastParams.spellId));
|
||||
|
||||
|
||||
TriggerSpellQueuedEvent(ON_SWING_QUEUE_POPPED, gLastOnSwingCastParams.spellId);
|
||||
Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.casterUnit,
|
||||
gLastOnSwingCastParams.spellId,
|
||||
gLastOnSwingCastParams.item, gLastOnSwingCastParams.guid);
|
||||
gCastData.onSwingQueued = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) {
|
||||
// not enough available args for itemId currently
|
||||
// uint32_t itemId = 0;
|
||||
// if (itemGUID && itemGUID != casterGUID) {
|
||||
// auto *item = reinterpret_cast<game::CGItem_C *>(game::ClntObjMgrObjectPtr(
|
||||
// game::TYPEMASK_ITEM, *itemGUID));
|
||||
// if (item) {
|
||||
// itemId = game::GetItemId(item);
|
||||
// }
|
||||
// }
|
||||
TriggerAuraCastEvent(spell, *casterGUID, targetGuids, numTargets, activePlayerGuid, castByActivePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyServer,
|
||||
game::SpellCastResult reason) {
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Nampower {
|
||||
CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyServer, game::SpellCastResult reason);
|
||||
|
||||
void SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *itemGUID, uint64_t *casterGUID, uint32_t spellId,
|
||||
CDataStore *spellData);
|
||||
CDataStore *packet);
|
||||
|
||||
bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour,
|
||||
uint32_t *player,
|
||||
|
||||
@@ -6,8 +6,52 @@
|
||||
#include "spellcast.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "offsets.hpp"
|
||||
#include "helper.hpp"
|
||||
|
||||
namespace Nampower {
|
||||
void TriggerSpellChannelStartEvent(uint32_t spellId, uint32_t duration) {
|
||||
static char format[] = "%d%s%d";
|
||||
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = game::GetObjectPtr(playerGuid);
|
||||
auto unitTargetGuid = game::UnitGetTargetGuid(playerUnit); // channel target guid gets updated later
|
||||
|
||||
char *targetGuidStr = ConvertGuidToString(unitTargetGuid);
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
char *targetGuidParam,
|
||||
uint32_t durationParam)) Offsets::SignalEventParam)(
|
||||
game::SPELL_CHANNEL_START,
|
||||
format,
|
||||
spellId,
|
||||
targetGuidStr,
|
||||
duration);
|
||||
|
||||
delete[] targetGuidStr;
|
||||
}
|
||||
|
||||
void TriggerSpellChannelUpdateEvent(uint32_t spellId, uint32_t channelRemainingTime) {
|
||||
static char format[] = "%d%s%d";
|
||||
|
||||
auto channelTargetGuid = *reinterpret_cast<uint64_t *>(Offsets::ChannelTargetGuid);
|
||||
char *targetGuidStr = ConvertGuidToString(channelTargetGuid);
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
char *targetGuidParam,
|
||||
uint32_t remainingParam)) Offsets::SignalEventParam)(
|
||||
game::SPELL_CHANNEL_UPDATE,
|
||||
format,
|
||||
spellId,
|
||||
targetGuidStr,
|
||||
channelRemainingTime);
|
||||
|
||||
delete[] targetGuidStr;
|
||||
}
|
||||
|
||||
int SpellChannelStartHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) {
|
||||
auto const rpos = packet->m_read;
|
||||
|
||||
@@ -96,7 +140,11 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
auto const spellChannelStartHandler = detour->GetTrampolineT<PacketHandlerT>();
|
||||
return spellChannelStartHandler(opCode, packet);
|
||||
auto ret = spellChannelStartHandler(opCode, packet);
|
||||
|
||||
TriggerSpellChannelStartEvent(spellId, duration);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// this gets called on damage and when channel ends with the end time of the channel
|
||||
@@ -108,6 +156,8 @@ namespace Nampower {
|
||||
|
||||
packet->m_read = rpos;
|
||||
|
||||
TriggerSpellChannelUpdateEvent(gCastData.channelSpellId, channelRemainingTime);
|
||||
|
||||
if (channelRemainingTime <= 0) {
|
||||
DEBUG_LOG("Channel done: " << game::GetSpellName(gCastData.channelSpellId)
|
||||
<< " elapsed " << (GetTime() - gLastCastData.channelStartTimeMs)
|
||||
@@ -122,4 +172,4 @@ namespace Nampower {
|
||||
auto const spellChannelUpdateHandler = detour->GetTrampolineT<PacketHandlerT>();
|
||||
return spellChannelUpdateHandler(opCode, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+508
-75
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Nampower {
|
||||
uint32_t lastCastResultTimeMs;
|
||||
constexpr int MAX_ALLOWED_SPELL_TARGETS = 100;
|
||||
|
||||
void SignalEventHook(hadesmem::PatchDetourBase *detour, game::Events eventId) {
|
||||
auto const signalEvent = detour->GetTrampolineT<SignalEventT>();
|
||||
@@ -43,10 +44,46 @@ namespace Nampower {
|
||||
return spellTargetUnit(luaState);
|
||||
}
|
||||
|
||||
|
||||
void TriggerSpellFailedSelfEvent(uint32_t spellId,
|
||||
game::SpellCastResult spellResult,
|
||||
bool failedByServer) {
|
||||
static char format[] = "%d%d%d";
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
uint32_t spellResultParam,
|
||||
uint32_t failedByServerParam)) Offsets::SignalEventParam)(
|
||||
game::SPELL_FAILED_SELF,
|
||||
format,
|
||||
spellId,
|
||||
static_cast<uint32_t>(spellResult),
|
||||
failedByServer ? 1 : 0);
|
||||
}
|
||||
|
||||
void TriggerSpellFailedOtherEvent(uint64_t casterGuid,
|
||||
uint32_t spellId) {
|
||||
static char format[] = "%s%d";
|
||||
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
char *casterGuidParam,
|
||||
uint32_t spellIdParam)) Offsets::SignalEventParam)(
|
||||
game::SPELL_FAILED_OTHER,
|
||||
format,
|
||||
casterGuidStr,
|
||||
spellId);
|
||||
|
||||
delete[] casterGuidStr;
|
||||
}
|
||||
|
||||
void Spell_C_SpellFailedHook(hadesmem::PatchDetourBase *detour, uint32_t spellId,
|
||||
game::SpellCastResult spellResult, int unk1, int unk2, char unk3) {
|
||||
game::SpellCastResult spellResult, int unk1, int unk2, bool failedByServer) {
|
||||
auto const spellFailed = detour->GetTrampolineT<Spell_C_SpellFailedT>();
|
||||
spellFailed(spellId, spellResult, unk1, unk2, unk3);
|
||||
spellFailed(spellId, spellResult, unk1, unk2, failedByServer);
|
||||
|
||||
// ignore fake failure (used by Unleashed Potential and not sure what else)
|
||||
if (spellResult == game::SpellCastResult::SPELL_FAILED_DONT_REPORT) {
|
||||
@@ -65,10 +102,12 @@ namespace Nampower {
|
||||
spellId == 51934 ||
|
||||
spellId == 51935 ||
|
||||
spellId == 51936)
|
||||
) {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
TriggerSpellFailedSelfEvent(spellId, spellResult, failedByServer);
|
||||
|
||||
ResetCastFlags();
|
||||
|
||||
if (spellId == gCastData.onSwingSpellId) {
|
||||
@@ -78,7 +117,7 @@ namespace Nampower {
|
||||
if ((spellResult == game::SpellCastResult::SPELL_FAILED_NOT_READY ||
|
||||
spellResult == game::SpellCastResult::SPELL_FAILED_ITEM_NOT_READY ||
|
||||
spellResult == game::SpellCastResult::SPELL_FAILED_SPELL_IN_PROGRESS)
|
||||
) {
|
||||
) {
|
||||
auto const currentTime = GetTime();
|
||||
|
||||
if (spellResult == game::SpellCastResult::SPELL_FAILED_NOT_READY) {
|
||||
@@ -99,8 +138,8 @@ namespace Nampower {
|
||||
if (castParams->castType == CastType::NON_GCD ||
|
||||
castParams->castType == CastType::TARGETING_NON_GCD) {
|
||||
DEBUG_LOG("Non gcd spell " << game::GetSpellName(spellId) << " is still on cooldown "
|
||||
<< spellCooldown
|
||||
<< " queuing retry");
|
||||
<< spellCooldown
|
||||
<< " queuing retry");
|
||||
gCastData.cooldownNonGcdSpellQueued = true;
|
||||
gCastData.cooldownNonGcdEndMs = spellCooldown + currentTime;
|
||||
|
||||
@@ -108,8 +147,8 @@ namespace Nampower {
|
||||
gNonGcdCastQueue.push(*castParams, gUserSettings.replaceMatchingNonGcdCategory);
|
||||
} else {
|
||||
DEBUG_LOG("Spell " << game::GetSpellName(spellId) << " is still on cooldown "
|
||||
<< spellCooldown
|
||||
<< " queuing retry");
|
||||
<< spellCooldown
|
||||
<< " queuing retry");
|
||||
gCastData.cooldownNormalSpellQueued = true;
|
||||
gCastData.cooldownNormalEndMs = spellCooldown + currentTime;
|
||||
|
||||
@@ -142,8 +181,8 @@ namespace Nampower {
|
||||
|
||||
if (!gUserSettings.retryServerRejectedSpells) {
|
||||
DEBUG_LOG("Cast failed for " << game::GetSpellName(spellId)
|
||||
<< " code " << int(spellResult)
|
||||
<< " not queuing retry due to retryServerRejectedSpells=false");
|
||||
<< " code " << int(spellResult)
|
||||
<< " not queuing retry due to retryServerRejectedSpells=false");
|
||||
return;
|
||||
}
|
||||
gLastErrorTimeMs = currentTime;
|
||||
@@ -162,22 +201,21 @@ namespace Nampower {
|
||||
|
||||
if (castParams->castType == CastType::NON_GCD ||
|
||||
castParams->castType == CastType::TARGETING_NON_GCD) {
|
||||
|
||||
// see if we have a recent successful cast of this spell
|
||||
auto successfulCastParams = gCastHistory.findNewestSuccessfulSpellId(spellId);
|
||||
if (successfulCastParams && successfulCastParams->castStartTimeMs > currentTime - 1000) {
|
||||
// we found a recent successful cast of this spell, ignore the failure that was the result
|
||||
// of spamming the cast
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " non gcd code " << int(spellResult)
|
||||
<< ", but a recent cast succeeded, not retrying");
|
||||
<< " non gcd code " << int(spellResult)
|
||||
<< ", but a recent cast succeeded, not retrying");
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " non gcd code " << int(spellResult)
|
||||
<< ", retry " << castParams->numRetries
|
||||
<< " result " << castParams->castResult);
|
||||
<< " non gcd code " << int(spellResult)
|
||||
<< ", retry " << castParams->numRetries
|
||||
<< " result " << castParams->castResult);
|
||||
gCastData.delayEndMs = currentTime + gBufferTimeMs; // retry after buffer delay
|
||||
TriggerSpellQueuedEvent(QueueEvents::NON_GCD_QUEUED, spellId);
|
||||
gCastData.nonGcdSpellQueued = true;
|
||||
@@ -186,13 +224,13 @@ namespace Nampower {
|
||||
// if gcd is active, do nothing
|
||||
if (gCastData.gcdEndMs > currentTime) {
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " code " << int(spellResult)
|
||||
<< ", gcd active not retrying");
|
||||
<< " code " << int(spellResult)
|
||||
<< ", gcd active not retrying");
|
||||
} else {
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " code " << int(spellResult)
|
||||
<< ", retry " << castParams->numRetries
|
||||
<< " result " << castParams->castResult);
|
||||
<< " code " << int(spellResult)
|
||||
<< ", retry " << castParams->numRetries
|
||||
<< " result " << castParams->castResult);
|
||||
|
||||
TriggerSpellQueuedEvent(QueueEvents::NORMAL_QUEUED, spellId);
|
||||
gCastData.normalSpellQueued = true;
|
||||
@@ -201,13 +239,13 @@ namespace Nampower {
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " code " << int(spellResult)
|
||||
<< ", not retrying as it has already been retried 3 times");
|
||||
<< " code " << int(spellResult)
|
||||
<< ", not retrying as it has already been retried 3 times");
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG("Cast failed for #" << lastCastId << " " << game::GetSpellName(spellId) << " "
|
||||
<< " code " << int(spellResult)
|
||||
<< ", no recent cast params found in history");
|
||||
<< " code " << int(spellResult)
|
||||
<< ", no recent cast params found in history");
|
||||
}
|
||||
|
||||
|
||||
@@ -223,10 +261,10 @@ namespace Nampower {
|
||||
}
|
||||
} else if (gCastData.normalSpellQueued) {
|
||||
DEBUG_LOG("Cast failed for " << game::GetSpellName(spellId)
|
||||
<< " spell queued + ignored code " << int(spellResult));
|
||||
<< " spell queued + ignored code " << int(spellResult));
|
||||
} else {
|
||||
DEBUG_LOG("Cast failed for " << game::GetSpellName(spellId)
|
||||
<< " ignored code " << int(spellResult));
|
||||
<< " ignored code " << int(spellResult));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,19 +286,43 @@ namespace Nampower {
|
||||
packet->m_read = rpos;
|
||||
|
||||
DEBUG_LOG("Spell cooldown opcode:" << opCode << " for " << game::GetSpellName(spellId) << " cooldown "
|
||||
<< cooldown);
|
||||
<< cooldown);
|
||||
}
|
||||
|
||||
return spellCooldownHandler(opCode, packet);
|
||||
}
|
||||
|
||||
|
||||
void TriggerSpellDelayedEvent(uint64_t casterGuid,
|
||||
uint32_t delayMs) {
|
||||
static char format[] = "%s%d";
|
||||
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
|
||||
auto event = game::SPELL_DELAYED_OTHER;
|
||||
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
|
||||
event = game::SPELL_DELAYED_SELF;
|
||||
}
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
char *casterGuidParam,
|
||||
uint32_t delayParam)) Offsets::SignalEventParam)(
|
||||
event,
|
||||
format,
|
||||
casterGuidStr,
|
||||
delayMs);
|
||||
|
||||
delete[] casterGuidStr;
|
||||
}
|
||||
|
||||
void Spell_C_CooldownEventTriggeredHook(hadesmem::PatchDetourBase *detour,
|
||||
uint32_t spellId,
|
||||
uint64_t *targetGUID,
|
||||
int param_3,
|
||||
int clearCooldowns) {
|
||||
DEBUG_LOG("Cooldown event triggered for " << game::GetSpellName(spellId) << " " <<
|
||||
targetGUID << " " << param_3 << " " << clearCooldowns);
|
||||
targetGUID << " " << param_3 << " " << clearCooldowns);
|
||||
|
||||
auto const cooldownEventTriggered = detour->GetTrampolineT<Spell_C_CooldownEventTriggeredT>();
|
||||
cooldownEventTriggered(spellId, targetGUID, param_3, clearCooldowns);
|
||||
@@ -279,6 +341,8 @@ namespace Nampower {
|
||||
|
||||
packet->m_read = rpos;
|
||||
|
||||
TriggerSpellDelayedEvent(guid, delay);
|
||||
|
||||
auto const activePlayer = game::ClntObjMgrGetActivePlayerGuid();
|
||||
|
||||
if (guid == activePlayer) {
|
||||
@@ -291,8 +355,8 @@ namespace Nampower {
|
||||
auto lastCastParams = gCastHistory.peek();
|
||||
if (lastCastParams != nullptr) {
|
||||
DEBUG_LOG("Spell delayed by " << delay << " for cast #" << lastCastParams->castId << " "
|
||||
<< game::GetSpellName(lastCastParams->spellId)
|
||||
<< " cast end " << gCastData.castEndMs);
|
||||
<< game::GetSpellName(lastCastParams->spellId)
|
||||
<< " cast end " << gCastData.castEndMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,19 +437,19 @@ namespace Nampower {
|
||||
|
||||
if (gLastServerSpellDelayMs > 0) {
|
||||
DEBUG_LOG("Cast result for #" << matchingCastId << " "
|
||||
<< game::GetSpellName(spellId)
|
||||
<< "(" << spellId << ")"
|
||||
<< " status " << int(status)
|
||||
<< " result " << int(spellCastResult) << " latency " << currentLatency
|
||||
<< " server delay " << gLastServerSpellDelayMs
|
||||
<< " since last cast result " << currentTime - lastCastResultTimeMs);
|
||||
<< game::GetSpellName(spellId)
|
||||
<< "(" << spellId << ")"
|
||||
<< " status " << int(status)
|
||||
<< " result " << int(spellCastResult) << " latency " << currentLatency
|
||||
<< " server delay " << gLastServerSpellDelayMs
|
||||
<< " since last cast result " << currentTime - lastCastResultTimeMs);
|
||||
} else {
|
||||
DEBUG_LOG("Cast result for #" << matchingCastId << " "
|
||||
<< game::GetSpellName(spellId)
|
||||
<< "(" << spellId << ")"
|
||||
<< " status " << int(status)
|
||||
<< " result " << int(spellCastResult) << " latency " << currentLatency
|
||||
<< " since last cast result " << currentTime - lastCastResultTimeMs);
|
||||
<< game::GetSpellName(spellId)
|
||||
<< "(" << spellId << ")"
|
||||
<< " status " << int(status)
|
||||
<< " result " << int(spellCastResult) << " latency " << currentLatency
|
||||
<< " since last cast result " << currentTime - lastCastResultTimeMs);
|
||||
}
|
||||
|
||||
lastCastResultTimeMs = currentTime;
|
||||
@@ -408,9 +472,358 @@ namespace Nampower {
|
||||
|
||||
DEBUG_LOG("Spell failed opcode:" << opCode << " for " << game::GetSpellName(spellId) << " guid " << guid);
|
||||
|
||||
TriggerSpellFailedOtherEvent(guid, spellId);
|
||||
|
||||
return spellFailedHandler(opCode, packet);
|
||||
}
|
||||
|
||||
bool doesSpellApplyAura(const game::SpellRec *spell) {
|
||||
if (!spell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i: spell->Effect) {
|
||||
switch (i) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void triggerAuraCastEventOnTarget(const game::SpellRec *spell,
|
||||
int spellEffectIndex,
|
||||
char *casterGuidStr,
|
||||
char *targetGuidStr, // if provided use this instead of creating one
|
||||
uint64_t targetGuid,
|
||||
uint64_t activePlayerGuid,
|
||||
uint32_t effectType,
|
||||
uint32_t duration) {
|
||||
static char format[] = "%d%s%s%d%d%d%d%d%d";
|
||||
|
||||
auto eventToTrigger = game::AURA_CAST_ON_OTHER;
|
||||
if (targetGuid == activePlayerGuid) {
|
||||
eventToTrigger = game::AURA_CAST_ON_SELF;
|
||||
}
|
||||
|
||||
auto auraName = spell->EffectApplyAuraName[spellEffectIndex];
|
||||
auto effectAmplitude = spell->EffectAmplitude[spellEffectIndex];
|
||||
auto effectMiscValue = spell->EffectMiscValue[spellEffectIndex];
|
||||
|
||||
auto *targetUnit = game::ClntObjMgrObjectPtr(
|
||||
static_cast<game::TypeMask>(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), targetGuid);
|
||||
|
||||
bool targetIsBuffCapped = game::UnitIsBuffCapped(targetUnit);
|
||||
bool targetIsDebuffCapped = game::UnitIsDebuffCapped(targetUnit);
|
||||
|
||||
bool createdGuidStr = false;
|
||||
if (!targetGuidStr) {
|
||||
if (targetGuid > 0) {
|
||||
targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
createdGuidStr = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto auraCapStatus = static_cast<uint32_t>(targetIsBuffCapped) |
|
||||
(static_cast<uint32_t>(targetIsDebuffCapped) << 1);
|
||||
|
||||
reinterpret_cast<int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
char *casterGuidStrParam,
|
||||
char *targetGuidStrParam,
|
||||
uint32_t effectParam,
|
||||
uint32_t auraNameParam,
|
||||
uint32_t effectAmplitudeParam,
|
||||
uint32_t effectMiscValueParam,
|
||||
uint32_t durationParam,
|
||||
uint32_t auraCapStatusParam)>(Offsets::SignalEventParam)(
|
||||
eventToTrigger,
|
||||
format,
|
||||
spell->Id,
|
||||
casterGuidStr,
|
||||
targetGuidStr,
|
||||
effectType,
|
||||
auraName,
|
||||
effectAmplitude,
|
||||
effectMiscValue,
|
||||
duration,
|
||||
auraCapStatus);
|
||||
|
||||
if (createdGuidStr) {
|
||||
delete [] targetGuidStr;
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerAuraCastEvent(const game::SpellRec *spell,
|
||||
uint64_t casterGuid,
|
||||
uint64_t targetGuids[MAX_ALLOWED_SPELL_TARGETS],
|
||||
uint8_t numTargets,
|
||||
uint64_t activePlayerGuid,
|
||||
bool castByActivePlayer) {
|
||||
// ignore modifiers if not cast by active player
|
||||
auto duration = game::GetSpellDuration(spell, !castByActivePlayer);
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto const effectType = spell->Effect[i];
|
||||
switch (effectType) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET: {
|
||||
auto const implicitTargetA = spell->EffectImplicitTargetA[i];
|
||||
auto const implicitTargetB = spell->EffectImplicitTargetB[i];
|
||||
|
||||
if (implicitTargetA == game::TARGET_UNIT_CASTER) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, casterGuidStr, casterGuid,
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
break;
|
||||
}
|
||||
|
||||
// handle no targets
|
||||
if (numTargets == 0 || implicitTargetA == game::TARGET_NONE) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, 0, activePlayerGuid,
|
||||
effectType, duration);
|
||||
} else if ((implicitTargetA >= game::TARGET_UNIT_ENEMY_NEAR_CASTER && implicitTargetA <=
|
||||
game::TARGET_UNIT_ENEMY) ||
|
||||
(implicitTargetA >= game::TARGET_PLAYER_NYI && implicitTargetA <=
|
||||
game::TARGET_PLAYER_FRIEND_NYI) ||
|
||||
implicitTargetA == game::TARGET_UNIT_FRIEND ||
|
||||
implicitTargetA == game::TARGET_UNIT ||
|
||||
implicitTargetA == game::TARGET_UNIT_CASTER_MASTER) {
|
||||
// trigger on first target
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[0], activePlayerGuid,
|
||||
effectType, duration);
|
||||
} else if (implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_24 ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_WITHIN_CASTER_RANGE ||
|
||||
implicitTargetA == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_54 ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_24 ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_WITHIN_CASTER_RANGE ||
|
||||
implicitTargetB == game::TARGET_ENUM_UNITS_ENEMY_IN_CONE_54) {
|
||||
// trigger on all targets except caster
|
||||
for (int t = 0; t < numTargets; ++t) {
|
||||
if (targetGuids[t] != casterGuid) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[t],
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// trigger on all targets (probably some exceptions but good enough for now)
|
||||
for (int t = 0; t < numTargets; ++t) {
|
||||
triggerAuraCastEventOnTarget(spell, i, casterGuidStr, nullptr, targetGuids[t],
|
||||
activePlayerGuid,
|
||||
effectType, duration);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] casterGuidStr;
|
||||
}
|
||||
|
||||
void TriggerSpellStartEvent(uint32_t itemId,
|
||||
uint32_t spellId,
|
||||
uint64_t casterGuid,
|
||||
uint64_t targetGuid,
|
||||
uint16_t castFlags,
|
||||
uint32_t castTime) {
|
||||
static char format[] = "%d%d%s%s%d%d";
|
||||
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
char *targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
|
||||
auto event = game::SPELL_START_OTHER;
|
||||
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
|
||||
event = game::SPELL_START_SELF;
|
||||
}
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t itemIdParam,
|
||||
uint32_t spellIdParam,
|
||||
char *casterGuidParam,
|
||||
char *targetGuidParam,
|
||||
uint32_t castFlagsParam,
|
||||
uint32_t castTimeParam)) Offsets::SignalEventParam)(
|
||||
event,
|
||||
format,
|
||||
itemId,
|
||||
spellId,
|
||||
casterGuidStr,
|
||||
targetGuidStr,
|
||||
castFlags,
|
||||
castTime);
|
||||
|
||||
delete[] casterGuidStr;
|
||||
delete[] targetGuidStr;
|
||||
}
|
||||
|
||||
void TriggerSpellGoEvent(uint32_t itemId,
|
||||
uint32_t spellId,
|
||||
const uint64_t *casterGuid,
|
||||
uint64_t targetGuid,
|
||||
uint16_t castFlags,
|
||||
uint8_t numTargetsHit,
|
||||
uint8_t numTargetsMissed) {
|
||||
static char format[] = "%d%d%s%s%d%d%d";
|
||||
|
||||
uint64_t casterGuidValue = casterGuid ? *casterGuid : 0;
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuidValue);
|
||||
char *targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
|
||||
auto event = game::SPELL_GO_OTHER;
|
||||
if (casterGuidValue == game::ClntObjMgrGetActivePlayerGuid()) {
|
||||
event = game::SPELL_GO_SELF;
|
||||
}
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t itemIdParam,
|
||||
uint32_t spellIdParam,
|
||||
char *casterGuidParam,
|
||||
char *targetGuidParam,
|
||||
uint32_t castFlagsParam,
|
||||
uint32_t numTargetsHitParam,
|
||||
uint32_t numTargetsMissedParam)) Offsets::SignalEventParam)(
|
||||
event,
|
||||
format,
|
||||
itemId,
|
||||
spellId,
|
||||
casterGuidStr,
|
||||
targetGuidStr,
|
||||
castFlags,
|
||||
numTargetsHit,
|
||||
numTargetsMissed);
|
||||
|
||||
delete[] casterGuidStr;
|
||||
delete[] targetGuidStr;
|
||||
}
|
||||
|
||||
void SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *itemGUID, uint64_t *casterGUID,
|
||||
uint32_t spellId, CDataStore *packet) {
|
||||
auto const rpos = packet->m_read;
|
||||
|
||||
int16_t castFlags;
|
||||
packet->Get(castFlags);
|
||||
|
||||
uint8_t numTargetsHit;
|
||||
packet->Get(numTargetsHit);
|
||||
|
||||
static uint64_t targetHitGuids[MAX_ALLOWED_SPELL_TARGETS]; // don't allocate new array each time
|
||||
// each target corresponds to a spell effect, should never have more than 3
|
||||
for (int i = 0; i < numTargetsHit && i < MAX_ALLOWED_SPELL_TARGETS; ++i) {
|
||||
targetHitGuids[i] = 0;
|
||||
packet->Get(targetHitGuids[i]);
|
||||
}
|
||||
|
||||
uint8_t numTargetsMissed;
|
||||
packet->Get(numTargetsMissed);
|
||||
for (int i = 0; i < numTargetsMissed; ++i) {
|
||||
uint64_t missedTargetGuid;
|
||||
packet->Get(missedTargetGuid); // not worth storing missed targets
|
||||
|
||||
uint8_t missCondition;
|
||||
packet->Get(missCondition);
|
||||
// Reflect entries include an extra result byte in SpellGoTargets.
|
||||
if (missCondition == game::SPELL_MISS_REFLECT) {
|
||||
uint8_t reflectResult;
|
||||
packet->Get(reflectResult);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t spellCastTargetMask;
|
||||
packet->Get(spellCastTargetMask);
|
||||
|
||||
uint64_t unitTargetGUID = 0;
|
||||
if (spellCastTargetMask & (game::TARGET_FLAG_UNIT | game::TARGET_FLAG_UNK2))
|
||||
packet->GetPackedGuid(unitTargetGUID);
|
||||
|
||||
uint32_t itemId = 0;
|
||||
uint64_t casterGuidValue = casterGUID ? *casterGUID : 0;
|
||||
if (itemGUID && *itemGUID != 0 && *itemGUID != casterGuidValue) {
|
||||
auto *item = reinterpret_cast<game::CGItem_C *>(game::ClntObjMgrObjectPtr(
|
||||
game::TYPEMASK_ITEM, *itemGUID));
|
||||
if (item) {
|
||||
itemId = game::GetItemId(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (gUserSettings.enableSpellGoEvents) {
|
||||
TriggerSpellGoEvent(itemId, spellId, casterGUID, unitTargetGUID, castFlags,
|
||||
numTargetsHit, numTargetsMissed);
|
||||
}
|
||||
|
||||
// reset read pointer
|
||||
packet->m_read = rpos;
|
||||
|
||||
auto const spellGo = detour->GetTrampolineT<SpellGoT>();
|
||||
// if this wasn't triggered by an item, itemGUID will just be the casterGUID duplicated
|
||||
spellGo(itemGUID, casterGUID, spellId, packet);
|
||||
|
||||
auto const activePlayerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto const castByActivePlayer = activePlayerGuid == casterGuidValue;
|
||||
auto const spell = game::GetSpellInfo(spellId);
|
||||
|
||||
if (!spell || spell->Id <= 0) {
|
||||
DEBUG_LOG("Unable to determine spell information in SpellGo for " << spellId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (castByActivePlayer) {
|
||||
auto const currentTime = GetTime();
|
||||
// only care about our own casts
|
||||
if (!gCastData.channeling) {
|
||||
// check if spell is on swing
|
||||
if (spell->Attributes & game::SPELL_ATTR_ON_NEXT_SWING_1) {
|
||||
gLastCastData.onSwingStartTimeMs = currentTime;
|
||||
|
||||
gCastData.pendingOnSwingCast = false;
|
||||
|
||||
if (gCastData.onSwingQueued) {
|
||||
DEBUG_LOG("On swing spell " << game::GetSpellName(spellId) <<
|
||||
" resolved, casting queued on swing spell "
|
||||
<< game::GetSpellName(gLastOnSwingCastParams.spellId));
|
||||
|
||||
TriggerSpellQueuedEvent(ON_SWING_QUEUE_POPPED, gLastOnSwingCastParams.spellId);
|
||||
Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.casterUnit,
|
||||
gLastOnSwingCastParams.spellId,
|
||||
gLastOnSwingCastParams.item, gLastOnSwingCastParams.guid);
|
||||
gCastData.onSwingQueued = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) {
|
||||
TriggerAuraCastEvent(spell, *casterGUID, targetHitGuids, numTargetsHit, activePlayerGuid,
|
||||
castByActivePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
int SpellStartHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t unk, uint32_t opCode, uint32_t unk2,
|
||||
CDataStore *packet) {
|
||||
auto const spellStartHandler = detour->GetTrampolineT<FastCallPacketHandlerT>();
|
||||
@@ -427,16 +840,36 @@ namespace Nampower {
|
||||
uint64_t casterGuid;
|
||||
packet->GetPackedGuid(casterGuid);
|
||||
|
||||
uint32_t spellId;
|
||||
packet->Get(spellId);
|
||||
|
||||
uint16_t castFlags;
|
||||
packet->Get(castFlags);
|
||||
|
||||
uint32_t castTime;
|
||||
packet->Get(castTime);
|
||||
|
||||
int16_t spellCastTargetMask;
|
||||
packet->Get(spellCastTargetMask);
|
||||
|
||||
uint64_t unitTargetGUID = 0;
|
||||
if (spellCastTargetMask & (game::TARGET_FLAG_UNIT | game::TARGET_FLAG_UNK2))
|
||||
packet->GetPackedGuid(unitTargetGUID);
|
||||
|
||||
uint32_t itemId = 0;
|
||||
if (itemGuid != 0 && itemGuid != casterGuid) {
|
||||
auto *item = reinterpret_cast<game::CGItem_C *>(game::ClntObjMgrObjectPtr(
|
||||
game::TYPEMASK_ITEM, itemGuid));
|
||||
if (item) {
|
||||
itemId = game::GetItemId(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (gUserSettings.enableSpellStartEvents) {
|
||||
TriggerSpellStartEvent(itemId, spellId, casterGuid, unitTargetGUID, castFlags, castTime);
|
||||
}
|
||||
|
||||
if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) {
|
||||
uint32_t spellId;
|
||||
packet->Get(spellId);
|
||||
|
||||
uint16_t castFlags;
|
||||
packet->Get(castFlags);
|
||||
|
||||
uint32_t castTime;
|
||||
packet->Get(castTime);
|
||||
|
||||
bool isAutoRepeat = false;
|
||||
|
||||
auto spellInfo = game::GetSpellInfo(spellId);
|
||||
@@ -460,8 +893,8 @@ namespace Nampower {
|
||||
|
||||
castParams->castTimeMs = castTime;
|
||||
DEBUG_LOG("Server cast time for " << game::GetSpellName(spellId) << " increased by "
|
||||
<< castTimeDifference << "ms. Updated cast end time to "
|
||||
<< gCastData.castEndMs);
|
||||
<< castTimeDifference << "ms. Updated cast end time to "
|
||||
<< gCastData.castEndMs);
|
||||
}
|
||||
} else if (!isAutoRepeat && castParams->castTimeMs > castTime) {
|
||||
// server cast time decreased
|
||||
@@ -470,23 +903,23 @@ namespace Nampower {
|
||||
if (castTimeDifference > 5) {
|
||||
auto gcdTime = GetGcdOrCooldownForSpell(spellId);
|
||||
if (gcdTime > 1500) {
|
||||
gcdTime = 1500; // items with spells on gcd will return their item gcd, make sure not to use that
|
||||
gcdTime = 1500;
|
||||
// items with spells on gcd will return their item gcd, make sure not to use that
|
||||
}
|
||||
|
||||
if (gcdTime > castTime) {
|
||||
DEBUG_LOG("Server cast time " << castTime << " was reduced below gcd of " << gcdTime
|
||||
<< " using gcd instead");
|
||||
<< " using gcd instead");
|
||||
castTime = gcdTime;
|
||||
gCastData.castEndMs = castParams->castStartTimeMs + gcdTime;
|
||||
} else {
|
||||
gCastData.castEndMs = castParams->castStartTimeMs + castTime + gBufferTimeMs;
|
||||
|
||||
}
|
||||
|
||||
castParams->castTimeMs = castTime;
|
||||
DEBUG_LOG("Server cast time for " << game::GetSpellName(spellId) << " decreased by "
|
||||
<< castTimeDifference << "ms. Updated cast end time to "
|
||||
<< gCastData.castEndMs);
|
||||
<< castTimeDifference << "ms. Updated cast end time to "
|
||||
<< gCastData.castEndMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +968,7 @@ namespace Nampower {
|
||||
int32_t resist,
|
||||
uint32_t auraType,
|
||||
uint32_t hitInfo) {
|
||||
char format[] = "%s%s%d%d%s%d%d%s";
|
||||
static char format[] = "%s%s%d%d%s%d%d%s";
|
||||
|
||||
char *targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
|
||||
@@ -575,16 +1008,16 @@ namespace Nampower {
|
||||
uint32_t hitInfo,
|
||||
uint32_t spellSchool,
|
||||
const char *effectAuraStr)) Offsets::SignalEventParam)(
|
||||
event,
|
||||
format,
|
||||
targetGuidStr,
|
||||
casterGuidStr,
|
||||
spellId,
|
||||
amount,
|
||||
mitigationStream.str().c_str(),
|
||||
hitInfo,
|
||||
spellSchool,
|
||||
effectsAuraTypeStream.str().c_str());
|
||||
event,
|
||||
format,
|
||||
targetGuidStr,
|
||||
casterGuidStr,
|
||||
spellId,
|
||||
amount,
|
||||
mitigationStream.str().c_str(),
|
||||
hitInfo,
|
||||
spellSchool,
|
||||
effectsAuraTypeStream.str().c_str());
|
||||
|
||||
delete[] targetGuidStr;
|
||||
delete[] casterGuidStr;
|
||||
@@ -615,7 +1048,7 @@ namespace Nampower {
|
||||
switch (auraType) {
|
||||
case 3: // SPELL_AURA_PERIODIC_DAMAGE
|
||||
case 89: // SPELL_AURA_PERIODIC_DAMAGE_PERCENT
|
||||
packet->Get(amount); // damage amount
|
||||
packet->Get(amount); // damage amount
|
||||
|
||||
uint32_t spellSchool;
|
||||
packet->Get(spellSchool);
|
||||
@@ -632,7 +1065,7 @@ namespace Nampower {
|
||||
break;
|
||||
case 8: // SPELL_AURA_PERIODIC_HEAL
|
||||
case 20: // SPELL_AURA_OBS_MOD_HEALTH
|
||||
packet->Get(amount); // heal amount
|
||||
packet->Get(amount); // heal amount
|
||||
|
||||
// No custom event for these for now
|
||||
break;
|
||||
@@ -640,16 +1073,16 @@ namespace Nampower {
|
||||
case 24: // SPELL_AURA_PERIODIC_ENERGIZE
|
||||
packet->Get(powerType);
|
||||
|
||||
packet->Get(amount); // power type amount
|
||||
packet->Get(amount); // power type amount
|
||||
|
||||
// No custom event for these for now
|
||||
break;
|
||||
case 64: // SPELL_AURA_PERIODIC_MANA_LEECH
|
||||
packet->Get(powerType);
|
||||
packet->Get(amount); // power type amount
|
||||
packet->Get(amount); // power type amount
|
||||
|
||||
uint32_t multiplier;
|
||||
packet->Get(multiplier); // gain multiplier
|
||||
packet->Get(multiplier); // gain multiplier
|
||||
|
||||
// No custom event for these for now
|
||||
break;
|
||||
@@ -726,7 +1159,7 @@ namespace Nampower {
|
||||
|
||||
DEBUG_LOG("Play spell visual id " << spellVisualKitIndex << " for guid " << targetGuid);
|
||||
|
||||
// return playSpellVisualHandler(opCode, packet);
|
||||
// return playSpellVisualHandler(opCode, packet);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Nampower {
|
||||
uint32_t Script_SpellTargetUnitHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
|
||||
|
||||
void Spell_C_SpellFailedHook(hadesmem::PatchDetourBase *detour, uint32_t spellId,
|
||||
game::SpellCastResult spellResult, int unk1, int unk2, char unk3);
|
||||
game::SpellCastResult spellResult, int unk1, int unk2, bool failedByServer);
|
||||
|
||||
int CastResultHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet);
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ struct UserSettings {
|
||||
bool spamProtectionEnabled = true;
|
||||
bool enableAuraCastEvents = false;
|
||||
bool enableAutoAttackEvents = false;
|
||||
bool enableSpellStartEvents = false;
|
||||
bool enableSpellGoEvents = false;
|
||||
bool preventMountingWhenBuffCapped = true;
|
||||
|
||||
uint32_t spellQueueWindowMs = 500;
|
||||
|
||||
Reference in New Issue
Block a user