warzone2100/src/multistat.c

328 lines
8.1 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2010 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* MultiStat.c
*
* Alex Lee , pumpkin studios, EIDOS
*
* load / update / store multiplayer statistics for league tables etc...
*/
#include "lib/framework/frame.h"
#include "lib/framework/file.h"
#include "lib/netplay/nettypes.h"
#include "main.h"
#include "mission.h" // for cheats
#include "multistat.h"
// ////////////////////////////////////////////////////////////////////////////
// STATS STUFF
// ////////////////////////////////////////////////////////////////////////////
static PLAYERSTATS playerStats[MAX_PLAYERS];
// ////////////////////////////////////////////////////////////////////////////
// Get Player's stats
PLAYERSTATS getMultiStats(UDWORD player)
{
return playerStats[player];
}
// ////////////////////////////////////////////////////////////////////////////
// Set Player's stats
// send stats to all players when bLocal is false
BOOL setMultiStats(SDWORD player, PLAYERSTATS plStats, BOOL bLocal)
{
uint32_t playerIndex = (uint32_t)player;
if (playerIndex >= MAX_PLAYERS)
{
return true;
}
// First copy over the data into our local array
memcpy(&playerStats[playerIndex], &plStats, sizeof(plStats));
if (!bLocal)
{
// Now send it to all other players
NETbeginEncode(NETbroadcastQueue(), NET_PLAYER_STATS);
// Send the ID of the player's stats we're updating
NETuint32_t(&playerIndex);
// Send over the actual stats
NETuint32_t(&playerStats[playerIndex].played);
NETuint32_t(&playerStats[playerIndex].wins);
NETuint32_t(&playerStats[playerIndex].losses);
NETuint32_t(&playerStats[playerIndex].totalKills);
NETuint32_t(&playerStats[playerIndex].totalScore);
NETuint32_t(&playerStats[playerIndex].recentKills);
NETuint32_t(&playerStats[playerIndex].recentScore);
NETuint32_t(&playerStats[playerIndex].killsToAdd);
NETuint32_t(&playerStats[playerIndex].scoreToAdd);
NETend();
}
return true;
}
void recvMultiStats(NETQUEUE queue)
{
uint32_t playerIndex;
NETbeginDecode(queue, NET_PLAYER_STATS);
// Retrieve the ID number of the player for which we need to
// update the stats
NETuint32_t(&playerIndex);
if (playerIndex >= MAX_PLAYERS)
{
return;
}
// we don't what to update ourselves, we already know our score (FIXME: rewrite setMultiStats())
if (!myResponsibility(playerIndex))
{
// Retrieve the actual stats
NETuint32_t(&playerStats[playerIndex].played);
NETuint32_t(&playerStats[playerIndex].wins);
NETuint32_t(&playerStats[playerIndex].losses);
NETuint32_t(&playerStats[playerIndex].totalKills);
NETuint32_t(&playerStats[playerIndex].totalScore);
NETuint32_t(&playerStats[playerIndex].recentKills);
NETuint32_t(&playerStats[playerIndex].recentScore);
NETuint32_t(&playerStats[playerIndex].killsToAdd);
NETuint32_t(&playerStats[playerIndex].scoreToAdd);
}
NETend();
}
// ////////////////////////////////////////////////////////////////////////////
// Load Player Stats
BOOL loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
{
char fileName[255];
UDWORD size;
char *pFileData;
memset(st, 0, sizeof(PLAYERSTATS)); // clear in case we don't get to load
// Prevent an empty player name (where the first byte is a 0x0 terminating char already)
if (!*sPlayerName)
{
strcpy(sPlayerName, _("Player"));
}
snprintf(fileName, sizeof(fileName), "%s%s.sta", MultiPlayersPath, sPlayerName);
debug(LOG_WZ, "loadMultiStats: %s", fileName);
// check player already exists
if ( !PHYSFS_exists( fileName ) )
{
PLAYERSTATS blankstats;
memset(&blankstats, 0, sizeof(PLAYERSTATS));
saveMultiStats(sPlayerName,sPlayerName,&blankstats); // didnt exist so create.
}
else
{
int num = 0;
loadFile(fileName,&pFileData,&size);
if (strncmp(pFileData, "WZ.STA.v3", 9) != 0)
{
return false; // wrong version or not a stats file
}
num = sscanf(pFileData, "WZ.STA.v3\n%u %u %u %u %u",
&st->wins, &st->losses, &st->totalKills, &st->totalScore, &st->played);
if (num < 5)
{
st->played = 0; // must be old, buggy format still
}
free(pFileData);
}
// reset recent scores
st->recentKills = 0;
st->recentScore = 0;
st->killsToAdd = 0;
st->scoreToAdd = 0;
// clear any skirmish stats.
for(size = 0;size<MAX_PLAYERS;size++)
{
ingame.skScores[size][0] =0;
ingame.skScores[size][1] =0;
}
return true;
}
// ////////////////////////////////////////////////////////////////////////////
// Save Player Stats
#define MAX_STA_SIZE 500
BOOL saveMultiStats(const char *sFileName, const char *sPlayerName, const PLAYERSTATS *st)
{
char buffer[MAX_STA_SIZE];
char fileName[255] = "";
snprintf(buffer, MAX_STA_SIZE, "WZ.STA.v3\n%u %u %u %u %u",
st->wins, st->losses, st->totalKills, st->totalScore, st->played);
snprintf(fileName, sizeof(fileName), "%s%s.sta", MultiPlayersPath, sFileName);
saveFile(fileName, buffer, strlen(buffer));
return true;
}
// ////////////////////////////////////////////////////////////////////////////
// score update functions
// update players damage stats.
void updateMultiStatsDamage(UDWORD attacker, UDWORD defender, UDWORD inflicted)
{
PLAYERSTATS st;
if (Cheated)
{
return;
}
// FIXME: Why in the world are we using two different structs for stats when we can use only one?
// Host controls self + AI, so update the scores for them as well.
if (attacker < MAX_PLAYERS)
{
if (myResponsibility(attacker) && NetPlay.bComms)
{
st = getMultiStats(attacker); // get stats
if (NetPlay.bComms)
{
st.scoreToAdd += (2 * inflicted);
}
else
{
st.recentScore += (2 * inflicted);
}
setMultiStats(attacker, st, true);
}
else
{
ingame.skScores[attacker][0] += (2 * inflicted); // increment skirmish players rough score.
}
}
// FIXME: Why in the world are we using two different structs for stats when we can use only one?
// Host controls self + AI, so update the scores for them as well.
if (defender < MAX_PLAYERS)
{
if (myResponsibility(defender) && NetPlay.bComms)
{
st = getMultiStats(defender); // get stats
if (NetPlay.bComms)
{
st.scoreToAdd -= inflicted;
}
else
{
st.recentScore -= inflicted;
}
setMultiStats(defender, st, true);
}
else
{
ingame.skScores[defender][0] -= inflicted; // increment skirmish players rough score.
}
}
}
// update games played.
void updateMultiStatsGames(void)
{
PLAYERSTATS st;
if (Cheated)
{
return;
}
st = getMultiStats(selectedPlayer);
st.played ++;
setMultiStats(selectedPlayer, st, true);
}
// games won
void updateMultiStatsWins(void)
{
PLAYERSTATS st;
if (Cheated)
{
return;
}
st = getMultiStats(selectedPlayer);
st.wins ++;
setMultiStats(selectedPlayer, st, true);
}
//games lost.
void updateMultiStatsLoses(void)
{
PLAYERSTATS st;
if (Cheated)
{
return;
}
st = getMultiStats(selectedPlayer);
++st.losses;
setMultiStats(selectedPlayer, st, true);
}
// update kills
void updateMultiStatsKills(BASE_OBJECT *psKilled,UDWORD player)
{
PLAYERSTATS st;
if (Cheated)
{
return;
}
// FIXME: Why in the world are we using two different structs for stats when we can use only one?
// Host controls self + AI, so update the scores for them as well.
if(myResponsibility(player) && NetPlay.bComms)
{
st = getMultiStats(player);
if(NetPlay.bComms)
{
st.killsToAdd++; // increase kill count;
}
else
{
st.recentKills++;
}
setMultiStats(player, st, true);
}
else
{
ingame.skScores[player][1]++;
}
}