Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 75 additions & 59 deletions include/py_combat_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

#include <GWCA/Packets/StoC.h>
#include <GWCA/Managers/StoCMgr.h>
#include <GWCA/Managers/MapMgr.h>
#include <GWCA/Constants/Constants.h>
#include <GWCA/Utilities/Hook.h>

#include <pybind11/pybind11.h>
Expand Down Expand Up @@ -93,115 +95,120 @@ struct RawCombatEvent {
* Note: The values are NOT arbitrary - they're chosen to avoid conflicts
* and are grouped by category (skills 1-8, attacks 13-15, damage 30-32, etc.)
*/
namespace CombatEventTypes {
enum class CombatEventType : uint32_t {
// ---- Skill Events (from GenericValue/GenericValueTarget packets) ----
// These fire when agents use skills (spells, signets, etc.)

constexpr uint32_t SKILL_ACTIVATED = 1; // Non-attack skill started casting
// agent_id=caster, value=skill_id, target_id=target
SKILL_ACTIVATED = 1, // Non-attack skill started casting
// agent_id=caster, value=skill_id, target_id=target

constexpr uint32_t ATTACK_SKILL_ACTIVATED = 2; // Attack skill started (e.g., Jagged Strike)
// agent_id=caster, value=skill_id, target_id=target
ATTACK_SKILL_ACTIVATED = 2, // Attack skill started (e.g., Jagged Strike)
// agent_id=caster, value=skill_id, target_id=target

constexpr uint32_t SKILL_STOPPED = 3; // Skill cast was cancelled (moved, etc.)
// agent_id=caster, value=skill_id
SKILL_STOPPED = 3, // Skill cast was cancelled (moved, etc.)
// agent_id=caster, value=skill_id

constexpr uint32_t SKILL_FINISHED = 4; // Skill completed successfully
// agent_id=caster, value=skill_id
SKILL_FINISHED = 4, // Skill completed successfully
// agent_id=caster, value=skill_id

constexpr uint32_t ATTACK_SKILL_FINISHED = 5; // Attack skill completed
// agent_id=caster, value=skill_id
ATTACK_SKILL_FINISHED = 5, // Attack skill completed
// agent_id=caster, value=skill_id

constexpr uint32_t INTERRUPTED = 6; // Skill was interrupted
// agent_id=interrupted agent, value=skill_id
INTERRUPTED = 6, // Skill was interrupted
// agent_id=interrupted agent, value=skill_id

constexpr uint32_t INSTANT_SKILL_ACTIVATED = 7; // Instant-cast skill used (no cast time)
// agent_id=caster, value=skill_id, target_id=target
INSTANT_SKILL_ACTIVATED = 7, // Instant-cast skill used (no cast time)
// agent_id=caster, value=skill_id, target_id=target

constexpr uint32_t ATTACK_SKILL_STOPPED = 8; // Attack skill was cancelled
// agent_id=caster, value=skill_id
ATTACK_SKILL_STOPPED = 8, // Attack skill was cancelled
// agent_id=caster, value=skill_id

// ---- Attack Events (auto-attacks, from GenericValueTarget) ----

constexpr uint32_t ATTACK_STARTED = 13; // Auto-attack started (not a skill)
// agent_id=attacker, target_id=target
ATTACK_STARTED = 13, // Auto-attack started (not a skill)
// agent_id=attacker, target_id=target

constexpr uint32_t ATTACK_STOPPED = 14; // Auto-attack stopped/cancelled
// agent_id=attacker
ATTACK_STOPPED = 14, // Auto-attack stopped/cancelled
// agent_id=attacker

constexpr uint32_t MELEE_ATTACK_FINISHED = 15; // Melee attack hit completed
// agent_id=attacker
MELEE_ATTACK_FINISHED = 15, // Melee attack hit completed
// agent_id=attacker

// ---- State Events ----

constexpr uint32_t DISABLED = 16; // Agent disabled state changed (cast-lock/aftercast)
// agent_id=agent, value=1 (disabled) or 0 (can act)
// This fires 4 times per skill: cast start, cast end,
// aftercast start, aftercast end
DISABLED = 16, // Agent disabled state changed (cast-lock/aftercast)
// agent_id=agent, value=1 (disabled) or 0 (can act)
// This fires 4 times per skill: cast start, cast end,
// aftercast start, aftercast end

constexpr uint32_t KNOCKED_DOWN = 17; // Agent was knocked down
// agent_id=knocked agent, float_value=duration in seconds
KNOCKED_DOWN = 17, // Agent was knocked down
// agent_id=knocked agent, float_value=duration in seconds

constexpr uint32_t CASTTIME = 18; // Cast time modifier received
// agent_id=caster, float_value=cast duration in seconds
CASTTIME = 18, // Cast time modifier received
// agent_id=caster, float_value=cast duration in seconds

// ---- Damage Events (from GenericModifier packets) ----
// Note: For damage, the packet naming is counter-intuitive!
// agent_id = target (who RECEIVES damage)
// target_id = source (who DEALS damage)
// float_value = damage as fraction of target's max HP

constexpr uint32_t DAMAGE = 30; // Normal damage dealt
// agent_id=target, target_id=source, float_value=damage%
DAMAGE = 30, // Normal damage dealt
// agent_id=target, target_id=source, float_value=damage%

constexpr uint32_t CRITICAL = 31; // Critical hit damage
// agent_id=target, target_id=source, float_value=damage%
CRITICAL = 31, // Critical hit damage
// agent_id=target, target_id=source, float_value=damage%

constexpr uint32_t ARMOR_IGNORING = 32; // Armor-ignoring damage (life steal, etc.)
// Can be negative for heals!
// agent_id=target, target_id=source, float_value=damage%
ARMOR_IGNORING = 32, // Armor-ignoring damage (life steal, etc.)
// Can be negative for heals!
// agent_id=target, target_id=source, float_value=damage%

// ---- Effect Events (from GenericValue/GenericValueTarget) ----

constexpr uint32_t EFFECT_APPLIED = 40; // Visual effect applied (internal effect_id, not skill_id!)
// agent_id=affected agent, value=effect_id
EFFECT_APPLIED = 40, // Visual effect applied (internal effect_id, not skill_id!)
// agent_id=affected agent, value=effect_id

constexpr uint32_t EFFECT_REMOVED = 41; // Visual effect removed
// agent_id=affected agent, value=effect_id
EFFECT_REMOVED = 41, // Visual effect removed
// agent_id=affected agent, value=effect_id

constexpr uint32_t EFFECT_ON_TARGET = 42; // Skill effect hit a target (from effect_on_target packet)
// agent_id=caster, value=effect_id, target_id=target
// Python correlates this with recent casts to get skill_id
EFFECT_ON_TARGET = 42, // Skill effect hit a target (from effect_on_target packet)
// agent_id=caster, value=effect_id, target_id=target
// Python correlates this with recent casts to get skill_id

// ---- Energy Events ----

constexpr uint32_t ENERGY_GAINED = 50; // Energy gained (from GenericValue energygain)
// agent_id=agent, float_value=energy amount (raw points)
ENERGY_GAINED = 50, // Energy gained (from GenericValue energygain)
// agent_id=agent, float_value=energy amount (raw points)

constexpr uint32_t ENERGY_SPENT = 51; // Energy spent (from GenericFloat energy_spent)
// agent_id=agent, float_value=energy as fraction of max
ENERGY_SPENT = 51, // Energy spent (from GenericFloat energy_spent)
// agent_id=agent, float_value=energy as fraction of max

// ---- Skill-Damage Attribution ----

constexpr uint32_t SKILL_DAMAGE = 60; // Pre-notification: this skill will cause damage
// agent_id=target, value=skill_id
// Sent to TARGET before DAMAGE packet arrives
SKILL_DAMAGE = 60, // Pre-notification: this skill will cause damage
// agent_id=target, value=skill_id
// Sent to TARGET before DAMAGE packet arrives

// ---- Pre-Notification ----

constexpr uint32_t SKILL_ACTIVATE_PACKET = 70; // Early skill activation notification
// agent_id=caster, value=skill_id
// From SkillActivate packet (arrives before GenericValue)
SKILL_ACTIVATE_PACKET = 70, // Early skill activation notification
// agent_id=caster, value=skill_id
// From SkillActivate packet (arrives before GenericValue)

// ---- Skill Recharge Events (from SkillRecharge/SkillRecharged packets) ----
// These track when skills go on cooldown and come off cooldown.
// Works for ANY agent - player, heroes, enemies, NPCs!

constexpr uint32_t SKILL_RECHARGE = 80; // Skill went on cooldown
// agent_id=agent, value=skill_id, float_value=recharge time in ms
SKILL_RECHARGE = 80, // Skill went on cooldown
// agent_id=agent, value=skill_id, float_value=recharge time in ms

constexpr uint32_t SKILL_RECHARGED = 81; // Skill came off cooldown
// agent_id=agent, value=skill_id
SKILL_RECHARGED = 81 // Skill came off cooldown
// agent_id=agent, value=skill_id
};

// Helper function to convert enum to uint32_t for backwards compatibility
constexpr uint32_t to_uint(CombatEventType type) {
return static_cast<uint32_t>(type);
}

// ============================================================================
Expand Down Expand Up @@ -312,6 +319,15 @@ class CombatEventQueue {
* If queue exceeds max_events, oldest events are dropped.
*/
void PushEvent(const RawCombatEvent& event);

/**
* @brief Check if the map is ready for packet processing.
* @return true if map is loaded and not in loading state.
*
* Prevents crashes during map transitions by skipping packet processing
* when game memory may be invalid.
*/
bool IsMapReady() const;
};

// ============================================================================
Expand Down
58 changes: 51 additions & 7 deletions src/py_combat_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,37 @@

namespace py = pybind11;

// Backwards compatibility: create namespace with constexpr aliases to enum class values
// This allows existing code using CombatEventTypes::FOO to continue working
namespace CombatEventTypes {
constexpr uint32_t SKILL_ACTIVATED = to_uint(CombatEventType::SKILL_ACTIVATED);
constexpr uint32_t ATTACK_SKILL_ACTIVATED = to_uint(CombatEventType::ATTACK_SKILL_ACTIVATED);
constexpr uint32_t SKILL_STOPPED = to_uint(CombatEventType::SKILL_STOPPED);
constexpr uint32_t SKILL_FINISHED = to_uint(CombatEventType::SKILL_FINISHED);
constexpr uint32_t ATTACK_SKILL_FINISHED = to_uint(CombatEventType::ATTACK_SKILL_FINISHED);
constexpr uint32_t INTERRUPTED = to_uint(CombatEventType::INTERRUPTED);
constexpr uint32_t INSTANT_SKILL_ACTIVATED = to_uint(CombatEventType::INSTANT_SKILL_ACTIVATED);
constexpr uint32_t ATTACK_SKILL_STOPPED = to_uint(CombatEventType::ATTACK_SKILL_STOPPED);
constexpr uint32_t ATTACK_STARTED = to_uint(CombatEventType::ATTACK_STARTED);
constexpr uint32_t ATTACK_STOPPED = to_uint(CombatEventType::ATTACK_STOPPED);
constexpr uint32_t MELEE_ATTACK_FINISHED = to_uint(CombatEventType::MELEE_ATTACK_FINISHED);
constexpr uint32_t DISABLED = to_uint(CombatEventType::DISABLED);
constexpr uint32_t KNOCKED_DOWN = to_uint(CombatEventType::KNOCKED_DOWN);
constexpr uint32_t CASTTIME = to_uint(CombatEventType::CASTTIME);
constexpr uint32_t DAMAGE = to_uint(CombatEventType::DAMAGE);
constexpr uint32_t CRITICAL = to_uint(CombatEventType::CRITICAL);
constexpr uint32_t ARMOR_IGNORING = to_uint(CombatEventType::ARMOR_IGNORING);
constexpr uint32_t EFFECT_APPLIED = to_uint(CombatEventType::EFFECT_APPLIED);
constexpr uint32_t EFFECT_REMOVED = to_uint(CombatEventType::EFFECT_REMOVED);
constexpr uint32_t EFFECT_ON_TARGET = to_uint(CombatEventType::EFFECT_ON_TARGET);
constexpr uint32_t ENERGY_GAINED = to_uint(CombatEventType::ENERGY_GAINED);
constexpr uint32_t ENERGY_SPENT = to_uint(CombatEventType::ENERGY_SPENT);
constexpr uint32_t SKILL_DAMAGE = to_uint(CombatEventType::SKILL_DAMAGE);
constexpr uint32_t SKILL_ACTIVATE_PACKET = to_uint(CombatEventType::SKILL_ACTIVATE_PACKET);
constexpr uint32_t SKILL_RECHARGE = to_uint(CombatEventType::SKILL_RECHARGE);
constexpr uint32_t SKILL_RECHARGED = to_uint(CombatEventType::SKILL_RECHARGED);
}

// ============================================================================
// Lifecycle Implementation
// ============================================================================
Expand Down Expand Up @@ -156,6 +187,12 @@ void CombatEventQueue::PushEvent(const RawCombatEvent& event) {
}
}

bool CombatEventQueue::IsMapReady() const {
auto instance_type = GW::Map::GetInstanceType();
return GW::Map::GetIsMapLoaded() &&
instance_type != GW::Constants::InstanceType::Loading;
}

// ============================================================================
// Packet Handlers
// ============================================================================
Expand All @@ -170,7 +207,8 @@ void CombatEventQueue::PushEvent(const RawCombatEvent& event) {
* with other packets that may not have the skill_id.
*/
void CombatEventQueue::OnSkillActivate(GW::Packet::StoC::SkillActivate* packet) {
uint32_t now = GetTickCount64 ();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());
PushEvent(RawCombatEvent(now, CombatEventTypes::SKILL_ACTIVATE_PACKET,
packet->agent_id, packet->skill_id, 0, 0.0f));
}
Expand All @@ -191,7 +229,8 @@ void CombatEventQueue::OnSkillActivate(GW::Packet::StoC::SkillActivate* packet)
* - energygain: Energy gained
*/
void CombatEventQueue::OnGenericValue(GW::Packet::StoC::GenericValue* packet) {
uint32_t now = GetTickCount64();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());

using namespace GW::Packet::StoC::GenericValueID;

Expand Down Expand Up @@ -293,7 +332,8 @@ void CombatEventQueue::OnGenericValue(GW::Packet::StoC::GenericValue* packet) {
* - target_id = target/victim
*/
void CombatEventQueue::OnGenericValueTarget(GW::Packet::StoC::GenericValueTarget* packet) {
uint32_t now = GetTickCount64();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());

using namespace GW::Packet::StoC::GenericValueID;

Expand Down Expand Up @@ -339,7 +379,8 @@ void CombatEventQueue::OnGenericValueTarget(GW::Packet::StoC::GenericValueTarget
* - energy_spent: Energy consumed, float_value = energy as fraction of max
*/
void CombatEventQueue::OnGenericFloat(GW::Packet::StoC::GenericFloat* packet) {
uint32_t now = GetTickCount64();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());

using namespace GW::Packet::StoC::GenericValueID;

Expand Down Expand Up @@ -380,7 +421,8 @@ void CombatEventQueue::OnGenericFloat(GW::Packet::StoC::GenericFloat* packet) {
* Example: float_value = 0.15 on a target with 480 HP = 72 damage
*/
void CombatEventQueue::OnGenericModifier(GW::Packet::StoC::GenericModifier* packet) {
uint32_t now = GetTickCount64();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());

using namespace GW::Packet::StoC::GenericValueID;

Expand Down Expand Up @@ -427,7 +469,8 @@ void CombatEventQueue::OnGenericModifier(GW::Packet::StoC::GenericModifier* pack
* - recharge: Cooldown duration in milliseconds
*/
void CombatEventQueue::OnSkillRecharge(GW::Packet::StoC::SkillRecharge* packet) {
uint32_t now = GetTickCount();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());
// agent_id=who, value=skill_id, float_value=recharge_ms
PushEvent(RawCombatEvent(now, CombatEventTypes::SKILL_RECHARGE,
packet->agent_id, packet->skill_id, 0, static_cast<float>(packet->recharge)));
Expand All @@ -449,7 +492,8 @@ void CombatEventQueue::OnSkillRecharge(GW::Packet::StoC::SkillRecharge* packet)
* - skill_id: The skill that is now ready to use
*/
void CombatEventQueue::OnSkillRecharged(GW::Packet::StoC::SkillRecharged* packet) {
uint32_t now = GetTickCount();
if (!IsMapReady()) return;
uint32_t now = static_cast<uint32_t>(GetTickCount64());
// agent_id=who, value=skill_id
PushEvent(RawCombatEvent(now, CombatEventTypes::SKILL_RECHARGED,
packet->agent_id, packet->skill_id, 0, 0.0f));
Expand Down
Loading