Add race result logger (existing patch from stk-server-docker)
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
#include "network/protocols/server_lobby.hpp"
|
#include "network/protocols/server_lobby.hpp"
|
||||||
|
|
||||||
#include "addons/addon.hpp"
|
#include "addons/addon.hpp"
|
||||||
|
#include "network/race_result_logger.hpp"
|
||||||
#include "config/user_config.hpp"
|
#include "config/user_config.hpp"
|
||||||
#include "items/network_item_manager.hpp"
|
#include "items/network_item_manager.hpp"
|
||||||
#include "items/powerup_manager.hpp"
|
#include "items/powerup_manager.hpp"
|
||||||
@@ -2297,6 +2298,7 @@ void ServerLobby::checkRaceFinished()
|
|||||||
if (!RaceEventManager::get()->isRaceOver()) return;
|
if (!RaceEventManager::get()->isRaceOver()) return;
|
||||||
|
|
||||||
Log::info("ServerLobby", "The game is considered finished.");
|
Log::info("ServerLobby", "The game is considered finished.");
|
||||||
|
RaceResultLogger::logResult();
|
||||||
// notify the network world that it is stopped
|
// notify the network world that it is stopped
|
||||||
RaceEventManager::get()->stop();
|
RaceEventManager::get()->stop();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// SuperTuxKart dedicated-server PATCH (not part of upstream STK).
|
||||||
|
//
|
||||||
|
// Captures the per-player classification of a finished networked game and
|
||||||
|
// appends it as one JSON object (newline-delimited) to $STK_RESULTS_FILE.
|
||||||
|
// A separate "result-shipper" sidecar tails that file and POSTs each line to
|
||||||
|
// the stats dashboard. Writing to a local file (rather than doing network I/O
|
||||||
|
// here) keeps the game loop free of blocking/error-prone calls.
|
||||||
|
//
|
||||||
|
// Called from ServerLobby::checkRaceFinished() while World/RaceManager are
|
||||||
|
// still alive (see stk-patches/0001-race-result-hook.patch).
|
||||||
|
|
||||||
|
#include "network/race_result_logger.hpp"
|
||||||
|
|
||||||
|
#include "config/stk_config.hpp"
|
||||||
|
#include "karts/abstract_kart.hpp"
|
||||||
|
#include "modes/linear_world.hpp"
|
||||||
|
#include "modes/world.hpp"
|
||||||
|
#include "network/remote_kart_info.hpp"
|
||||||
|
#include "network/server_config.hpp"
|
||||||
|
#include "race/race_manager.hpp"
|
||||||
|
#include "utils/log.hpp"
|
||||||
|
#include "utils/string_utils.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <ctime>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string jsonEscape(const std::string& in)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
out.reserve(in.size() + 8);
|
||||||
|
for (unsigned char c : in)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '"': out += "\\\""; break;
|
||||||
|
case '\\': out += "\\\\"; break;
|
||||||
|
case '\n': out += "\\n"; break;
|
||||||
|
case '\r': out += "\\r"; break;
|
||||||
|
case '\t': out += "\\t"; break;
|
||||||
|
default:
|
||||||
|
if (c < 0x20)
|
||||||
|
{
|
||||||
|
char buf[8];
|
||||||
|
std::snprintf(buf, sizeof(buf), "\\u%04x", (int)c);
|
||||||
|
out += buf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out += (char)c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void RaceResultLogger::logResult()
|
||||||
|
{
|
||||||
|
const char* path = std::getenv("STK_RESULTS_FILE");
|
||||||
|
if (path == NULL || path[0] == '\0')
|
||||||
|
return; // feature disabled -> behave like stock STK
|
||||||
|
|
||||||
|
World* world = World::getWorld();
|
||||||
|
RaceManager* rm = RaceManager::get();
|
||||||
|
if (world == NULL || rm == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LinearWorld* lw = rm->modeHasLaps()
|
||||||
|
? static_cast<LinearWorld*>(world) : NULL;
|
||||||
|
|
||||||
|
std::ostringstream o;
|
||||||
|
o << "{";
|
||||||
|
o << "\"schema\":1";
|
||||||
|
o << ",\"ts\":" << (long long)std::time(NULL);
|
||||||
|
o << ",\"server\":\""
|
||||||
|
<< jsonEscape((std::string)ServerConfig::m_server_name) << "\"";
|
||||||
|
o << ",\"track\":\"" << jsonEscape(rm->getTrackName()) << "\"";
|
||||||
|
o << ",\"mode\":\"" << jsonEscape(rm->getMinorModeName()) << "\"";
|
||||||
|
o << ",\"difficulty\":" << (int)rm->getDifficulty();
|
||||||
|
o << ",\"laps\":" << rm->getNumLaps();
|
||||||
|
o << ",\"reverse\":" << (rm->getReverseTrack() ? "true" : "false");
|
||||||
|
|
||||||
|
if (lw != NULL)
|
||||||
|
{
|
||||||
|
int flt = lw->getFastestLapTicks();
|
||||||
|
long long flms = (flt >= 0)
|
||||||
|
? (long long)(stk_config->ticks2Time(flt) * 1000.0f) : -1;
|
||||||
|
o << ",\"fastest_lap_ms\":" << flms;
|
||||||
|
o << ",\"fastest_lap_by\":\""
|
||||||
|
<< jsonEscape(StringUtils::wideToUtf8(lw->getFastestLapKartName()))
|
||||||
|
<< "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
o << ",\"players\":[";
|
||||||
|
const unsigned n = rm->getNumPlayers();
|
||||||
|
bool first = true;
|
||||||
|
for (unsigned i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
const RemoteKartInfo& ki = rm->getKartInfo(i);
|
||||||
|
AbstractKart* kart = world->getKart(i);
|
||||||
|
if (!first) o << ",";
|
||||||
|
first = false;
|
||||||
|
o << "{";
|
||||||
|
o << "\"name\":\""
|
||||||
|
<< jsonEscape(StringUtils::wideToUtf8(ki.getPlayerName())) << "\"";
|
||||||
|
o << ",\"online_id\":" << (unsigned)ki.getOnlineId();
|
||||||
|
o << ",\"country\":\"" << jsonEscape(ki.getCountryCode()) << "\"";
|
||||||
|
o << ",\"kart\":\"" << jsonEscape(ki.getKartName()) << "\"";
|
||||||
|
o << ",\"position\":" << (kart != NULL ? kart->getPosition() : -1);
|
||||||
|
o << ",\"time_ms\":"
|
||||||
|
<< (long long)(rm->getKartRaceTime(i) * 1000.0f);
|
||||||
|
o << ",\"score\":" << rm->getKartScore(i);
|
||||||
|
o << ",\"eliminated\":"
|
||||||
|
<< ((kart != NULL && kart->isEliminated()) ? "true" : "false");
|
||||||
|
o << "}";
|
||||||
|
}
|
||||||
|
o << "]}";
|
||||||
|
|
||||||
|
std::ofstream f(path, std::ios::out | std::ios::app);
|
||||||
|
if (!f.is_open())
|
||||||
|
{
|
||||||
|
Log::warn("RaceResultLogger",
|
||||||
|
"Could not open results file '%s'", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
f << o.str() << "\n";
|
||||||
|
f.flush();
|
||||||
|
Log::info("RaceResultLogger",
|
||||||
|
"Wrote race result: track=%s mode=%s players=%u",
|
||||||
|
rm->getTrackName().c_str(), rm->getMinorModeName().c_str(), n);
|
||||||
|
} // logResult
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// SuperTuxKart dedicated-server PATCH (not part of upstream STK).
|
||||||
|
// Emits one JSON line per finished game so an external service can build
|
||||||
|
// leaderboards / history / records. See apps/stk-server/stk-patches/README.md.
|
||||||
|
#ifndef HEADER_RACE_RESULT_LOGGER_HPP
|
||||||
|
#define HEADER_RACE_RESULT_LOGGER_HPP
|
||||||
|
|
||||||
|
namespace RaceResultLogger
|
||||||
|
{
|
||||||
|
/** Append a single JSON object describing the just-finished game to the
|
||||||
|
* file named by the STK_RESULTS_FILE environment variable. No-op when the
|
||||||
|
* variable is unset/empty, so an unpatched-feeling default is preserved. */
|
||||||
|
void logResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user