#include "UnrealProject.h" #if WITH_EDITOR #include "Metrics.hpp" //#include #include #include #include #include #include #include #if PLATFORM_SPECIFIC_WIN == 0 #include "AllowWindowsPlatformTypes.h" #include #endif using namespace jlib; using namespace std; using namespace std::chrono; #include "Events.hpp" #include namespace Metrics { ofstream writeStream; timer writeTime; uint32_t lastWriteTime; unordered_map players; uint32_t CurrentTime() { return uint32_t(writeTime.milliseconds()); } uint32_t TimeOffset() { const uint32_t currentTime = uint32_t(writeTime.milliseconds()); const uint32_t offset = currentTime - lastWriteTime; lastWriteTime = currentTime; return offset; } wstring FormatNumber(uint16_t number, size_t desiredLength) { wstring result = wstring() + number; while (result.size() < desiredLength) result = wstring(L"0") + result; return result; } template void WriteEvent(const Event& event) { writeStream.write((const char*)&event, streamsize(sizeof(Event))); } void WriteBinary(void* addr, size_t size) { writeStream.write((const char*)addr, size); } void Clear() { for (auto iter = players.begin(); iter != players.end(); iter++) delete iter->second; players.clear(); } } bool Metrics::StartSession(const wstring& filePath) { EndSession(); time_t timePoint = system_clock::to_time_t(std::chrono::system_clock::now()); const char* timeData = ctime(&timePoint); wstring dateTime = convert_cstring_type(timeData); wstring dateTimeFiltered; for (size_t i = 0; i < dateTime.size(); i++) { if (dateTime[i] == L'\n' || dateTime[i] == L'\r') continue; dateTimeFiltered += dateTime[i]; } dateTime = dateTimeFiltered; wstring sessionName = L"MetricsSession"; #if PLATFORM_SPECIFIC_WIN == 0 sessionName.clear(); sessionName.resize(256); DWORD length = 256; GetComputerName(&sessionName[0], &length); sessionName.resize(length); wstring sessionNameFiltered; for (size_t i = 0; i < sessionName.size(); i++) { wchar_t character = sessionName[i]; if ((character >= L'0' && character <= L'9') || (character >= L'a' && character <= L'z') || (character >= L'A' && character <= L'Z') || character == L'-' || character == L'_') sessionNameFiltered += character; } sessionName = sessionNameFiltered; #endif wstring fileName = sessionName + L" - " + dateTime + L".hms"; replace(fileName.begin(), fileName.end(), L':', L'-'); writeStream.open(convert_string_type(filePath + fileName), ios::out | ios::binary | ios::trunc); writeTime.restart(); lastWriteTime = 0; // Write the header writeStream.write("HMS", 3); return writeStream.is_open(); } bool Metrics::EndSession() { if (writeStream.is_open()) { WriteEvent(SessionEnd(TimeOffset())); Clear(); writeStream.close(); return true; } Clear(); return false; } struct Metrics::PlayerHandleSet { static void Enable(PlayerHandle& handle, uint8_t id, uint8_t team) { handle.m_id = id; handle.m_isValid = true; handle.m_team = team; } }; void Metrics::SetMapData(float minX, float maxX, float minY, float maxY) { if (!writeStream.is_open()) return; WriteEvent(MapData(minX, maxX, minY, maxY)); } void Metrics::SetMapImage(uint8_t* ptr, uint32_t width, uint32_t height) { if (!writeStream.is_open()) return; WriteEvent(MapImage(width, height)); WriteBinary(ptr, width * height * 4); } Metrics::PlayerHandle::PlayerHandle() : m_isValid(false), m_id(-1), m_team(-1), m_updateTime(0), m_alive(false), m_health(INT_MAX), m_mana(INT_MAX), m_maxHealth(INT_MAX), m_maxMana(INT_MAX), m_level(255), m_playerX(FLT_MAX), m_playerY(FLT_MAX) { } void Metrics::PlayerHandle::OnPlayerSpawn(float playerX, float playerY) { if (!writeStream.is_open() || !m_isValid) return; if (m_alive) return; m_alive = true; m_playerX = playerX; m_playerY = playerY; WriteEvent(PlayerSpawn(TimeOffset(), m_id, playerX, playerY)); } void Metrics::PlayerHandle::OnPlayerUpdate(float playerX, float playerY) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; // No need to write an event if the player hasnt moved if (fabsf(playerX - m_playerX) < 0.01f && fabsf(playerY - m_playerY) < 0.01f) return; // Only update position 30 times per second const uint32_t currentTime = CurrentTime(); if (currentTime - m_updateTime < 33) return; m_updateTime = currentTime; m_playerX = playerX; m_playerY = playerY; WriteEvent(PlayerUpdate(TimeOffset(), m_id, playerX, playerY)); } void Metrics::PlayerHandle::OnPlayerDie(uint8_t source) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; m_alive = false; WriteEvent(PlayerDie(TimeOffset(), m_id, source)); } void Metrics::PlayerHandle::OnPlayerCast(uint32_t ability) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; WriteEvent(PlayerCast(TimeOffset(), m_id, ability)); } void Metrics::PlayerHandle::OnPlayerHealth(int32_t health) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; // No need to write an event if the player hasnt lost/gained health if (m_health == health) return; m_health = health; WriteEvent(PlayerHealth(TimeOffset(), m_id, health)); } void Metrics::PlayerHandle::OnPlayerMana(int32_t mana) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; // No need to write an event if the player hasnt lost/gained mana if (m_mana == mana) return; m_mana = mana; WriteEvent(PlayerMana(TimeOffset(), m_id, mana)); } void Metrics::PlayerHandle::OnPlayerMaxHealth(int32_t maxHealth) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; // No need to write an event if the player hasnt changed max health if (m_maxHealth == maxHealth) return; m_maxHealth = maxHealth; WriteEvent(PlayerMaxHealth(TimeOffset(), m_id, maxHealth)); } void Metrics::PlayerHandle::OnPlayerMaxMana(int32_t maxMana) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; // No need to write an event if the player hasnt changed max mana if (m_maxMana == maxMana) return; m_maxMana = maxMana; WriteEvent(PlayerMaxMana(TimeOffset(), m_id, maxMana)); } void Metrics::PlayerHandle::OnPlayerDealDamage(uint32_t ability, int32_t damage) { if (!writeStream.is_open() || !m_isValid) return; if (!m_alive) return; WriteEvent(PlayerDealDamage(TimeOffset(), m_id, ability, damage)); } void Metrics::PlayerHandle::OnPlayerLevel(uint8_t level) { if (!writeStream.is_open() || !m_isValid) return; if (m_level == level) return; m_level = level; WriteEvent(PlayerLevel(TimeOffset(), m_id, level)); } bool Metrics::PlayerHandle::IsValid() const { return m_isValid; } uint8_t Metrics::PlayerHandle::Id() const { return m_id; } uint8_t Metrics::PlayerHandle::Team() const { return m_team; } Metrics::PlayerHandle& Metrics::RegisterPlayer(const wstring& name, uint8_t team) { static PlayerHandle errorHandle; if (!writeStream.is_open()) return errorHandle; const uint8_t id = uint8_t(players.size() + 1); PlayerHandle* newHandle = new PlayerHandle(); PlayerHandleSet::Enable(*newHandle, id, team); players.emplace(id, newHandle); wstring copy = name; if (copy.size() > 128) copy.resize(128); else if (copy.empty()) copy = L"UNKNOWN PLAYER"; WriteEvent(PlayerName(id, (uint16_t)copy.size())); WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type)); WriteEvent(PlayerTeam(id, team)); return *newHandle; } void Metrics::RegisterAbility(uint32_t ability, const std::wstring& name) { if (!writeStream.is_open()) return; wstring copy = name; if (copy.size() > 128) copy.resize(128); else if (copy.empty()) copy = L"UNKNOWN ABILITY"; WriteEvent(AbilityName(ability, (uint16_t)copy.size())); WriteBinary((void*)copy.data(), copy.size() * sizeof(wstring::value_type)); } #endif