Merge pull request #5882 from Laserlicht/save_stat

Statistics in highscore screen
This commit is contained in:
Ivan Savenko
2025-07-08 10:21:02 +03:00
committed by GitHub
5 changed files with 135 additions and 7 deletions
+40 -6
View File
@@ -37,10 +37,13 @@
#include "../../lib/gameState/HighScore.h"
#include "../../lib/gameState/GameStatistics.h"
#include "../../lib/GameLibrary.h"
#include "../../lib/serializer/JsonSerializer.h"
#include "../../lib/serializer/JsonDeserializer.h"
CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
: CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted)
{
addUsedEvents(LCLICK);
addUsedEvents(SHOW_POPUP);
OBJECT_CONSTRUCTION;
@@ -50,7 +53,7 @@ CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
addButtons();
}
void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
void CHighScoreScreen::rowEvent(std::function<void(int row, bool currentGameNotInListEntry)> func, const Point & cursorPosition)
{
for (int i = 0; i < screenRows; i++)
{
@@ -58,14 +61,33 @@ void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
Rect r = Rect(80, 40 + i * 50, 635, 50);
if(r.isInside(cursorPosition - pos))
{
std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : i]["datetime"].String();
if(!tmp.empty())
CRClickPopup::createAndPush(tmp);
}
func(i, currentGameNotInListEntry);
}
}
void CHighScoreScreen::clickPressed(const Point & cursorPosition)
{
rowEvent([this](int row, bool currentGameNotInListEntry){
auto node = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : row];
if(node["statistic"].isNull())
return;
JsonDeserializer ser(nullptr, node);
StatisticDataSet stat;
ser.serializeStruct("statistic", stat);
ENGINE->windows().createAndPushWindow<CStatisticScreen>(stat);
}, cursorPosition);
}
void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
{
rowEvent([this](int row, bool currentGameNotInListEntry){
std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : row]["datetime"].String();
if(!tmp.empty())
CRClickPopup::createAndPush("{" + LIBRARY->generaltexth->translate("core.help.316.hover") + "}\n\n" + tmp);
}, cursorPosition);
}
void CHighScoreScreen::addButtons()
{
OBJECT_CONSTRUCTION;
@@ -251,6 +273,15 @@ int CHighScoreInputScreen::addEntry(std::string text) {
newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total;
newNode["datetime"].String() = TextOperations::getFormattedDateTimeLocal(std::time(nullptr));
newNode["posFlag"].Bool() = true;
if(!calc.isCampaign)
{
JsonSerializer ser(nullptr, newNode);
ser.serializeStruct("statistic", stat);
}
int highscoreEntriesCap = settings["general"]["highscoreEntriesCap"].Integer();
if (baseNode.size() > highscoreEntriesCap - 1)
baseNode.resize(highscoreEntriesCap - 1);
baseNode.push_back(newNode);
boost::range::sort(baseNode, sortFunctor);
@@ -258,6 +289,9 @@ int CHighScoreInputScreen::addEntry(std::string text) {
int pos = -1;
for (int i = 0; i < baseNode.size(); i++)
{
if(!baseNode[i]["statistic"].isNull() && i >= settings["general"]["highscoreStatisticEntriesCap"].Integer() && baseNode[i]["posFlag"].isNull())
baseNode[i]["statistic"].clear();
if(!baseNode[i]["posFlag"].isNull())
{
baseNode[i]["posFlag"].clear();
+3
View File
@@ -38,6 +38,9 @@ private:
void buttonResetClick();
void buttonExitClick();
void rowEvent(std::function<void(int row, bool currentGameNotInListEntry)> func, const Point & cursorPosition);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void showAll(Canvas & to) override;
+11 -1
View File
@@ -46,7 +46,9 @@
"enableOverlay",
"lastKindomInterface",
"enableSubtitle",
"ignoreMuteSwitch"
"ignoreMuteSwitch",
"highscoreEntriesCap",
"highscoreStatisticEntriesCap"
],
"properties" : {
"playerName" : {
@@ -166,6 +168,14 @@
"ignoreMuteSwitch" : {
"type": "boolean",
"default": true
},
"highscoreEntriesCap" : {
"type" : "number",
"default" : 100
},
"highscoreStatisticEntriesCap" : {
"type" : "number",
"default" : 15
}
}
},
+75
View File
@@ -22,6 +22,8 @@
#include "../mapObjects/MiscObjects.h"
#include "../mapping/CMap.h"
#include "../entities/building/CBuilding.h"
#include "../serializer/JsonDeserializer.h"
#include "../serializer/JsonUpdater.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -79,6 +81,79 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons
return data;
}
void StatisticDataSetEntry::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeString("map", map);
handler.serializeInt("timestamp", timestamp);
handler.serializeInt("day", day);
handler.serializeId("player", player, PlayerColor::CANNOT_DETERMINE);
handler.serializeString("playerName", playerName);
handler.serializeInt("team", team);
handler.serializeBool("isHuman", isHuman);
handler.serializeEnum("status", status, {"ingame", "loser", "winner"});
resources.serializeJson(handler, "resources");
handler.serializeInt("numberHeroes", numberHeroes);
handler.serializeInt("numberTowns", numberTowns);
handler.serializeInt("numberArtifacts", numberArtifacts);
handler.serializeInt("numberDwellings", numberDwellings);
handler.serializeInt("armyStrength", armyStrength);
handler.serializeInt("totalExperience", totalExperience);
handler.serializeInt("income", income);
handler.serializeFloat("mapExploredRatio", mapExploredRatio);
handler.serializeFloat("obeliskVisitedRatio", obeliskVisitedRatio);
handler.serializeFloat("townBuiltRatio", townBuiltRatio);
handler.serializeBool("hasGrail", hasGrail);
{
auto zonesData = handler.enterStruct("numMines");
for(TResource idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], numMines[idx], 0);
}
handler.serializeInt("score", score);
handler.serializeInt("maxHeroLevel", maxHeroLevel);
handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
handler.serializeInt("numHeroEscaped", numHeroEscaped);
spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
tradeVolume.serializeJson(handler, "tradeVolume");
handler.serializeBool("eventCapturedTown", eventCapturedTown);
handler.serializeBool("eventDefeatedStrongestHero", eventDefeatedStrongestHero);
handler.serializeInt("movementPointsUsed", movementPointsUsed);
}
void StatisticDataSet::PlayerAccumulatedValueStorage::serializeJson(JsonSerializeFormat & handler)
{
handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
handler.serializeInt("numHeroEscaped", numHeroEscaped);
spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
tradeVolume.serializeJson(handler, "tradeVolume");
handler.serializeInt("movementPointsUsed", movementPointsUsed);
handler.serializeInt("lastCapturedTownDay", lastCapturedTownDay);
handler.serializeInt("lastDefeatedStrongestHeroDay", lastDefeatedStrongestHeroDay);
}
void StatisticDataSet::serializeJson(JsonSerializeFormat & handler)
{
{
auto eventsHandler = handler.enterArray("data");
eventsHandler.syncSize(data, JsonNode::JsonType::DATA_VECTOR);
eventsHandler.serializeStruct(data);
}
{
auto eventsHandler = handler.enterStruct("accumulatedValues");
for(auto & val : accumulatedValues)
eventsHandler->serializeStruct(GameConstants::PLAYER_COLOR_NAMES[val.first], val.second);
}
}
std::string StatisticDataSet::toCsv(std::string sep) const
{
std::stringstream ss;
+6
View File
@@ -57,6 +57,8 @@ struct DLL_LINKAGE StatisticDataSetEntry
bool eventDefeatedStrongestHero;
si64 movementPointsUsed;
void serializeJson(JsonSerializeFormat & handler);
template <typename Handler> void serialize(Handler &h)
{
h & map;
@@ -105,6 +107,8 @@ public:
std::string toCsv(std::string sep) const;
std::string writeCsv() const;
void serializeJson(JsonSerializeFormat & handler);
struct PlayerAccumulatedValueStorage // holds some actual values needed for stats
{
int numBattlesNeutral;
@@ -120,6 +124,8 @@ public:
int lastCapturedTownDay;
int lastDefeatedStrongestHeroDay;
void serializeJson(JsonSerializeFormat & handler);
template <typename Handler> void serialize(Handler &h)
{
h & numBattlesNeutral;