mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
306 lines
No EOL
7.7 KiB
C
306 lines
No EOL
7.7 KiB
C
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2024 by AJ "Tyron" Martinez.
|
|
// Copyright (C) 2024 by Kart Krew
|
|
// Copyright (C) 2020 by Sonic Team Junior.
|
|
// Copyright (C) 2000 by DooM Legacy Team.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file k_serverstats.c
|
|
/// \brief implements methods for serverside stat tracking.
|
|
|
|
#include "doomtype.h"
|
|
#include "d_main.h" // pandf
|
|
#include "byteptr.h" // READ/WRITE macros
|
|
#include "p_saveg.h" // savebuffer_t
|
|
#include "m_misc.h" //FIL_WriteFile()
|
|
#include "k_serverstats.h"
|
|
#include "z_zone.h"
|
|
#include "time.h"
|
|
|
|
static serverplayer_t *trackedList;
|
|
static size_t numtracked = 0;
|
|
static size_t numallocated = 0;
|
|
static boolean initialized = false;
|
|
|
|
UINT16 guestpwr[PWRLV_NUMTYPES]; // All-zero power level to reference for guests
|
|
|
|
static void SV_InitializeStats(void)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
numallocated = 8;
|
|
trackedList = Z_Calloc(
|
|
sizeof(serverplayer_t) * numallocated,
|
|
PU_STATIC,
|
|
&trackedList
|
|
);
|
|
|
|
if (trackedList == NULL)
|
|
{
|
|
I_Error("Not enough memory for server stats\n");
|
|
}
|
|
|
|
initialized = true;
|
|
}
|
|
}
|
|
|
|
static void SV_ExpandStats(size_t needed)
|
|
{
|
|
I_Assert(trackedList != NULL);
|
|
|
|
while (numallocated < needed)
|
|
{
|
|
numallocated *= 2;
|
|
trackedList = Z_Realloc(
|
|
trackedList,
|
|
sizeof(serverplayer_t) * numallocated,
|
|
PU_STATIC,
|
|
&trackedList
|
|
);
|
|
|
|
if (trackedList == NULL)
|
|
{
|
|
I_Error("Not enough memory for server stats\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Read stats file to trackedList for ingame use
|
|
void SV_LoadStats(void)
|
|
{
|
|
const size_t headerlen = strlen(SERVERSTATSHEADER);
|
|
savebuffer_t save = {0};
|
|
unsigned int i, j;
|
|
|
|
if (!server)
|
|
return;
|
|
|
|
if (P_SaveBufferFromFile(&save, va(pandf, srb2home, SERVERSTATSFILE)) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SV_InitializeStats();
|
|
|
|
if (strncmp(SERVERSTATSHEADER, (const char *)save.buffer, headerlen))
|
|
{
|
|
const char *gdfolder = "the Ring Racers folder";
|
|
if (strcmp(srb2home,"."))
|
|
gdfolder = srb2home;
|
|
|
|
P_SaveBufferFree(&save);
|
|
I_Error("Not a valid server stats file.\nDelete %s (maybe in %s) and try again.", SERVERSTATSFILE, gdfolder);
|
|
}
|
|
|
|
save.p += headerlen;
|
|
UINT8 version = READUINT8(save.p);
|
|
if (version > SERVERSTATSVER)
|
|
{
|
|
P_SaveBufferFree(&save);
|
|
I_Error("Existing %s is from the future! (expected %d, got %d)", SERVERSTATSFILE, SERVERSTATSVER, version);
|
|
}
|
|
else if (version < SERVERSTATSVER)
|
|
{
|
|
// We're converting - let's create a backup.
|
|
FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, SERVERSTATSFILE), save.buffer, save.size);
|
|
}
|
|
|
|
numtracked = READUINT32(save.p);
|
|
|
|
SV_ExpandStats(numtracked);
|
|
|
|
for(i = 0; i < numtracked; i++)
|
|
{
|
|
READMEM(save.p, trackedList[i].public_key, PUBKEYLENGTH);
|
|
READMEM(save.p, &trackedList[i].lastseen, sizeof(trackedList[i].lastseen));
|
|
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
|
{
|
|
trackedList[i].powerlevels[j] = READUINT16(save.p);
|
|
}
|
|
|
|
// Migration 1 -> 2: Add finishedrounds
|
|
if (version < 2)
|
|
trackedList[i].finishedrounds = 0;
|
|
else
|
|
trackedList[i].finishedrounds = READUINT32(save.p);
|
|
|
|
trackedList[i].hash = quickncasehash((char*)trackedList[i].public_key, PUBKEYLENGTH);
|
|
}
|
|
}
|
|
|
|
// Save trackedList to disc
|
|
void SV_SaveStats(void)
|
|
{
|
|
size_t length = 0;
|
|
const size_t headerlen = strlen(SERVERSTATSHEADER);
|
|
savebuffer_t save = {0};
|
|
unsigned int i, j;
|
|
|
|
if (!server)
|
|
return;
|
|
|
|
// header + version + numtracked + payload
|
|
if (P_SaveBufferAlloc(&save, headerlen + sizeof(UINT32) + sizeof(UINT8) + (numtracked * sizeof(serverplayer_t))) == false)
|
|
{
|
|
I_Error("No more free memory for saving server stats\n");
|
|
return;
|
|
}
|
|
|
|
// Add header.
|
|
WRITESTRINGN(save.p, SERVERSTATSHEADER, headerlen);
|
|
|
|
WRITEUINT8(save.p, SERVERSTATSVER);
|
|
|
|
WRITEUINT32(save.p, numtracked);
|
|
|
|
for(i = 0; i < numtracked; i++)
|
|
{
|
|
WRITEMEM(save.p, trackedList[i].public_key, PUBKEYLENGTH);
|
|
WRITEMEM(save.p, &trackedList[i].lastseen, sizeof(trackedList[i].lastseen));
|
|
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
|
{
|
|
WRITEUINT16(save.p, trackedList[i].powerlevels[j]);
|
|
}
|
|
WRITEUINT32(save.p, trackedList[i].finishedrounds);
|
|
}
|
|
|
|
length = save.p - save.buffer;
|
|
|
|
if (!FIL_WriteFile(va(pandf, srb2home, SERVERSTATSFILE), save.buffer, length))
|
|
{
|
|
P_SaveBufferFree(&save);
|
|
I_Error("Couldn't save server stats. Are you out of disk space / playing in a protected folder?");
|
|
}
|
|
P_SaveBufferFree(&save);
|
|
}
|
|
|
|
// New player, grab their stats from trackedList or initialize new ones if they're new
|
|
serverplayer_t *SV_GetStatsByKey(uint8_t *key)
|
|
{
|
|
UINT32 j, hash;
|
|
|
|
SV_InitializeStats();
|
|
|
|
hash = quickncasehash((char*)key, PUBKEYLENGTH);
|
|
|
|
// Existing record?
|
|
for(j = 0; j < numtracked; j++)
|
|
{
|
|
if (hash != trackedList[j].hash) // Not crypto magic, just an early out with a faster comparison
|
|
continue;
|
|
if (memcmp(trackedList[j].public_key, key, PUBKEYLENGTH) == 0)
|
|
return &trackedList[j];
|
|
}
|
|
|
|
// Untracked below this point, make a new record
|
|
SV_ExpandStats(numtracked+1);
|
|
|
|
// Default stats
|
|
// (NB: This will make a GUEST record if someone tries to retrieve GUEST stats, because
|
|
// at the very least we should try to provide other codepaths the right _data type_,
|
|
// but it will not be written back.)
|
|
trackedList[numtracked].lastseen = time(NULL);
|
|
memcpy(&trackedList[numtracked].public_key, key, PUBKEYLENGTH);
|
|
for(j = 0; j < PWRLV_NUMTYPES; j++)
|
|
{
|
|
trackedList[numtracked].powerlevels[j] = PR_IsKeyGuest(key) ? 0 : PWRLVRECORD_START;
|
|
}
|
|
trackedList[numtracked].finishedrounds = 0;
|
|
trackedList[numtracked].hash = quickncasehash((char*)key, PUBKEYLENGTH);
|
|
|
|
numtracked++;
|
|
|
|
return &trackedList[numtracked - 1];
|
|
}
|
|
|
|
serverplayer_t *SV_GetStatsByPlayerIndex(UINT8 p)
|
|
{
|
|
return SV_GetStatsByKey(players[p].public_key);
|
|
}
|
|
|
|
serverplayer_t *SV_GetStats(player_t *player)
|
|
{
|
|
return SV_GetStatsByKey(player->public_key);
|
|
}
|
|
|
|
// Write clientpowerlevels and timestamps back to matching trackedList entries, then save trackedList to disk
|
|
// (NB: Stats changes can be made directly to trackedList through other paths, but will only write to disk here)
|
|
void SV_UpdateStats(void)
|
|
{
|
|
UINT32 i, j, hash;
|
|
|
|
if (!server)
|
|
return;
|
|
|
|
SV_InitializeStats();
|
|
|
|
for(i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
|
|
if (PR_IsKeyGuest(players[i].public_key))
|
|
continue;
|
|
|
|
hash = quickncasehash((char*)players[i].public_key, PUBKEYLENGTH);
|
|
|
|
for(j = 0; j < numtracked; j++)
|
|
{
|
|
if (hash != trackedList[j].hash) // Not crypto magic, just an early out with a faster comparison
|
|
continue;
|
|
if (memcmp(&trackedList[j].public_key, players[i].public_key, PUBKEYLENGTH) == 0)
|
|
{
|
|
trackedList[j].lastseen = time(NULL);
|
|
memcpy(&trackedList[j].powerlevels, clientpowerlevels[i], sizeof(trackedList[j].powerlevels));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// SV_RetrievePWR should always be called for a key before SV_UpdateStats runs,
|
|
// so this shouldn't be reachable.
|
|
}
|
|
|
|
SV_SaveStats();
|
|
}
|
|
|
|
void SV_BumpMatchStats(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
if (players[i].spectator)
|
|
continue;
|
|
if (PR_IsKeyGuest(players[i].public_key))
|
|
continue;
|
|
|
|
serverplayer_t *stat = SV_GetStatsByPlayerIndex(i);
|
|
|
|
// It should never be advantageous to idle, only count rounds where the player accomplishes something.
|
|
// If you NO CONTESTed, assume no participation...
|
|
boolean participated = !(players[i].pflags & PF_NOCONTEST);
|
|
|
|
if (gametyperules & GTR_CIRCUIT)
|
|
{
|
|
// ...unless you completed at least one lap...
|
|
if (players[i].laps > 1)
|
|
participated = true;
|
|
}
|
|
else if (gametyperules & GTR_POINTLIMIT)
|
|
{
|
|
// ...or scored at least 2 points.
|
|
if (players[i].roundscore > 1)
|
|
participated = true;
|
|
}
|
|
|
|
if (participated)
|
|
stat->finishedrounds++;
|
|
}
|
|
} |