warzone2100/src/multistat.cpp

354 lines
9.1 KiB
C++

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2013 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"
#include <QtCore/QSettings>
// ////////////////////////////////////////////////////////////////////////////
// STATS STUFF
// ////////////////////////////////////////////////////////////////////////////
static PLAYERSTATS playerStats[MAX_PLAYERS];
PLAYERSTATS::PLAYERSTATS()
: played(0)
, wins(0)
, losses(0)
, totalKills(0)
, totalScore(0)
, recentKills(0)
, recentScore(0)
{}
// ////////////////////////////////////////////////////////////////////////////
// Get Player's stats
PLAYERSTATS const &getMultiStats(UDWORD player)
{
return playerStats[player];
}
// ////////////////////////////////////////////////////////////////////////////
// Set Player's stats
// send stats to all players when bLocal is false
bool setMultiStats(uint32_t playerIndex, PLAYERSTATS plStats, bool bLocal)
{
if (playerIndex >= MAX_PLAYERS)
{
return true;
}
// First copy over the data into our local array
playerStats[playerIndex] = 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);
EcKey::Key identity;
if (!playerStats[playerIndex].identity.empty())
{
identity = playerStats[playerIndex].identity.toBytes(EcKey::Public);
}
NETbytes(&identity);
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)
{
NETend();
return;
}
if (playerIndex != queue.index && queue.index != NET_HOST_ONLY)
{
HandleBadParam("NET_PLAYER_STATS given incorrect params.", playerIndex, queue.index);
NETend();
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);
EcKey::Key identity;
NETbytes(&identity);
EcKey::Key prevIdentity;
if (!playerStats[playerIndex].identity.empty())
{
prevIdentity = playerStats[playerIndex].identity.toBytes(EcKey::Public);
}
playerStats[playerIndex].identity.clear();
if (!identity.empty())
{
playerStats[playerIndex].identity.fromBytes(identity, EcKey::Public);
}
if (identity != prevIdentity)
{
ingame.PingTimes[playerIndex] = PING_LIMIT;
}
}
NETend();
}
// ////////////////////////////////////////////////////////////////////////////
// Load Player Stats
bool loadMultiStats(char *sPlayerName, PLAYERSTATS *st)
{
char fileName[255];
UDWORD size;
char *pFileData;
*st = 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))
{
loadFile(fileName,&pFileData,&size);
if (strncmp(pFileData, "WZ.STA.v3", 9) != 0)
{
return false; // wrong version or not a stats file
}
char identity[1001];
identity[0] = '\0';
sscanf(pFileData, "WZ.STA.v3\n%u %u %u %u %u\n%1000[A-Za-z0-9+/=]",
&st->wins, &st->losses, &st->totalKills, &st->totalScore, &st->played, identity);
free(pFileData);
if (identity[0] != '\0')
{
st->identity.fromBytes(base64Decode(identity), EcKey::Private);
}
}
if (st->identity.empty())
{
st->identity = EcKey::generate(); // Generate new identity.
saveMultiStats(sPlayerName, sPlayerName, st); // Save new identity.
}
// reset recent scores
st->recentKills = 0;
st->recentScore = 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
bool saveMultiStats(const char *sFileName, const char *sPlayerName, const PLAYERSTATS *st)
{
char buffer[1000];
char fileName[255] = "";
ssprintf(buffer, "WZ.STA.v3\n%u %u %u %u %u\n%s\n",
st->wins, st->losses, st->totalKills, st->totalScore, st->played, base64Encode(st->identity.toBytes(EcKey::Private)).c_str());
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)
{
if (Cheated)
{
return;
}
// FIXME: Why in the world are we using two different structs for stats when we can use only one?
if (attacker < MAX_PLAYERS)
{
if (NetPlay.bComms)
{
playerStats[attacker].totalScore += 2 * inflicted;
playerStats[attacker].recentScore += 2 * inflicted;
}
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?
if (defender < MAX_PLAYERS)
{
if (NetPlay.bComms)
{
playerStats[defender].totalScore -= inflicted;
playerStats[defender].recentScore -= inflicted;
}
else
{
ingame.skScores[defender][0] -= inflicted; // increment skirmish players rough score.
}
}
}
// update games played.
void updateMultiStatsGames(void)
{
if (Cheated || selectedPlayer >= MAX_PLAYERS)
{
return;
}
++playerStats[selectedPlayer].played;
}
// games won
void updateMultiStatsWins(void)
{
if (Cheated || selectedPlayer >= MAX_PLAYERS)
{
return;
}
++playerStats[selectedPlayer].wins;
}
//games lost.
void updateMultiStatsLoses(void)
{
if (Cheated || selectedPlayer >= MAX_PLAYERS)
{
return;
}
++playerStats[selectedPlayer].losses;
}
// update kills
void updateMultiStatsKills(BASE_OBJECT *psKilled,UDWORD player)
{
if (Cheated || player >= MAX_PLAYERS)
{
return;
}
// FIXME: Why in the world are we using two different structs for stats when we can use only one?
if (NetPlay.bComms)
{
++playerStats[player].totalKills;
++playerStats[player].recentKills;
}
else
{
ingame.skScores[player][1]++;
}
}
static std::map<std::string, EcKey::Key> knownPlayers;
static QSettings *knownPlayersIni = nullptr;
std::map<std::string, EcKey::Key> const &getKnownPlayers()
{
if (knownPlayersIni == nullptr)
{
knownPlayersIni = new QSettings(PHYSFS_getWriteDir() + QString("/") + "knownPlayers.ini", QSettings::IniFormat);
QStringList names = knownPlayersIni->allKeys();
for (int i = 0; i < names.size(); ++i)
{
knownPlayers[names[i].toUtf8().constData()] = base64Decode(knownPlayersIni->value(names[i]).toString().toStdString());
}
}
return knownPlayers;
}
void addKnownPlayer(std::string const &name, EcKey const &key, bool override)
{
if (key.empty())
{
return;
}
if (!override && knownPlayers.find(name) != knownPlayers.end())
{
return;
}
getKnownPlayers(); // Init knownPlayersIni.
knownPlayers[name] = key.toBytes(EcKey::Public);
knownPlayersIni->setValue(QString::fromUtf8(name.c_str()), base64Encode(key.toBytes(EcKey::Public)).c_str());
knownPlayersIni->sync();
}