1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-19 00:17:56 +02:00

Statistics is now managed as part of CGameHandler

This commit is contained in:
Ivan Savenko
2025-05-20 21:40:05 +03:00
parent af20b39fe6
commit aa9b13b66a
8 changed files with 56 additions and 45 deletions

View File

@ -83,8 +83,6 @@ public:
CBonusSystemNode globalEffects; CBonusSystemNode globalEffects;
RumorState currentRumor; RumorState currentRumor;
StatisticDataSet statistic;
// NOTE: effectively AI mutex, only used by adventure map AI // NOTE: effectively AI mutex, only used by adventure map AI
static std::shared_mutex mutex; static std::shared_mutex mutex;
@ -185,7 +183,11 @@ public:
std::map<ArtifactID, int> allocatedArtifactsUnused; std::map<ArtifactID, int> allocatedArtifactsUnused;
h & allocatedArtifactsUnused; h & allocatedArtifactsUnused;
} }
h & statistic; if (!h.hasFeature(Handler::Version::SERVER_STATISTICS))
{
StatisticDataSet statistic;
h & statistic;
}
if(!h.saving && h.loadingGamestate) if(!h.saving && h.loadingGamestate)
restoreBonusSystemTree(); restoreBonusSystemTree();

View File

@ -31,7 +31,7 @@ void StatisticDataSet::add(StatisticDataSetEntry entry)
data.push_back(entry); data.push_back(entry);
} }
StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs) StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs, const StatisticDataSet & accumulatedData)
{ {
StatisticDataSetEntry data; StatisticDataSetEntry data;
@ -63,18 +63,18 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons
data.numMines = Statistic::getNumMines(gs, ps); data.numMines = Statistic::getNumMines(gs, ps);
data.score = scenarioHighScores.calculate().total; data.score = scenarioHighScores.calculate().total;
data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0; data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0;
data.numBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesNeutral : 0; data.numBattlesNeutral = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numBattlesNeutral : 0;
data.numBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesPlayer : 0; data.numBattlesPlayer = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numBattlesPlayer : 0;
data.numWinBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesNeutral : 0; data.numWinBattlesNeutral = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numWinBattlesNeutral : 0;
data.numWinBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesPlayer : 0; data.numWinBattlesPlayer = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numWinBattlesPlayer : 0;
data.numHeroSurrendered = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroSurrendered : 0; data.numHeroSurrendered = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numHeroSurrendered : 0;
data.numHeroEscaped = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroEscaped : 0; data.numHeroEscaped = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numHeroEscaped : 0;
data.spentResourcesForArmy = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources(); data.spentResourcesForArmy = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources();
data.spentResourcesForBuildings = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources(); data.spentResourcesForBuildings = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources();
data.tradeVolume = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).tradeVolume : TResources(); data.tradeVolume = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).tradeVolume : TResources();
data.eventCapturedTown = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).lastCapturedTownDay == gs->getDate(Date::DAY) : false; data.eventCapturedTown = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).lastCapturedTownDay == gs->getDate(Date::DAY) : false;
data.eventDefeatedStrongestHero = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).lastDefeatedStrongestHeroDay == gs->getDate(Date::DAY) : false; data.eventDefeatedStrongestHero = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).lastDefeatedStrongestHeroDay == gs->getDate(Date::DAY) : false;
data.movementPointsUsed = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).movementPointsUsed : 0; data.movementPointsUsed = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).movementPointsUsed : 0;
return data; return data;
} }

View File

@ -100,8 +100,8 @@ struct DLL_LINKAGE StatisticDataSetEntry
class DLL_LINKAGE StatisticDataSet class DLL_LINKAGE StatisticDataSet
{ {
public: public:
void add(StatisticDataSetEntry entry); void add(StatisticDataSetEntry entry);
static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs); static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs, const StatisticDataSet & accumulatedData);
std::string toCsv(std::string sep) const; std::string toCsv(std::string sep) const;
std::string writeCsv() const; std::string writeCsv() const;

View File

@ -23,7 +23,7 @@
/// - change 'CURRENT' to 'CURRENT = NEW_TEST_KEY'. /// - change 'CURRENT' to 'CURRENT = NEW_TEST_KEY'.
/// ///
/// To check for version in serialize() call use form /// To check for version in serialize() call use form
/// if (h.version >= Handler::Version::NEW_TEST_KEY) /// if (h.hasFeature(Handler::Version::NEW_TEST_KEY))
/// h & newKey; // loading/saving save of a new version /// h & newKey; // loading/saving save of a new version
/// else /// else
/// newKey = saneDefaultValue; // loading of old save /// newKey = saneDefaultValue; // loading of old save
@ -43,8 +43,9 @@ enum class ESerializationVersion : int32_t
FLAGGABLE_BONUS_SYSTEM_NODE, // flaggable objects now contain bonus system node FLAGGABLE_BONUS_SYSTEM_NODE, // flaggable objects now contain bonus system node
RANDOMIZATION_REWORK, // random rolls logic has been moved to server RANDOMIZATION_REWORK, // random rolls logic has been moved to server
CUSTOM_BONUS_ICONS, // support for custom icons in bonuses CUSTOM_BONUS_ICONS, // support for custom icons in bonuses
SERVER_STATISTICS, // statistics now only saved on server
CURRENT = CUSTOM_BONUS_ICONS, CURRENT = SERVER_STATISTICS,
}; };
static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!"); static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!");

View File

@ -519,6 +519,7 @@ CGameHandler::CGameHandler(CVCMIServer * lobby)
, complainInvalidSlot("Invalid slot accessed!") , complainInvalidSlot("Invalid slot accessed!")
, turnTimerHandler(std::make_unique<TurnTimerHandler>(*this)) , turnTimerHandler(std::make_unique<TurnTimerHandler>(*this))
, newTurnProcessor(std::make_unique<NewTurnProcessor>(this)) , newTurnProcessor(std::make_unique<NewTurnProcessor>(this))
, statistics(std::make_unique<StatisticDataSet>())
{ {
QID = 1; QID = 1;
@ -620,7 +621,7 @@ void CGameHandler::addStatistics(StatisticDataSet &stat) const
if (elem.first == PlayerColor::NEUTRAL || !elem.first.isValidPlayer()) if (elem.first == PlayerColor::NEUTRAL || !elem.first.isValidPlayer())
continue; continue;
auto data = StatisticDataSet::createEntry(&elem.second, &gameState()); auto data = StatisticDataSet::createEntry(&elem.second, &gameState(), *statistics);
stat.add(data); stat.add(data);
} }
@ -649,7 +650,7 @@ void CGameHandler::onNewTurn()
} }
else else
{ {
addStatistics(gameState().statistic); // write at end of turn addStatistics(*statistics); // write at end of turn
} }
for (const auto & townID : gameState().getMap().getAllTowns()) for (const auto & townID : gameState().getMap().getAllTowns())
@ -1035,7 +1036,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
turnTimerHandler->setEndTurnAllowed(h->getOwner(), !movingOntoWater && !movingOntoObstacle); turnTimerHandler->setEndTurnAllowed(h->getOwner(), !movingOntoWater && !movingOntoObstacle);
doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE);
gameState().statistic.accumulatedValues[asker].movementPointsUsed += tmh.movePoints; statistics->accumulatedValues[asker].movementPointsUsed += tmh.movePoints;
return true; return true;
} }
} }
@ -1079,7 +1080,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, const PlayerColor owne
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj); const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
if (town) //town captured if (town) //town captured
{ {
gameState().statistic.accumulatedValues[owner].lastCapturedTownDay = gameState().getDate(Date::DAY); statistics->accumulatedValues[owner].lastCapturedTownDay = gameState().getDate(Date::DAY);
if (owner.isValidPlayer()) //new owner is real player if (owner.isValidPlayer()) //new owner is real player
{ {
@ -1489,7 +1490,7 @@ void CGameHandler::sendToAllClients(CPackForClient & pack)
void CGameHandler::sendAndApply(CPackForClient & pack) void CGameHandler::sendAndApply(CPackForClient & pack)
{ {
sendToAllClients(pack); sendToAllClients(pack);
gameState().apply(pack); gs->apply(pack);
logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name()); logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name());
} }
@ -1646,8 +1647,8 @@ bool CGameHandler::load(const std::string & filename)
gameLobby().announceMessage(str); gameLobby().announceMessage(str);
return false; return false;
} }
gameState().preInit(LIBRARY); gs->preInit(LIBRARY);
gameState().updateOnLoad(gameLobby().si.get()); gs->updateOnLoad(gameLobby().si.get());
return true; return true;
} }
@ -2229,7 +2230,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
if(!force) if(!force)
{ {
giveResources(t->tempOwner, -requestedBuilding->resources); giveResources(t->tempOwner, -requestedBuilding->resources);
gameState().statistic.accumulatedValues[t->tempOwner].spentResourcesForBuildings += requestedBuilding->resources; statistics->accumulatedValues[t->tempOwner].spentResourcesForBuildings += requestedBuilding->resources;
} }
//We know what has been built, apply changes. Do this as final step to properly update town window //We know what has been built, apply changes. Do this as final step to properly update town window
@ -2429,7 +2430,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
//recruit //recruit
TResources cost = (c->getFullRecruitCost() * cram); TResources cost = (c->getFullRecruitCost() * cram);
giveResources(army->tempOwner, -cost); giveResources(army->tempOwner, -cost);
gameState().statistic.accumulatedValues[army->tempOwner].spentResourcesForArmy += cost; statistics->accumulatedValues[army->tempOwner].spentResourcesForArmy += cost;
SetAvailableCreatures sac; SetAvailableCreatures sac;
sac.tid = objid; sac.tid = objid;
@ -2492,7 +2493,7 @@ bool CGameHandler::upgradeCreature(ObjectInstanceID objid, SlotID pos, CreatureI
//take resources //take resources
giveResources(player, -totalCost); giveResources(player, -totalCost);
gameState().statistic.accumulatedValues[player].spentResourcesForArmy += totalCost; statistics->accumulatedValues[player].spentResourcesForArmy += totalCost;
//upgrade creature //upgrade creature
changeStackType(StackLocation(obj->id, pos), upgID.toCreature()); changeStackType(StackLocation(obj->id, pos), upgID.toCreature());
@ -3194,8 +3195,8 @@ bool CGameHandler::tradeResources(const IMarket *market, ui32 amountToSell, Play
giveResource(player, toSell, -b1 * amountToBoy); giveResource(player, toSell, -b1 * amountToBoy);
giveResource(player, toBuy, b2 * amountToBoy); giveResource(player, toBuy, b2 * amountToBoy);
gameState().statistic.accumulatedValues[player].tradeVolume[toSell] += -b1 * amountToBoy; statistics->accumulatedValues[player].tradeVolume[toSell] += -b1 * amountToBoy;
gameState().statistic.accumulatedValues[player].tradeVolume[toBuy] += b2 * amountToBoy; statistics->accumulatedValues[player].tradeVolume[toBuy] += b2 * amountToBoy;
return true; return true;
} }
@ -3568,7 +3569,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
PlayerEndsGame peg; PlayerEndsGame peg;
peg.player = player; peg.player = player;
peg.victoryLossCheckResult = victoryLossCheckResult; peg.victoryLossCheckResult = victoryLossCheckResult;
peg.statistic = StatisticDataSet(gameState().statistic); peg.statistic = *statistics;
addStatistics(peg.statistic); // add last turn befor win / loss addStatistics(peg.statistic); // add last turn befor win / loss
sendAndApply(peg); sendAndApply(peg);
@ -4288,7 +4289,7 @@ std::shared_ptr<CGObjectInstance> CGameHandler::createNewObject(const int3 & vis
auto o = handler->create(&gameInfo(), nullptr); auto o = handler->create(&gameInfo(), nullptr);
handler->configureObject(o.get(), *randomizer); handler->configureObject(o.get(), *randomizer);
assert(o->ID == objectID); assert(o->ID == objectID);
gameState().getMap().generateUniqueInstanceName(o.get()); gs->getMap().generateUniqueInstanceName(o.get());
assert(!handler->getTemplates(terrainType).empty()); assert(!handler->getTemplates(terrainType).empty());
if (handler->getTemplates().empty()) if (handler->getTemplates().empty())

View File

@ -27,6 +27,7 @@ class CCommanderInstance;
class EVictoryLossCheckResult; class EVictoryLossCheckResult;
class CRandomGenerator; class CRandomGenerator;
class GameRandomizer; class GameRandomizer;
class StatisticDataSet;
struct StartInfo; struct StartInfo;
struct TerrainTile; struct TerrainTile;
@ -68,6 +69,7 @@ public:
std::unique_ptr<TurnTimerHandler> turnTimerHandler; std::unique_ptr<TurnTimerHandler> turnTimerHandler;
std::unique_ptr<NewTurnProcessor> newTurnProcessor; std::unique_ptr<NewTurnProcessor> newTurnProcessor;
std::unique_ptr<GameRandomizer> randomizer; std::unique_ptr<GameRandomizer> randomizer;
std::unique_ptr<StatisticDataSet> statistics;
std::shared_ptr<CGameState> gs; std::shared_ptr<CGameState> gs;
//use enums as parameters, because doMove(sth, true, false, true) is not readable //use enums as parameters, because doMove(sth, true, false, true) is not readable
@ -96,7 +98,6 @@ public:
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h); void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
IGameInfoCallback & gameInfo(); IGameInfoCallback & gameInfo();
CGameState & gameState() { return *gs; }
const CGameState & gameState() const { return *gs; } const CGameState & gameState() const { return *gs; }
// Helpers to create new object of specified type // Helpers to create new object of specified type
@ -257,6 +258,12 @@ public:
h & *turnOrder; h & *turnOrder;
h & *turnTimerHandler; h & *turnTimerHandler;
if (h.hasFeature(Handler::Version::SERVER_STATISTICS))
{
h & *statistics;
}
#if SCRIPTING_ENABLED #if SCRIPTING_ENABLED
JsonNode scriptsState; JsonNode scriptsState;
if(h.saving) if(h.saving)

View File

@ -343,21 +343,21 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
if(!strongestHero || hero->exp > strongestHero->exp) if(!strongestHero || hero->exp > strongestHero->exp)
strongestHero = hero; strongestHero = hero;
if(strongestHero->id == finishingBattle->loserId && strongestHero->level > 5) if(strongestHero->id == finishingBattle->loserId && strongestHero->level > 5)
gameHandler->gameState().statistic.accumulatedValues[finishingBattle->victor].lastDefeatedStrongestHeroDay = gameHandler->gameState().getDate(Date::DAY); gameHandler->statistics->accumulatedValues[finishingBattle->victor].lastDefeatedStrongestHeroDay = gameHandler->gameState().getDate(Date::DAY);
} }
if(battle.sideToPlayer(BattleSide::ATTACKER) == PlayerColor::NEUTRAL || battle.sideToPlayer(BattleSide::DEFENDER) == PlayerColor::NEUTRAL) if(battle.sideToPlayer(BattleSide::ATTACKER) == PlayerColor::NEUTRAL || battle.sideToPlayer(BattleSide::DEFENDER) == PlayerColor::NEUTRAL)
{ {
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(BattleSide::ATTACKER)].numBattlesNeutral++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(BattleSide::ATTACKER)].numBattlesNeutral++;
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(BattleSide::DEFENDER)].numBattlesNeutral++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(BattleSide::DEFENDER)].numBattlesNeutral++;
if(!finishingBattle->isDraw()) if(!finishingBattle->isDraw())
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesNeutral++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesNeutral++;
} }
else else
{ {
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(BattleSide::ATTACKER)].numBattlesPlayer++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(BattleSide::ATTACKER)].numBattlesPlayer++;
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(BattleSide::DEFENDER)].numBattlesPlayer++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(BattleSide::DEFENDER)].numBattlesPlayer++;
if(!finishingBattle->isDraw()) if(!finishingBattle->isDraw())
gameHandler->gameState().statistic.accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesPlayer++; gameHandler->statistics->accumulatedValues[battle.sideToPlayer(finishingBattle->winnerSide)].numWinBattlesPlayer++;
} }
BattleResultAccepted raccepted; BattleResultAccepted raccepted;
@ -563,13 +563,13 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
if (result.result == EBattleResult::SURRENDER) if (result.result == EBattleResult::SURRENDER)
{ {
gameHandler->gameState().statistic.accumulatedValues[finishingBattle->loser].numHeroSurrendered++; gameHandler->statistics->accumulatedValues[finishingBattle->loser].numHeroSurrendered++;
gameHandler->heroPool->onHeroSurrendered(finishingBattle->loser, loserHero); gameHandler->heroPool->onHeroSurrendered(finishingBattle->loser, loserHero);
} }
if (result.result == EBattleResult::ESCAPE) if (result.result == EBattleResult::ESCAPE)
{ {
gameHandler->gameState().statistic.accumulatedValues[finishingBattle->loser].numHeroEscaped++; gameHandler->statistics->accumulatedValues[finishingBattle->loser].numHeroEscaped++;
gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, loserHero); gameHandler->heroPool->onHeroEscaped(finishingBattle->loser, loserHero);
} }

View File

@ -149,7 +149,7 @@ void PlayerMessageProcessor::commandStatistic(PlayerColor player, const std::vec
if(!isHost) if(!isHost)
return; return;
std::string path = gameHandler->gameState().statistic.writeCsv(); std::string path = gameHandler->statistics->writeCsv();
auto str = MetaString::createFromTextID("vcmi.broadcast.statisticFile"); auto str = MetaString::createFromTextID("vcmi.broadcast.statisticFile");
str.replaceRawString(path); str.replaceRawString(path);