diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index 2e16e64be..8365e6989 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -134,6 +134,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::HTTP].log_to_gmsay = static_cast(Logs::General); log_settings[Logs::ChecksumVerification].log_to_console = static_cast(Logs::General); log_settings[Logs::ChecksumVerification].log_to_gmsay = static_cast(Logs::General); + log_settings[Logs::CombatRecord].log_to_gmsay = static_cast(Logs::General); /** * RFC 5424 diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 5ec265dfa..b9b8789c6 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -128,6 +128,7 @@ namespace Logs { HTTP, Saylink, ChecksumVerification, + CombatRecord, MaxCategoryID /* Don't Remove this */ }; @@ -214,6 +215,7 @@ namespace Logs { "HTTP", "Saylink", "ChecksumVerification", + "CombatRecord", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 84e978dfb..ec78a49fe 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -706,6 +706,16 @@ OutF(LogSys, Logs::Detail, Logs::ChecksumVerification, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogCombatRecord(message, ...) do {\ + if (LogSys.log_settings[Logs::CombatRecord].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogCombatRecordDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::CombatRecord].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::CombatRecord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -1138,6 +1148,12 @@ #define LogChecksumVerificationDetail(message, ...) do {\ } while (0) +#define LogCombatRecord(message, ...) do {\ +} while (0) + +#define LogCombatRecordDetail(message, ...) do {\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ } while (0) diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index ee8de4655..256cb5706 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -19,6 +19,7 @@ SET(zone_sources client_mods.cpp client_packet.cpp client_process.cpp + combat_record.cpp command.cpp corpse.cpp data_bucket.cpp @@ -162,7 +163,7 @@ SET(zone_sources zone_reload.cpp zone_store.cpp zoning.cpp - ) +) SET(zone_headers aa.h @@ -179,6 +180,7 @@ SET(zone_headers cheat_manager.h client.h client_packet.h + combat_record.h command.h common.h corpse.h @@ -283,7 +285,7 @@ SET(zone_headers zonedump.h zone_reload.h zone_store.h - ) +) ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) diff --git a/zone/attack.cpp b/zone/attack.cpp index 6eff80ded..8eca03213 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2724,6 +2724,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy static_cast(attack_skill) ); parse->EventNPC(EVENT_DEATH_COMPLETE, this, oos, export_string, 0); + combat_record.Stop(); /* Zone controller process EVENT_DEATH_ZONE (Death events) */ if (RuleB(Zone, UseZoneController)) { diff --git a/zone/combat_record.cpp b/zone/combat_record.cpp new file mode 100644 index 000000000..af55dba36 --- /dev/null +++ b/zone/combat_record.cpp @@ -0,0 +1,60 @@ +#include "combat_record.h" +#include "../common/eqemu_logsys.h" +#include "../common/string_util.h" + +void CombatRecord::Start(std::string in_mob_name) +{ + start_time = std::time(nullptr); + end_time = 0; + damage_received = 0; + heal_received = 0; + mob_name = in_mob_name; +} + +void CombatRecord::Stop() +{ + end_time = std::time(nullptr); + + double time_in_combat = TimeInCombat(); + + LogCombatRecord( + "[Summary] Mob [{}] [Received] DPS [{:.0f}] Heal/s [{:.0f}] Duration [{}] ({}s)", + mob_name, + time_in_combat > 0 ? (damage_received / time_in_combat) : damage_received, + time_in_combat > 0 ? (heal_received / time_in_combat) : heal_received, + time_in_combat > 0 ? ConvertSecondsToTime(time_in_combat) : "", + time_in_combat + ); +} + +bool CombatRecord::InCombat() +{ + return start_time > 0; +} + +void CombatRecord::ProcessHPEvent(int hp, int current_hp) +{ + // damage + if (hp < current_hp) { + damage_received = damage_received + abs(current_hp - hp); + } + + // heal + if (hp > current_hp && current_hp > 0) { + heal_received = heal_received + abs(current_hp - hp); + } + + LogCombatRecordDetail( + "damage_received [{}] heal_received [{}] current_hp [{}] hp [{}] calc [{}]", + damage_received, + heal_received, + current_hp, + hp, + abs(current_hp - hp) + ); +} + +double CombatRecord::TimeInCombat() const +{ + return difftime(end_time, start_time); +} diff --git a/zone/combat_record.h b/zone/combat_record.h new file mode 100644 index 000000000..a0ad0c14e --- /dev/null +++ b/zone/combat_record.h @@ -0,0 +1,23 @@ +#ifndef EQEMU_COMBAT_RECORD_H +#define EQEMU_COMBAT_RECORD_H + +#include +#include +#include "../common/types.h" + +class CombatRecord { +public: + void Start(std::string in_mob_name); + void Stop(); + bool InCombat(); + void ProcessHPEvent(int hp, int current_hp); + double TimeInCombat() const; +private: + std::string mob_name; + time_t start_time = 0; + time_t end_time = 0; + int64 damage_received = 0; + int64 heal_received = 0; +}; + +#endif //EQEMU_COMBAT_RECORD_H diff --git a/zone/mob.h b/zone/mob.h index 25b6c870b..00b8c1be6 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -28,6 +28,7 @@ #include "aa.h" #include "../common/light_source.h" #include "../common/emu_constants.h" +#include "combat_record.h" #include #include #include @@ -494,7 +495,7 @@ public: virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) = 0; virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) = 0; - inline virtual void SetHP(int32 hp) { if (hp >= max_hp) current_hp = max_hp; else current_hp = hp;} + virtual void SetHP(int32 hp); bool ChangeHP(Mob* other, int32 amount, uint16 spell_id = 0, int8 buffslot = -1, bool iBuffTic = false); inline void SetOOCRegen(int32 newoocregen) {ooc_regen = newoocregen;} virtual void Heal(); @@ -1643,6 +1644,8 @@ protected: bool degenerating_effects; // true if we have a buff that needs to be recalced every tick bool spawned_in_water; + CombatRecord combat_record{}; + public: bool GetWasSpawnedInWater() const; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 4373e4bd1..8b9fd08db 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1955,6 +1955,8 @@ void Mob::AI_Event_Engaged(Mob *attacker, bool yell_for_help) if (emoteid != 0) { CastToNPC()->DoNPCEmote(ENTERCOMBAT, emoteid); } + std::string mob_name = GetCleanName(); + combat_record.Start(mob_name); CastToNPC()->SetCombatEvent(true); } } @@ -1976,19 +1978,18 @@ void Mob::AI_Event_NoLongerEngaged() { StopNavigation(); ClearRampage(); - if(IsNPC()) - { + if (IsNPC()) { SetPrimaryAggro(false); SetAssistAggro(false); - if(CastToNPC()->GetCombatEvent() && GetHP() > 0) - { - if(entity_list.GetNPCByID(GetID())) - { - uint16 emoteid = CastToNPC()->GetEmoteID(); - parse->EventNPC(EVENT_COMBAT, CastToNPC(), nullptr, "0", 0); - if(emoteid != 0) - CastToNPC()->DoNPCEmote(LEAVECOMBAT,emoteid); - CastToNPC()->SetCombatEvent(false); + if (CastToNPC()->GetCombatEvent() && GetHP() > 0) { + if (entity_list.GetNPCByID(this->GetID())) { + uint16 emoteid = CastToNPC()->GetEmoteID(); + parse->EventNPC(EVENT_COMBAT, CastToNPC(), nullptr, "0", 0); + if (emoteid != 0) { + CastToNPC()->DoNPCEmote(LEAVECOMBAT, emoteid); + } + combat_record.Stop(); + CastToNPC()->SetCombatEvent(false); } } } diff --git a/zone/spells.cpp b/zone/spells.cpp index 4539642c3..fd58b42ef 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -102,6 +102,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "mob_movement_manager.h" #include "client.h" +#include "mob.h" extern Zone* zone; @@ -6773,3 +6774,17 @@ bool Mob::IsFromTriggeredSpell(CastingSlot slot, uint32 item_slot) { } return false; } + +void Mob::SetHP(int32 hp) +{ + if (hp >= max_hp) { + current_hp = max_hp; + return; + } + + if (combat_record.InCombat()) { + combat_record.ProcessHPEvent(hp, current_hp); + } + + current_hp = hp; +}