From afb045ef149bec6d8af53bb19031b5eba0f1b29c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 22 Nov 2025 13:14:50 +0100 Subject: [PATCH] show statistics ingame --- Mods/vcmi/Content/config/english.json | 1 + Mods/vcmi/Content/config/german.json | 1 + client/CPlayerInterface.cpp | 6 +++++ client/CPlayerInterface.h | 2 ++ client/ClientNetPackVisitors.h | 1 + client/NetPacksClient.cpp | 5 ++++ client/adventureMap/AdventureMapShortcuts.cpp | 14 +++++++++++ client/adventureMap/AdventureMapShortcuts.h | 2 ++ client/gui/Shortcut.h | 1 + client/gui/ShortcutHandler.cpp | 1 + .../netlag/NetworkLagPredictionTestVisitor.h | 1 + client/netlag/PackRollbackGeneratorVisitor.h | 1 + config/keyBindingsConfig.json | 1 + lib/callback/CCallback.cpp | 6 +++++ lib/callback/CCallback.h | 1 + lib/callback/IGameActionCallback.h | 1 + lib/callback/IGameEventsReceiver.h | 3 +++ lib/networkPacks/NetPackVisitor.h | 2 ++ lib/networkPacks/NetPacksLib.cpp | 10 ++++++++ lib/networkPacks/PacksForClient.h | 14 +++++++++++ lib/networkPacks/PacksForServer.h | 10 ++++++++ lib/serializer/RegisterTypes.h | 2 ++ server/CGameHandler.cpp | 24 +++++++++++++++++++ server/CGameHandler.h | 1 + server/NetPacksServer.cpp | 7 ++++++ server/ServerNetPackVisitors.h | 1 + 26 files changed, 119 insertions(+) diff --git a/Mods/vcmi/Content/config/english.json b/Mods/vcmi/Content/config/english.json index 16381b008..8a573f16b 100644 --- a/Mods/vcmi/Content/config/english.json +++ b/Mods/vcmi/Content/config/english.json @@ -349,6 +349,7 @@ "vcmi.keyBindings.keyBinding.adventureViewPuzzle": "Adventure view puzzle", "vcmi.keyBindings.keyBinding.adventureViewScenario": "Adventure view scenario", "vcmi.keyBindings.keyBinding.adventureViewSelected": "Adventure view selected", + "vcmi.keyBindings.keyBinding.adventureViewStatistic": "Adventure view statistic", "vcmi.keyBindings.keyBinding.adventureViewWorld": "Adventure view world", "vcmi.keyBindings.keyBinding.adventureViewWorld1": "Adventure view world1", "vcmi.keyBindings.keyBinding.adventureViewWorld2": "Adventure view world2", diff --git a/Mods/vcmi/Content/config/german.json b/Mods/vcmi/Content/config/german.json index 9cc0ae2a6..4fbbec625 100644 --- a/Mods/vcmi/Content/config/german.json +++ b/Mods/vcmi/Content/config/german.json @@ -348,6 +348,7 @@ "vcmi.keyBindings.keyBinding.adventureViewPuzzle": "Abenteuer Rätsel anzeigen", "vcmi.keyBindings.keyBinding.adventureViewScenario": "Abenteuer Szenario anzeigen", "vcmi.keyBindings.keyBinding.adventureViewSelected": "Abenteuer Auswahl anzeigen", + "vcmi.keyBindings.keyBinding.adventureViewStatistic": "Abenteuer Statistik anzeigen", "vcmi.keyBindings.keyBinding.adventureViewWorld": "Abenteuer Weltansicht", "vcmi.keyBindings.keyBinding.adventureViewWorld1": "Abenteuer Weltansicht 1", "vcmi.keyBindings.keyBinding.adventureViewWorld2": "Abenteuer Weltansicht 2", diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c0ff4943f..fbd8602cb 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -37,6 +37,7 @@ #include "mainmenu/CMainMenu.h" #include "mainmenu/CHighScoreScreen.h" +#include "mainmenu/CStatisticScreen.h" #include "mapView/mapHandler.h" @@ -1847,3 +1848,8 @@ void CPlayerInterface::unregisterBattleInterface(std::shared_ptrserver().client->unregisterBattleInterface(autofightingAI, playerID); autofightingAI.reset(); } + +void CPlayerInterface::responseStatistic(StatisticDataSet & statistic) +{ + ENGINE->windows().createAndPushWindow(statistic); +} diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index c8fd10ba8..d01871f00 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -12,6 +12,7 @@ #include "ArtifactsUIController.h" #include "../lib/callback/CGameInterface.h" +#include "../lib/gameState/GameStatistics.h" #include "../lib/FunctionList.h" #include "gui/CIntObject.h" @@ -147,6 +148,7 @@ protected: // Call-ins from server, should not be called directly, but only via void playerEndsTurn(PlayerColor player) override; void showWorldViewEx(const std::vector & objectPositions, bool showTerrain) override; void setColorScheme(ColorScheme scheme) override; + void responseStatistic(StatisticDataSet & statistic) override; //for battles void actionFinished(const BattleID & battleID, const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 30828bb48..0a3ed9883 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -106,6 +106,7 @@ public: void visitEntitiesChanged(EntitiesChanged & pack) override; void visitPlayerCheated(PlayerCheated & pack) override; void visitChangeTownName(ChangeTownName & pack) override; + void visitResponseStatistic(ResponseStatistic & pack) override; }; class ApplyFirstClientNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index dfbd1f03c..ec5fbe621 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -1098,3 +1098,8 @@ void ApplyClientNetPackVisitor::visitChangeTownName(ChangeTownName & pack) ENGINE->windows().totalRedraw(); } } + +void ApplyClientNetPackVisitor::visitResponseStatistic(ResponseStatistic & pack) +{ + callInterfaceIfPresent(cl, pack.player, &IGameEventsReceiver::responseStatistic, pack.statistic); +} diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 5f3c23c1d..efcaf0a31 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -73,6 +73,7 @@ std::vector AdventureMapShortcuts::getShortcuts() { EShortcut::ADVENTURE_VIEW_WORLD_X1, optionInWorldView(), [this]() { this->worldViewScale1x(); } }, { EShortcut::ADVENTURE_VIEW_WORLD_X2, optionInWorldView(), [this]() { this->worldViewScale2x(); } }, { EShortcut::ADVENTURE_VIEW_WORLD_X4, optionInWorldView(), [this]() { this->worldViewScale4x(); } }, + { EShortcut::ADVENTURE_VIEW_STATISTIC, optionViewStatistic(), [this]() { this->viewStatistic(); } }, { EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, optionCanToggleLevel(), [this]() { this->switchMapLevel(); } }, { EShortcut::ADVENTURE_QUEST_LOG, optionCanViewQuests(), [this]() { this->showQuestlog(); } }, { EShortcut::ADVENTURE_TOGGLE_SLEEP, optionHeroSelected(), [this]() { this->toggleSleepWake(); } }, @@ -153,6 +154,11 @@ void AdventureMapShortcuts::worldViewScale4x() owner.openWorldView(16); } +void AdventureMapShortcuts::viewStatistic() +{ + GAME->interface()->cb->requestStatistic(); +} + void AdventureMapShortcuts::switchMapLevel() { owner.hotkeySwitchMapLevel(); @@ -677,3 +683,11 @@ bool AdventureMapShortcuts::optionHeroDig() auto hero = GAME->interface()->localState->getCurrentHero(); return optionInMapView() && hero && hero->diggingStatus() == EDiggingStatus::CAN_DIG; } + +bool AdventureMapShortcuts::optionViewStatistic() +{ + if(!GAME->interface()->makingTurn) + return false; + auto day = GAME->interface()->cb->getDate(Date::DAY); + return optionInMapView() && day > 1; +} diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index 8d6b055a0..f87d6616e 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -43,6 +43,7 @@ class AdventureMapShortcuts void worldViewScale1x(); void worldViewScale2x(); void worldViewScale4x(); + void viewStatistic(); void switchMapLevel(); void showQuestlog(); void toggleTrackHero(); @@ -103,6 +104,7 @@ public: bool optionMarketplace(); bool optionHeroBoat(EPathfindingLayer layer); bool optionHeroDig(); + bool optionViewStatistic(); void setState(EAdventureState newState); EAdventureState getState() const; diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index 905fcc2fd..be1ecc5a4 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -149,6 +149,7 @@ enum class EShortcut ADVENTURE_VIEW_WORLD_X1, ADVENTURE_VIEW_WORLD_X2, ADVENTURE_VIEW_WORLD_X4, + ADVENTURE_VIEW_STATISTIC, ADVENTURE_TRACK_HERO, ADVENTURE_TOGGLE_MAP_LEVEL, ADVENTURE_KINGDOM_OVERVIEW, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 2870a2f19..cd38267d4 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -203,6 +203,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"adventureViewWorld1", EShortcut::ADVENTURE_VIEW_WORLD_X1 }, {"adventureViewWorld2", EShortcut::ADVENTURE_VIEW_WORLD_X2 }, {"adventureViewWorld4", EShortcut::ADVENTURE_VIEW_WORLD_X4 }, + {"adventureViewStatistic", EShortcut::ADVENTURE_VIEW_STATISTIC }, {"adventureTrackHero", EShortcut::ADVENTURE_TRACK_HERO, }, {"adventureToggleMapLevel", EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL}, {"adventureKingdomOverview", EShortcut::ADVENTURE_KINGDOM_OVERVIEW}, diff --git a/client/netlag/NetworkLagPredictionTestVisitor.h b/client/netlag/NetworkLagPredictionTestVisitor.h index 6b3d18a79..533b49d70 100644 --- a/client/netlag/NetworkLagPredictionTestVisitor.h +++ b/client/netlag/NetworkLagPredictionTestVisitor.h @@ -55,6 +55,7 @@ class NetworkLagPredictionTestVisitor final : public ICPackVisitor //void visitMakeAction(MakeAction & pack) override; //void visitDigWithHero(DigWithHero & pack) override; //void visitCastAdvSpell(CastAdvSpell & pack) override; + //void visitRequestStatistic(RequestStatistic & pack) override; //void visitPlayerMessage(PlayerMessage & pack) override; //void visitSaveLocalState(SaveLocalState & pack) override; diff --git a/client/netlag/PackRollbackGeneratorVisitor.h b/client/netlag/PackRollbackGeneratorVisitor.h index a26877591..eedd5951a 100644 --- a/client/netlag/PackRollbackGeneratorVisitor.h +++ b/client/netlag/PackRollbackGeneratorVisitor.h @@ -102,6 +102,7 @@ private: //void visitBattleResultsApplied(BattleResultsApplied & pack) override; //void visitBattleResultAccepted(BattleResultAccepted & pack) override; //void visitTurnTimeUpdate(TurnTimeUpdate & pack) override; + //void visitResponseStatistic(ResponseStatistic & pack) override; public: PackRollbackGeneratorVisitor(const CGameState & gs) diff --git a/config/keyBindingsConfig.json b/config/keyBindingsConfig.json index 89fc60bed..e0edf9c6b 100644 --- a/config/keyBindingsConfig.json +++ b/config/keyBindingsConfig.json @@ -44,6 +44,7 @@ "adventureViewPuzzle": "P", "adventureViewScenario": "I", "adventureViewSelected": [ "Return", "Keypad Enter"], + "adventureViewStatistic": "J", "adventureViewWorld": "V", "adventureViewWorld1": "1", "adventureViewWorld2": "2", diff --git a/lib/callback/CCallback.cpp b/lib/callback/CCallback.cpp index f94c8cdf1..4722d0378 100644 --- a/lib/callback/CCallback.cpp +++ b/lib/callback/CCallback.cpp @@ -392,6 +392,12 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int sendRequest(cas); } +void CCallback::requestStatistic() +{ + RequestStatistic sr; + sendRequest(sr); +} + int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) { if(s1->getCreature(p1) == s2->getCreature(p2)) diff --git a/lib/callback/CCallback.h b/lib/callback/CCallback.h index 7eb6a42c0..0c48454d3 100644 --- a/lib/callback/CCallback.h +++ b/lib/callback/CCallback.h @@ -84,6 +84,7 @@ public: void buildBoat(const IShipyard *obj) override; void dig(const CGObjectInstance *hero) override; void castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos = int3(-1, -1, -1)) override; + void requestStatistic() override; //friends friend class CClient; diff --git a/lib/callback/IGameActionCallback.h b/lib/callback/IGameActionCallback.h index 8f5a9bcd9..8fc718780 100644 --- a/lib/callback/IGameActionCallback.h +++ b/lib/callback/IGameActionCallback.h @@ -76,6 +76,7 @@ public: virtual void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) = 0; virtual void gamePause(bool pause) = 0; virtual void buildBoat(const IShipyard *obj) = 0; + virtual void requestStatistic() = 0; // To implement high-level army management bulk actions virtual int bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) = 0; diff --git a/lib/callback/IGameEventsReceiver.h b/lib/callback/IGameEventsReceiver.h index 6626af3a6..c622e85b2 100644 --- a/lib/callback/IGameEventsReceiver.h +++ b/lib/callback/IGameEventsReceiver.h @@ -10,6 +10,7 @@ #pragma once #include "../constants/EntityIdentifiers.h" +#include "../gameState/GameStatistics.h" #include "../int3.h" VCMI_LIB_NAMESPACE_BEGIN @@ -97,6 +98,8 @@ public: virtual void playerStartsTurn(PlayerColor player){}; virtual void playerEndsTurn(PlayerColor player){}; + virtual void responseStatistic(StatisticDataSet & statistic){}; + //TODO shouldn't be moved down the tree? virtual void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID queryID){}; }; diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 1e62a7870..5b18ad4e0 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -153,6 +153,7 @@ public: virtual void visitMakeAction(MakeAction & pack) {} virtual void visitDigWithHero(DigWithHero & pack) {} virtual void visitCastAdvSpell(CastAdvSpell & pack) {} + virtual void visitRequestStatistic(RequestStatistic & pack) {} virtual void visitSaveGame(SaveGame & pack) {} virtual void visitPlayerMessage(PlayerMessage & pack) {} virtual void visitPlayerMessageClient(PlayerMessageClient & pack) {} @@ -188,6 +189,7 @@ public: virtual void visitBattleCancelled(BattleCancelled & pack) {} virtual void visitBattleResultAccepted(BattleResultAccepted & pack) {} virtual void visitBattleStackMoved(BattleLogMessage & pack) {} + virtual void visitResponseStatistic(ResponseStatistic & pack) {} }; VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 69a14b974..529e1c3d0 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -688,6 +688,11 @@ void CastAdvSpell::visitTyped(ICPackVisitor & visitor) visitor.visitCastAdvSpell(*this); } +void RequestStatistic::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitRequestStatistic(*this); +} + void SaveGame::visitTyped(ICPackVisitor & visitor) { visitor.visitSaveGame(*this); @@ -863,4 +868,9 @@ void TurnTimeUpdate::visitTyped(ICPackVisitor & visitor) visitor.visitTurnTimeUpdate(*this); } +void ResponseStatistic::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitResponseStatistic(*this); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 9c3fb54d6..b4a2b0637 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1523,4 +1523,18 @@ struct DLL_LINKAGE CenterView : public CPackForClient } }; +struct DLL_LINKAGE ResponseStatistic : public CPackForClient +{ + PlayerColor player; + StatisticDataSet statistic; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & player; + h & statistic; + } +}; + VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index f28d64c5f..8c62b9f2a 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -741,6 +741,16 @@ struct DLL_LINKAGE CastAdvSpell : public CPackForServer } }; +struct DLL_LINKAGE RequestStatistic : public CPackForServer +{ + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + } +}; + /***********************************************************************************************************/ struct DLL_LINKAGE SaveGame : public CPackForServer diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index a76b921a4..6b9bc0948 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -295,6 +295,8 @@ void registerTypes(Serializer &s) s.template registerType(253); s.template registerType(254); s.template registerType(255); + s.template registerType(256); + s.template registerType(257); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index afe63f1d0..73e4f2f33 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1582,6 +1582,30 @@ void CGameHandler::throwAndComplain(GameConnectionID connectionID, const std::st throwNotAllowedAction(connectionID); } +bool CGameHandler::responseStatistic(PlayerColor player) +{ + ResponseStatistic rs; + rs.statistic = *statistics; + rs.player = player; + + // Keep only team statistics, no enemy + const TeamState * team = gameState().getPlayerTeam(player); + + for(auto it = rs.statistic.accumulatedValues.begin(); it != rs.statistic.accumulatedValues.end();) { + if (std::find(team->players.begin(), team->players.end(), it->first) == team->players.end()) + it = rs.statistic.accumulatedValues.erase(it); + else + ++it; + } + rs.statistic.data.erase(std::remove_if(rs.statistic.data.begin(), rs.statistic.data.end(), [&team](const StatisticDataSetEntry& entry) { + return std::find(team->players.begin(), team->players.end(), entry.player) == team->players.end(); + }), rs.statistic.data.end()); + + sendAndApply(rs); + + return true; +} + void CGameHandler::save(const std::string & filename) { logGlobal->info("Saving to %s", filename); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e0e7c791e..c26320e0d 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -237,6 +237,7 @@ public: bool bulkSplitStack(SlotID src, ObjectInstanceID srcOwner, si32 howMany); bool bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner); bool bulkSplitAndRebalanceStack(SlotID slotSrc, ObjectInstanceID srcOwner); + bool responseStatistic(PlayerColor player); void save(const std::string &fname); void load(const StartInfo &info); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index e7cde39c3..70ef7b765 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -441,6 +441,13 @@ void ApplyGhNetPackVisitor::visitCastAdvSpell(CastAdvSpell & pack) result = s->adventureCast(gh.spellEnv.get(), p); } +void ApplyGhNetPackVisitor::visitRequestStatistic(RequestStatistic & pack) +{ + gh.throwIfPlayerNotActive(connection, &pack); + + result = gh.responseStatistic(pack.player); +} + void ApplyGhNetPackVisitor::visitPlayerMessage(PlayerMessage & pack) { if(!pack.player.isSpectator()) // TODO: clearly not a great way to verify permissions diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 3b971fa7d..003f7a3e4 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -65,6 +65,7 @@ public: void visitMakeAction(MakeAction & pack) override; void visitDigWithHero(DigWithHero & pack) override; void visitCastAdvSpell(CastAdvSpell & pack) override; + void visitRequestStatistic(RequestStatistic & pack) override; void visitPlayerMessage(PlayerMessage & pack) override; void visitSaveLocalState(SaveLocalState & pack) override; };