rott/kf_sources/Engine/Classes/GameStats.uc
2026-07-14 20:27:09 +07:00

395 lines
9.7 KiB
Ucode

// ====================================================================
// Class: Engine.GameStats
// Parent: Engine.Info
//
// the GameStats object is used to send individual stat events to the
// stats server. Each game should spawn a GameStats object if it
// wishes to have stat logging.
//
// ====================================================================
class GameStats extends Info
Native Config;
var FileLog TempLog;
var GameReplicationInfo GRI;
var bool bShowBots;
var string Tab;
/** create local stat logs */
var globalconfig bool bLocalLog;
/** filename to use, check GetLogFilename() for replacements */
var globalconfig string LogFileName;
native final function string GetStatsIdentifier( Controller C );
native final function string GetMapFileName(); // Returns the name of the current map
/////////////////////////////////////
// GameStats interface
function Init()
{
if (bLocalLog)
{
TempLog = spawn(class 'FileLog');
if (TempLog != None)
{
TempLog.OpenLog(GetLogFilename());
}
else {
Warn("Could not create output file");
}
}
}
function Shutdown()
{
if (TempLog!=None)
TempLog.Destroy();
}
function Logf(string LogString)
{
if (TempLog!=None)
TempLog.Logf(LogString);
}
/////////////////////////////////////
// Internals
event PostBeginPlay()
{
Super.PostBeginPlay();
Tab = Chr(9);
Init();
}
event Destroyed()
{
Shutdown();
Super.Destroyed();
}
function string TimeStamp()
{
local string seconds;
seconds = string(Level.TimeSeconds);
// Remove the centiseconds
if( InStr(seconds,".") != -1 )
seconds = Left( seconds, InStr(seconds,".") );
return seconds;
}
function string Header()
{
return TimeStamp()$Tab;
}
function String FullTimeDate() // Date/Time in MYSQL format
{
return Level.Year$"-"$Level.Month$"-"$Level.Day$" "$Level.Hour$":"$Level.Minute$":"$Level.Second;
}
function String TimeZone() // Timezone (offset) of game server's local time to GTM, e.g. -4 or +5
{
return "0"; // FIXME Jack - currently pretending GMT
}
function String MapName()
{
local string mapname;
mapname = GetMapFileName();
// Remove the file name extention .ut2
if( InStr(mapname,".ut2") != -1 )
mapname = Left( mapname, InStr(mapname,".ut2") );
ReplaceText(mapname, tab, "_");
return mapname;
}
// Stat Logging functions
function NewGame()
{
local string out, tmp;
local string ngTitle, ngAuthor, ngGameGameName;
local int i;
local mutator MyMutie;
local GameRules MyRules;
ngTitle = Level.Title; // Making local copies
ngAuthor = Level.Author;
ngGameGameName = Level.Game.GameName;
ReplaceText(ngTitle, tab, "_"); // Replacing tabs with _
ReplaceText(ngAuthor, tab, "_");
ReplaceText(ngGameGameName, tab, "_");
GRI = Level.Game.GameReplicationInfo;
out = Header()$"NG"$Tab; // "NewGame"
out $= FullTimeDate()$Tab; // Game server's local time
out $= TimeZone()$Tab; // Game server's time zone (offset to GMT)
out $= MapName()$Tab; // Map file name without map extention .ut2
out $= ngTitle$Tab;
out $= ngAuthor$Tab;
out $= Level.Game.Class$Tab;
out $= ngGameGameName;
tmp = "";
i = 0;
foreach AllActors(class'Mutator',MyMutie)
{
if (tmp != "")
tmp $= "|" $ MyMutie.Class;
else
tmp $= MyMutie.Class;
i++;
}
foreach AllActors(class'GameRules',MyRules)
{
if (tmp!="")
tmp $= "|"$MyRules.Class;
else
tmp $= MyRules.Class;
i++;
}
if (i>0)
{
ReplaceText(tmp, tab, "_");
out $= Tab $ "Mutators=" $ tmp;
}
Logf(out);
}
function ServerInfo()
{
local string out, flags;
local string siServerName, siAdminName, siAdminEmail;
local GameInfo.ServerResponseLine ServerState;
local int i;
siServerName = GRI.ServerName; // Making local copies
siAdminName = GRI.AdminName;
siAdminEmail = GRI.AdminEmail;
ReplaceText(siServerName, tab, "_");
ReplaceText(siAdminName, tab, "_");
ReplaceText(siAdminEmail, tab, "_");
out = Header() $ "SI" $ Tab; // "SeverInfo"
out $= siServerName $ Tab; // Server name
out $= TimeZone()$Tab; // Timezone
out $= siAdminName$Tab; // Admin name
out $= siAdminEmail$Tab; // Admin email
out $= Tab; // IP:port (filled in by Master Server)
flags = ""; // Server flag / key combos
Level.Game.GetServerDetails( ServerState );
for( i=0;i<ServerState.ServerInfo.Length;i++ )
flags $= "\\"$ServerState.ServerInfo[i].Key$"\\"$ServerState.ServerInfo[i].Value;
ReplaceText(flags, tab, "_");
out $= flags;
Logf(out);
}
function StartGame()
{
Logf( Header()$"SG" ); // "StartGame"
}
// Send stats for the end of the game
function EndGame(string Reason)
{
local string out;
local int i,j;
local array<PlayerReplicationInfo> PRIs;
local PlayerReplicationInfo PRI,t;
out = Header()$"EG"$Tab$Reason; // "EndGame"
// Quick cascade sort.
for (i=0;i<GRI.PRIArray.Length;i++)
{
PRI = GRI.PRIArray[i];
if ( !PRI.bOnlySpectator && !PRI.bBot )
{
PRIs.Length = PRIs.Length+1;
for (j=0;j<Pris.Length-1;j++)
{
if (PRIs[j].Score < PRI.Score ||
(PRIs[j].Score == PRI.Score && PRIs[j].Deaths > PRI.Deaths) )
{
t = PRIs[j];
PRIs[j] = PRI;
PRI = t;
}
}
PRIs[j] = PRI;
}
}
// Minimal scoreboard, shows Playernumbers in order of Score
for (i=0;i<PRIs.Length;i++)
out $= Tab$Controller(PRIs[i].Owner).PlayerNum;
Logf(out);
}
// Connect Events get fired every time a player connects to a server
function ConnectEvent(PlayerReplicationInfo Who)
{
local string out;
if ( Who.bBot || Who.bOnlySpectator ) // Spectators should never show up in stats!
return;
// C 0 11d8944d9e138a5aa688d503e0e4c3e0
out = Header()$"C"$Tab$Controller(Who.Owner).PlayerNum$Tab;
// Login identifier
out $= GetStatsIdentifier(Controller(Who.Owner));
Logf(out);
}
// Connect Events get fired every time a player connects or leaves from a server
function DisconnectEvent(PlayerReplicationInfo Who)
{
local string out;
if ( Who.bBot || Who.bOnlySpectator ) // Spectators should never show up in stats!
return;
// D 0
out = Header()$"D"$Tab$Controller(Who.Owner).PlayerNum; //"Disconnect"
Logf(out);
}
// Scoring Events occur when a player's score changes
function ScoreEvent(PlayerReplicationInfo Who, float Points, string Desc)
{
if ( Who.bBot || Who.bOnlySpectator ) // Just to be totally safe Spectators sends nothing
return;
Logf( Header()$"S"$Tab$Controller(Who.Owner).PlayerNum$Tab$Points$Tab$Desc ); //"Score"
}
function TeamScoreEvent(int Team, float Points, string Desc)
{
Logf( Header()$"T"$Tab$Team$Tab$Points$Tab$Desc ); //"TeamScore"
}
// KillEvents occur when a player kills, is killed, suicides
function KillEvent(string Killtype, PlayerReplicationInfo Killer, PlayerReplicationInfo Victim, class<DamageType> Damage)
{
local string out;
if ( Victim.bBot || Victim.bOnlySpectator || ((Killer != None) && Killer.bBot) )
return;
out = Header()$Killtype$Tab;
// KillerNumber and KillerDamagetype
if (Killer!=None)
{
out $= Controller(Killer.Owner).PlayerNum$Tab;
// KillerWeapon no longer used, using damagetype
out $= GetItemName(string(Damage))$Tab;
}
else
out $= "-1"$Tab$GetItemName(string(Damage))$Tab; // No PlayerNum -> -1, Environment "deaths"
// VictimNumber and VictimWeapon
out $= Controller(Victim.Owner).PlayerNum$Tab$GetItemName(string(Controller(Victim.Owner).GetLastWeapon()));
// Type killers tracked as player event (redundant Typing, removed from kill line)
if ( PlayerController(Victim.Owner)!= None && PlayerController(Victim.Owner).bIsTyping)
{
if ( PlayerController(Killer.Owner) != PlayerController(Victim.Owner) )
SpecialEvent(Killer, "type_kill"); // Killer killed typing victim
}
Logf(out);
}
// Special Events are everything else regarding the player
function SpecialEvent(PlayerReplicationInfo Who, string Desc)
{
local string out;
if (Who != None)
{
if ( Who.bBot || Who.bOnlySpectator ) // Avoid spectator suicide on console "type_kill"
return;
out = string(Controller(Who.Owner).PlayerNum);
}
else
out = "-1";
Logf( Header()$"P"$Tab$out$Tab$Desc ); //"PSpecial"
}
// Special events regarding the game
function GameEvent(string GEvent, string Desc, PlayerReplicationInfo Who)
{
local string out, geDesc;
if (Who != None)
{
if ( Who.bBot || Who.bOnlySpectator ) // Specator could cause NameChange, TeamChange! No longer.
return;
out = string(Controller(Who.Owner).PlayerNum);
}
else
out = "-1";
geDesc = Desc;
ReplaceText(geDesc, tab, "_"); // geDesc, can be the nickname!
Logf( Header()$"G"$Tab$GEvent$Tab$out$Tab$geDesc ); //"GSpecial"
}
/**
return the filename to use for the log file. The following formatting rules are accepted:
%P server port
%Y year
%M month
%D day
%H hour
%I minute
%S second
%W day of the week
*/
function string GetLogFilename()
{
local string result;
result = LogFileName;
ReplaceText(result, "%P", string(Level.Game.GetServerPort()));
ReplaceText(result, "%N", Level.Game.GameReplicationInfo.ServerName);
ReplaceText(result, "%Y", Right("0000"$string(Level.Year), 4));
ReplaceText(result, "%M", Right("00"$string(Level.Month), 2));
ReplaceText(result, "%D", Right("00"$string(Level.Day), 2));
ReplaceText(result, "%H", Right("00"$string(Level.Hour), 2));
ReplaceText(result, "%I", Right("00"$string(Level.Minute), 2));
ReplaceText(result, "%W", Right("0"$string(Level.DayOfWeek), 1));
ReplaceText(result, "%S", Right("00"$string(Level.Second), 2));
return result;
}
defaultproperties
{
bLocalLog=false
LogFileName="Stats_%P_%Y_%M_%D_%H_%I_%S"
}