395 lines
9.7 KiB
Ucode
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"
|
|
}
|