feat: add persistent combat log window (/combatlog or /cl)

Stores up to 500 combat events in a rolling deque alongside the existing
floating combat text. Events are populated via the existing addCombatText()
call site, resolving attacker/target names from the entity manager and
player name cache at event time.

- CombatLogEntry struct in spell_defines.hpp (type, amount, spellId,
  isPlayerSource, timestamp, sourceName, targetName)
- getCombatLog() / clearCombatLog() accessors on GameHandler
- renderCombatLog() in GameScreen: scrollable two-column table (Time +
  Event), color-coded by event category, with Damage/Healing/Misc filter
  checkboxes, auto-scroll toggle, and Clear button
- /combatlog (/cl) chat command toggles the window
This commit is contained in:
Kelsi 2026-03-12 11:00:10 -07:00
parent 36d40905e1
commit 661f7e3e8d
5 changed files with 248 additions and 1 deletions

View file

@ -536,6 +536,10 @@ public:
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
void updateCombatText(float deltaTime);
// Combat log (persistent rolling history, max MAX_COMBAT_LOG entries)
const std::deque<CombatLogEntry>& getCombatLog() const { return combatLog_; }
void clearCombatLog() { combatLog_.clear(); }
// Threat
struct ThreatEntry {
uint64_t victimGuid = 0;
@ -2149,6 +2153,8 @@ private:
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
std::unordered_set<uint64_t> hostileAttackers_;
std::vector<CombatTextEntry> combatText;
static constexpr size_t MAX_COMBAT_LOG = 500;
std::deque<CombatLogEntry> combatLog_;
// unitGuid → sorted threat list (descending by threat value)
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;

View file

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <ctime>
#include <string>
#include <vector>
@ -63,6 +64,19 @@ struct CombatTextEntry {
bool isExpired() const { return age >= LIFETIME; }
};
/**
* Persistent combat log entry (stored in a rolling deque, survives beyond floating-text lifetime)
*/
struct CombatLogEntry {
CombatTextEntry::Type type = CombatTextEntry::MELEE_DAMAGE;
int32_t amount = 0;
uint32_t spellId = 0;
bool isPlayerSource = false;
time_t timestamp = 0; // Wall-clock time (std::time(nullptr))
std::string sourceName; // Resolved display name of attacker/caster
std::string targetName; // Resolved display name of victim/target
};
/**
* Spell cooldown entry received from server
*/

View file

@ -400,6 +400,10 @@ private:
bool showWhoWindow_ = false;
void renderWhoWindow(game::GameHandler& gameHandler);
// Combat Log window
bool showCombatLog_ = false;
void renderCombatLog(game::GameHandler& gameHandler);
// Instance Lockouts window
bool showInstanceLockouts_ = false;