362 lines
8.2 KiB
C++
362 lines
8.2 KiB
C++
#include "UnrealProject.h"
|
|
|
|
#if WITH_EDITOR
|
|
|
|
#include "Metrics.hpp"
|
|
//#include <append>
|
|
#include <timer>
|
|
|
|
#include <fstream>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <chrono>
|
|
#include <algorithm>
|
|
|
|
#if PLATFORM_SPECIFIC_WIN == 0
|
|
#include "AllowWindowsPlatformTypes.h"
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
using namespace jlib;
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
|
|
#include "Events.hpp"
|
|
|
|
#include <type_traits>
|
|
|
|
namespace Metrics
|
|
{
|
|
ofstream writeStream;
|
|
timer writeTime;
|
|
uint32_t lastWriteTime;
|
|
|
|
unordered_map<uint8_t, PlayerHandle*> 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<typename Event> 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<wchar_t, char>(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<char, wchar_t>(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 |