mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
Merge branch 'develop' into resource_generation
This commit is contained in:
commit
02002ce69d
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=armeabi-v7a
|
||||
DEPS_FILENAME=dependencies-android-32
|
||||
. CI/android/before_install.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=aarch64-v8a
|
||||
DEPS_FILENAME=dependencies-android-64
|
||||
. CI/android/before_install.sh
|
||||
|
@ -4,6 +4,4 @@ echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-dependencies/releases/download/android-1.1/$DEPS_FILENAME.txz" \
|
||||
| tar -xf - --xz
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
9
CI/install_conan_dependencies.sh
Normal file
9
CI/install_conan_dependencies.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
RELEASE_TAG="1.2"
|
||||
FILENAME="$1"
|
||||
DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz"
|
||||
|
||||
mkdir ~/.conan
|
||||
cd ~/.conan
|
||||
curl -L "$DOWNLOAD_URL" | tar -xf - --xz
|
@ -2,6 +2,4 @@
|
||||
|
||||
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2.1/ios-arm64.txz' \
|
||||
| tar -xf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-ios"
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=intel-cross-arm
|
||||
DEPS_FILENAME=dependencies-mac-arm
|
||||
. CI/mac/before_install.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=intel
|
||||
DEPS_FILENAME=dependencies-mac-intel
|
||||
. CI/mac/before_install.sh
|
||||
|
@ -4,6 +4,4 @@ echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2.1/$DEPS_FILENAME.txz" \
|
||||
| tar -xf -
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
||||
|
@ -11,6 +11,4 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb;
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.2/vcmi-deps-windows-conan-w32.tgz" \
|
||||
| tar -xzf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw-32"
|
||||
|
@ -11,6 +11,4 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-x86-64-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb;
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.2/vcmi-deps-windows-conan-w64.tgz" \
|
||||
| tar -xzf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw"
|
||||
|
@ -162,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Town Screen",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistics",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Data to clipboard",
|
||||
"vcmi.statisticWindow.selectView" : "Select view",
|
||||
"vcmi.statisticWindow.value" : "Value",
|
||||
"vcmi.statisticWindow.title.overview" : "Overview",
|
||||
"vcmi.statisticWindow.title.resources" : "Resources",
|
||||
"vcmi.statisticWindow.title.income" : "Income",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "No. of heroes",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "No. of towns",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "No. of artifacts",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "No. of dwellings",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "No. of mines",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Army strength",
|
||||
"vcmi.statisticWindow.title.experience" : "Experience",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Army costs",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Building costs",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Map explore ratio",
|
||||
"vcmi.statisticWindow.param.playerName" : "Player name",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Days survived",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Max hero level",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Win ratio (vs. hero)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Win ratio (vs. neutral)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Battles (vs. hero)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Battles (vs. neutral)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Max total army strength",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Trade volume",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisk visited",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Town captured",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Strongest hero of opponent defeated",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Grail found",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Defeated",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Fullscreen (exclusive)",
|
||||
|
@ -162,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Andere Einstellungen", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistik",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Daten in Zwischenabl.",
|
||||
"vcmi.statisticWindow.selectView" : "Ansicht wählen",
|
||||
"vcmi.statisticWindow.value" : "Wert",
|
||||
"vcmi.statisticWindow.title.overview" : "Überblick",
|
||||
"vcmi.statisticWindow.title.resources" : "Ressourcen",
|
||||
"vcmi.statisticWindow.title.income" : "Einkommen",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Nr. der Helden",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Nr. der Städte",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Nr. der Artefakte",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Nr. der Behausungen",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Nr. der Minen",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Armeestärke",
|
||||
"vcmi.statisticWindow.title.experience" : "Erfahrung",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Armeekosten",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Gebäudekosten",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Maperkundungsrate",
|
||||
"vcmi.statisticWindow.param.playerName" : "Spielername",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Tage überlebt",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Max Heldenlevel",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Sieg Verh. (Helden)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Sieg Verh. (Neutral)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Kämpfe (Helden)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Kämpfe (Neutral)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Max Gesamt-Armeestärke",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Handelsvol.",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisk besucht",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Stadt erobert",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Stärksten Helden eines Gegners besiegt",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Gral gefunden",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Besiegt",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Vollbild (randlos)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Randloses Vollbild}\n\nWenn diese Option ausgewählt ist, wird VCMI im randlosen Vollbildmodus ausgeführt. In diesem Modus wird das Spiel immer dieselbe Auflösung wie der Desktop verwenden und die gewählte Auflösung ignorieren.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Vollbild (exklusiv)",
|
||||
|
@ -65,6 +65,7 @@ set(client_SRCS
|
||||
mainmenu/CPrologEpilogVideo.cpp
|
||||
mainmenu/CreditsScreen.cpp
|
||||
mainmenu/CHighScoreScreen.cpp
|
||||
mainmenu/CStatisticScreen.cpp
|
||||
|
||||
mapView/MapRenderer.cpp
|
||||
mapView/MapRendererContext.cpp
|
||||
@ -261,6 +262,7 @@ set(client_HEADERS
|
||||
mainmenu/CPrologEpilogVideo.h
|
||||
mainmenu/CreditsScreen.h
|
||||
mainmenu/CHighScoreScreen.h
|
||||
mainmenu/CStatisticScreen.h
|
||||
|
||||
mapView/IMapRendererContext.h
|
||||
mapView/IMapRendererObserver.h
|
||||
|
@ -673,13 +673,13 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
|
||||
setState(EClientState::GAMEPLAY);
|
||||
}
|
||||
|
||||
void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victory)
|
||||
void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victory, const StatisticDataSet & statistic)
|
||||
{
|
||||
HighScoreParameter param = HighScore::prepareHighScores(client->gameState(), player, victory);
|
||||
|
||||
if(victory && client->gameState()->getStartInfo()->campState)
|
||||
{
|
||||
startCampaignScenario(param, client->gameState()->getStartInfo()->campState);
|
||||
startCampaignScenario(param, client->gameState()->getStartInfo()->campState, statistic);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -689,7 +689,7 @@ void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victo
|
||||
|
||||
endGameplay();
|
||||
CMM->menu->switchToTab("main");
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(victory, scenarioHighScores);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(victory, scenarioHighScores, statistic);
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,7 +722,7 @@ void CServerHandler::restartGameplay()
|
||||
logicConnection->enterLobbyConnectionMode();
|
||||
}
|
||||
|
||||
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs)
|
||||
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs, const StatisticDataSet & statistic)
|
||||
{
|
||||
std::shared_ptr<CampaignState> ourCampaign = cs;
|
||||
|
||||
@ -738,7 +738,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
||||
endGameplay();
|
||||
|
||||
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
|
||||
auto finisher = [ourCampaign, campaignScoreCalculator]()
|
||||
auto finisher = [ourCampaign, campaignScoreCalculator, statistic]()
|
||||
{
|
||||
if(ourCampaign->campaignSet != "" && ourCampaign->isCampaignFinished())
|
||||
{
|
||||
@ -754,7 +754,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
||||
else
|
||||
{
|
||||
CMM->openCampaignScreen(ourCampaign->campaignSet);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "../lib/network/NetworkInterface.h"
|
||||
#include "../lib/StartInfo.h"
|
||||
#include "../lib/gameState/GameStatistics.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -204,11 +205,11 @@ public:
|
||||
void debugStartTest(std::string filename, bool save = false);
|
||||
|
||||
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
|
||||
void showHighScoresAndEndGameplay(PlayerColor player, bool victory);
|
||||
void showHighScoresAndEndGameplay(PlayerColor player, bool victory, const StatisticDataSet & statistic);
|
||||
void endNetwork();
|
||||
void endGameplay();
|
||||
void restartGameplay();
|
||||
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});
|
||||
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs, const StatisticDataSet & statistic);
|
||||
void showServerError(const std::string & txt) const;
|
||||
|
||||
// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
|
||||
|
@ -420,7 +420,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
|
||||
adventureInt.reset();
|
||||
}
|
||||
|
||||
CSH->showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory());
|
||||
CSH->showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
|
||||
}
|
||||
|
||||
// In auto testing pack.mode we always close client if red pack.player won or lose
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <SDL_timer.h>
|
||||
#include <SDL_clipboard.h>
|
||||
|
||||
InputHandler::InputHandler()
|
||||
: enableMouse(settings["input"]["enableMouse"].Bool())
|
||||
@ -142,6 +143,11 @@ InputMode InputHandler::getCurrentInputMode()
|
||||
return currentInputMode;
|
||||
}
|
||||
|
||||
void InputHandler::copyToClipBoard(const std::string & text)
|
||||
{
|
||||
SDL_SetClipboardText(text.c_str());
|
||||
}
|
||||
|
||||
std::vector<SDL_Event> InputHandler::acquireEvents()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(eventsMutex);
|
||||
|
@ -103,4 +103,6 @@ public:
|
||||
bool isKeyboardShiftDown() const;
|
||||
|
||||
InputMode getCurrentInputMode();
|
||||
|
||||
void copyToClipBoard(const std::string & text);
|
||||
};
|
||||
|
@ -156,12 +156,17 @@ void CIntObject::setRedrawParent(bool on)
|
||||
}
|
||||
|
||||
void CIntObject::fitToScreen(int borderWidth, bool propagate)
|
||||
{
|
||||
fitToRect(Rect(Point(0, 0), GH.screenDimensions()), borderWidth, propagate);
|
||||
}
|
||||
|
||||
void CIntObject::fitToRect(Rect rect, int borderWidth, bool propagate)
|
||||
{
|
||||
Point newPos = pos.topLeft();
|
||||
vstd::amax(newPos.x, borderWidth);
|
||||
vstd::amax(newPos.y, borderWidth);
|
||||
vstd::amin(newPos.x, GH.screenDimensions().x - borderWidth - pos.w);
|
||||
vstd::amin(newPos.y, GH.screenDimensions().y - borderWidth - pos.h);
|
||||
vstd::amax(newPos.x, rect.x + borderWidth);
|
||||
vstd::amax(newPos.y, rect.y + borderWidth);
|
||||
vstd::amin(newPos.x, rect.x + rect.w - borderWidth - pos.w);
|
||||
vstd::amin(newPos.y, rect.y + rect.h - borderWidth - pos.h);
|
||||
if (newPos != pos.topLeft())
|
||||
moveTo(newPos, propagate);
|
||||
}
|
||||
|
@ -122,6 +122,7 @@ public:
|
||||
const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center
|
||||
const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position
|
||||
void fitToScreen(int borderWidth, bool propagate = true); //moves window to fit into screen
|
||||
void fitToRect(Rect rect, int borderWidth, bool propagate = true); //moves window to fit into rect
|
||||
void moveBy(const Point &p, bool propagate = true);
|
||||
void moveTo(const Point &p, bool propagate = true);//move this to new position, coordinates are absolute (0,0 is topleft screen corner)
|
||||
|
||||
|
@ -74,6 +74,7 @@ enum class EShortcut
|
||||
HIGH_SCORES_CAMPAIGNS,
|
||||
HIGH_SCORES_SCENARIOS,
|
||||
HIGH_SCORES_RESET,
|
||||
HIGH_SCORES_STATISTICS,
|
||||
|
||||
// Game lobby / scenario selection
|
||||
LOBBY_BEGIN_STANDARD_GAME, // b
|
||||
|
@ -290,6 +290,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"highScoresCampaigns", EShortcut::HIGH_SCORES_CAMPAIGNS },
|
||||
{"highScoresScenarios", EShortcut::HIGH_SCORES_SCENARIOS },
|
||||
{"highScoresReset", EShortcut::HIGH_SCORES_RESET },
|
||||
{"highScoresStatistics", EShortcut::HIGH_SCORES_STATISTICS },
|
||||
{"lobbyReplayVideo", EShortcut::LOBBY_REPLAY_VIDEO },
|
||||
{"lobbyExtraOptions", EShortcut::LOBBY_EXTRA_OPTIONS },
|
||||
{"lobbyTurnOptions", EShortcut::LOBBY_TURN_OPTIONS },
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "StdInc.h"
|
||||
|
||||
#include "CHighScoreScreen.h"
|
||||
#include "CStatisticScreen.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
@ -33,6 +34,7 @@
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/constants/EntityIdentifiers.h"
|
||||
#include "../../lib/gameState/HighScore.h"
|
||||
#include "../../lib/gameState/GameStatistics.h"
|
||||
|
||||
CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
|
||||
: CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted)
|
||||
@ -170,8 +172,8 @@ void CHighScoreScreen::buttonExitClick()
|
||||
close();
|
||||
}
|
||||
|
||||
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc)
|
||||
: CWindowObject(BORDERED), won(won), calc(calc)
|
||||
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic)
|
||||
: CWindowObject(BORDERED), won(won), calc(calc), stat(statistic)
|
||||
{
|
||||
addUsedEvents(LCLICK | KEYBOARD);
|
||||
|
||||
@ -204,6 +206,12 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
|
||||
videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), VideoPath::builtin("LOSEGAME.SMK"), true, [this](){close();});
|
||||
CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
|
||||
}
|
||||
|
||||
if (settings["general"]["enableUiEnhancements"].Bool())
|
||||
{
|
||||
statisticButton = std::make_shared<CButton>(Point(726, 10), AnimationPath::builtin("TPTAV02.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.statisticWindow.statistics")), [this](){ GH.windows().createAndPushWindow<CStatisticScreen>(stat); }, EShortcut::HIGH_SCORES_STATISTICS);
|
||||
texts.push_back(std::make_shared<CLabel>(716, 25, EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTERRIGHT, Colors::WHITE, CGI->generaltexth->translate("vcmi.statisticWindow.statistics") + ":"));
|
||||
}
|
||||
}
|
||||
|
||||
int CHighScoreInputScreen::addEntry(std::string text) {
|
||||
@ -253,6 +261,9 @@ void CHighScoreInputScreen::show(Canvas & to)
|
||||
|
||||
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
if(statisticButton->pos.isInside(cursorPosition))
|
||||
return;
|
||||
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
if(!won)
|
||||
@ -280,6 +291,8 @@ void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
|
||||
|
||||
void CHighScoreInputScreen::keyPressed(EShortcut key)
|
||||
{
|
||||
if(key == EShortcut::HIGH_SCORES_STATISTICS) // ignore shortcut for skipping video with key
|
||||
return;
|
||||
clickPressed(Point());
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
#include "../windows/CWindowObject.h"
|
||||
#include "../../lib/gameState/HighScore.h"
|
||||
#include "../../lib/gameState/GameStatistics.h"
|
||||
|
||||
class CButton;
|
||||
class CLabel;
|
||||
@ -70,16 +71,19 @@ public:
|
||||
|
||||
class CHighScoreInputScreen : public CWindowObject
|
||||
{
|
||||
std::vector<std::shared_ptr<CMultiLineLabel>> texts;
|
||||
std::vector<std::shared_ptr<CLabel>> texts;
|
||||
std::shared_ptr<CHighScoreInput> input;
|
||||
std::shared_ptr<TransparentFilledRectangle> background;
|
||||
std::shared_ptr<VideoWidgetBase> videoPlayer;
|
||||
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
|
||||
|
||||
std::shared_ptr<CButton> statisticButton;
|
||||
|
||||
bool won;
|
||||
HighScoreCalculation calc;
|
||||
StatisticDataSet stat;
|
||||
public:
|
||||
CHighScoreInputScreen(bool won, HighScoreCalculation calc);
|
||||
CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic);
|
||||
|
||||
int addEntry(std::string text);
|
||||
|
||||
|
521
client/mainmenu/CStatisticScreen.cpp
Normal file
521
client/mainmenu/CStatisticScreen.cpp
Normal file
@ -0,0 +1,521 @@
|
||||
/*
|
||||
* CStatisticScreen.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
|
||||
#include "CStatisticScreen.h"
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
|
||||
#include "../render/Graphics.h"
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
#include "../widgets/ComboBox.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../widgets/Slider.h"
|
||||
|
||||
#include "../../lib/gameState/GameStatistics.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
|
||||
#include <vstd/DateUtils.h>
|
||||
|
||||
std::string CStatisticScreen::getDay(int d)
|
||||
{
|
||||
return std::to_string(CGameState::getDate(d, Date::MONTH)) + "/" + std::to_string(CGameState::getDate(d, Date::WEEK)) + "/" + std::to_string(CGameState::getDate(d, Date::DAY_OF_WEEK));
|
||||
}
|
||||
|
||||
CStatisticScreen::CStatisticScreen(const StatisticDataSet & stat)
|
||||
: CWindowObject(BORDERED), statistic(stat)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos = center(Rect(0, 0, 800, 600));
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
filledBackground->setPlayerColor(PlayerColor(1));
|
||||
|
||||
contentArea = Rect(10, 40, 780, 510);
|
||||
layout.emplace_back(std::make_shared<CLabel>(400, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->translate("vcmi.statisticWindow.statistics")));
|
||||
layout.emplace_back(std::make_shared<TransparentFilledRectangle>(contentArea, ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 80, 128, 255), 1));
|
||||
layout.emplace_back(std::make_shared<CButton>(Point(725, 558), AnimationPath::builtin("MUBCHCK"), CButton::tooltip(), [this](){ close(); }, EShortcut::GLOBAL_ACCEPT));
|
||||
|
||||
buttonSelect = std::make_shared<CToggleButton>(Point(10, 564), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), [this](bool on){ onSelectButton(); });
|
||||
buttonSelect->setTextOverlay(CGI->generaltexth->translate("vcmi.statisticWindow.selectView"), EFonts::FONT_SMALL, Colors::YELLOW);
|
||||
|
||||
buttonCsvSave = std::make_shared<CToggleButton>(Point(150, 564), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), [this](bool on){ GH.input().copyToClipBoard(statistic.toCsv("\t")); });
|
||||
buttonCsvSave->setTextOverlay(CGI->generaltexth->translate("vcmi.statisticWindow.tsvCopy"), EFonts::FONT_SMALL, Colors::YELLOW);
|
||||
|
||||
mainContent = getContent(OVERVIEW, EGameResID::NONE);
|
||||
}
|
||||
|
||||
void CStatisticScreen::onSelectButton()
|
||||
{
|
||||
std::vector<std::string> texts;
|
||||
for(auto & val : contentInfo)
|
||||
texts.emplace_back(CGI->generaltexth->translate(std::get<0>(val.second)));
|
||||
GH.windows().createAndPushWindow<StatisticSelector>(texts, [this](int selectedIndex)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(!std::get<1>(contentInfo[static_cast<Content>(selectedIndex)]))
|
||||
mainContent = getContent(static_cast<Content>(selectedIndex), EGameResID::NONE);
|
||||
else
|
||||
{
|
||||
auto content = static_cast<Content>(selectedIndex);
|
||||
auto possibleRes = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS};
|
||||
std::vector<std::string> resourceText;
|
||||
for(const auto & res : possibleRes)
|
||||
resourceText.emplace_back(CGI->generaltexth->translate(TextIdentifier("core.restypes", res.getNum()).get()));
|
||||
|
||||
GH.windows().createAndPushWindow<StatisticSelector>(resourceText, [this, content, possibleRes](int index)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
mainContent = getContent(content, possibleRes[index]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TData CStatisticScreen::extractData(const StatisticDataSet & stat, const ExtractFunctor & selector) const
|
||||
{
|
||||
auto tmpData = stat.data;
|
||||
std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
|
||||
|
||||
PlayerColor tmpColor = PlayerColor::NEUTRAL;
|
||||
std::vector<float> tmpColorSet;
|
||||
TData plotData;
|
||||
EPlayerStatus statusLastRound = EPlayerStatus::INGAME;
|
||||
for(const auto & val : tmpData)
|
||||
{
|
||||
if(tmpColor != val.player)
|
||||
{
|
||||
if(tmpColorSet.size())
|
||||
{
|
||||
plotData.push_back({graphics->playerColors[tmpColor.getNum()], std::vector<float>(tmpColorSet)});
|
||||
tmpColorSet.clear();
|
||||
}
|
||||
|
||||
tmpColor = val.player;
|
||||
}
|
||||
if(val.status == EPlayerStatus::INGAME || (statusLastRound == EPlayerStatus::INGAME && val.status == EPlayerStatus::LOSER))
|
||||
tmpColorSet.emplace_back(selector(val));
|
||||
statusLastRound = val.status; //to keep at least one dataset after loose
|
||||
}
|
||||
if(tmpColorSet.size())
|
||||
plotData.push_back({graphics->playerColors[tmpColor.getNum()], std::vector<float>(tmpColorSet)});
|
||||
|
||||
return plotData;
|
||||
}
|
||||
|
||||
TIcons CStatisticScreen::extractIcons() const
|
||||
{
|
||||
TIcons icons;
|
||||
|
||||
auto tmpData = statistic.data;
|
||||
std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
|
||||
|
||||
auto imageTown = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 3, 0, EImageBlitMode::COLORKEY);
|
||||
imageTown->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageBattle = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 5, 0, EImageBlitMode::COLORKEY);
|
||||
imageBattle->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("tpthchk"), 1, 0, EImageBlitMode::COLORKEY);
|
||||
imageDefeated->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageGrail = GH.renderHandler().loadImage(AnimationPath::builtin("vwsymbol"), 2, 0, EImageBlitMode::COLORKEY);
|
||||
imageGrail->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
|
||||
std::map<PlayerColor, bool> foundDefeated;
|
||||
std::map<PlayerColor, bool> foundGrail;
|
||||
|
||||
for(const auto & val : tmpData)
|
||||
{
|
||||
if(val.eventCapturedTown)
|
||||
icons.push_back({ graphics->playerColors[val.player], val.day, imageTown, CGI->generaltexth->translate("vcmi.statisticWindow.icon.townCaptured") });
|
||||
if(val.eventDefeatedStrongestHero)
|
||||
icons.push_back({ graphics->playerColors[val.player], val.day, imageBattle, CGI->generaltexth->translate("vcmi.statisticWindow.icon.strongestHeroDefeated") });
|
||||
if(val.status == EPlayerStatus::LOSER && !foundDefeated[val.player])
|
||||
{
|
||||
foundDefeated[val.player] = true;
|
||||
icons.push_back({ graphics->playerColors[val.player], val.day, imageDefeated, CGI->generaltexth->translate("vcmi.statisticWindow.icon.defeated") });
|
||||
}
|
||||
if(val.hasGrail && !foundGrail[val.player])
|
||||
{
|
||||
foundGrail[val.player] = true;
|
||||
icons.push_back({ graphics->playerColors[val.player], val.day, imageGrail, CGI->generaltexth->translate("vcmi.statisticWindow.icon.grailFound") });
|
||||
}
|
||||
}
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
std::shared_ptr<CIntObject> CStatisticScreen::getContent(Content c, EGameResID res)
|
||||
{
|
||||
TData plotData;
|
||||
TIcons icons = extractIcons();
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case OVERVIEW:
|
||||
return std::make_shared<OverviewPanel>(contentArea.resize(-15), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), statistic);
|
||||
|
||||
case CHART_RESOURCES:
|
||||
plotData = extractData(statistic, [res](const StatisticDataSetEntry & val) -> float { return val.resources[res]; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])) + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", res.getNum()).get()), plotData, icons, 0);
|
||||
|
||||
case CHART_INCOME:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.income; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_NUMBER_OF_HEROES:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.numberHeroes; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_NUMBER_OF_TOWNS:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.numberTowns; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_NUMBER_OF_ARTIFACTS:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.numberArtifacts; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_NUMBER_OF_DWELLINGS:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.numberDwellings; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_NUMBER_OF_MINES:
|
||||
plotData = extractData(statistic, [res](StatisticDataSetEntry val) -> float { return val.numMines[res]; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])) + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", res.getNum()).get()), plotData, icons, 0);
|
||||
|
||||
case CHART_ARMY_STRENGTH:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.armyStrength; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_EXPERIENCE:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.totalExperience; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 0);
|
||||
|
||||
case CHART_RESOURCES_SPENT_ARMY:
|
||||
plotData = extractData(statistic, [res](const StatisticDataSetEntry & val) -> float { return val.spentResourcesForArmy[res]; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])) + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", res.getNum()).get()), plotData, icons, 0);
|
||||
|
||||
case CHART_RESOURCES_SPENT_BUILDINGS:
|
||||
plotData = extractData(statistic, [res](const StatisticDataSetEntry & val) -> float { return val.spentResourcesForBuildings[res]; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])) + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", res.getNum()).get()), plotData, icons, 0);
|
||||
|
||||
case CHART_MAP_EXPLORED:
|
||||
plotData = extractData(statistic, [](const StatisticDataSetEntry & val) -> float { return val.mapExploredRatio; });
|
||||
return std::make_shared<LineChart>(contentArea.resize(-5), CGI->generaltexth->translate(std::get<0>(contentInfo[c])), plotData, icons, 1);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StatisticSelector::StatisticSelector(const std::vector<std::string> & texts, const std::function<void(int selectedIndex)> & cb)
|
||||
: CWindowObject(BORDERED | NEEDS_ANIMATED_BACKGROUND), texts(texts), cb(cb)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos = center(Rect(0, 0, 128 + 16, std::min(static_cast<int>(texts.size()), LINES) * 40));
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
|
||||
filledBackground->setPlayerColor(PlayerColor(1));
|
||||
|
||||
slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, [this](int to){ update(to); redraw(); }, LINES, texts.size(), 0, Orientation::VERTICAL, CSlider::BLUE);
|
||||
slider->setPanningStep(40);
|
||||
slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, pos.w, pos.h));
|
||||
|
||||
update(0);
|
||||
}
|
||||
|
||||
void StatisticSelector::update(int to)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
buttons.clear();
|
||||
for(int i = to; i < LINES + to; i++)
|
||||
{
|
||||
if(i>=texts.size())
|
||||
continue;
|
||||
|
||||
auto button = std::make_shared<CToggleButton>(Point(0, 10 + (i - to) * 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), [this, i](bool on){ close(); cb(i); });
|
||||
button->setTextOverlay(texts[i], EFonts::FONT_SMALL, Colors::WHITE);
|
||||
buttons.emplace_back(button);
|
||||
}
|
||||
}
|
||||
|
||||
OverviewPanel::OverviewPanel(Rect position, std::string title, const StatisticDataSet & stat)
|
||||
: CIntObject(), data(stat)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
pos = position + pos.topLeft();
|
||||
|
||||
layout.emplace_back(std::make_shared<CLabel>(pos.w / 2, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, title));
|
||||
|
||||
canvas = std::make_shared<GraphicalPrimitiveCanvas>(Rect(0, Y_OFFS, pos.w - 16, pos.h - Y_OFFS));
|
||||
|
||||
dataExtract = {
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.playerName"), [this](PlayerColor color){
|
||||
return playerDataFilter(color).front().playerName;
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.daysSurvived"), [this](PlayerColor color){
|
||||
return CStatisticScreen::getDay(playerDataFilter(color).size());
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.maxHeroLevel"), [this](PlayerColor color){
|
||||
int maxLevel = 0;
|
||||
for(const auto & val : playerDataFilter(color))
|
||||
if(maxLevel < val.maxHeroLevel)
|
||||
maxLevel = val.maxHeroLevel;
|
||||
return std::to_string(maxLevel);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.battleWinRatioHero"), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
if(!val.numBattlesPlayer)
|
||||
return std::string("");
|
||||
float tmp = (static_cast<float>(val.numWinBattlesPlayer) / static_cast<float>(val.numBattlesPlayer)) * 100;
|
||||
return std::to_string(static_cast<int>(tmp)) + " %";
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.battleWinRatioNeutral"), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
if(!val.numWinBattlesNeutral)
|
||||
return std::string("");
|
||||
float tmp = (static_cast<float>(val.numWinBattlesNeutral) / static_cast<float>(val.numBattlesNeutral)) * 100;
|
||||
return std::to_string(static_cast<int>(tmp)) + " %";
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.battlesHero"), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.numBattlesPlayer);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.battlesNeutral"), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.numBattlesNeutral);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.obeliskVisited"), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(static_cast<int>(val.obeliskVisitedRatio * 100)) + " %";
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.maxArmyStrength"), [this](PlayerColor color){
|
||||
int maxArmyStrength = 0;
|
||||
for(const auto & val : playerDataFilter(color))
|
||||
if(maxArmyStrength < val.armyStrength)
|
||||
maxArmyStrength = val.armyStrength;
|
||||
return TextOperations::formatMetric(maxArmyStrength, 6);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::GOLD).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::GOLD]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::WOOD).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::WOOD]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::MERCURY).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::MERCURY]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::ORE).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::ORE]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::SULFUR).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::SULFUR]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::CRYSTAL).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::CRYSTAL]);
|
||||
}
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.tradeVolume") + " - " + CGI->generaltexth->translate(TextIdentifier("core.restypes", EGameResID::GEMS).get()), [this](PlayerColor color){
|
||||
auto val = playerDataFilter(color).back();
|
||||
return std::to_string(val.tradeVolume[EGameResID::GEMS]);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
int usedLines = dataExtract.size();
|
||||
|
||||
slider = std::make_shared<CSlider>(Point(pos.w - 16, Y_OFFS), pos.h - Y_OFFS, [this](int to){ update(to); setRedrawParent(true); redraw(); }, LINES - 1, usedLines, 0, Orientation::VERTICAL, CSlider::BLUE);
|
||||
slider->setPanningStep(canvas->pos.h / LINES);
|
||||
slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, pos.w, canvas->pos.h));
|
||||
|
||||
fieldSize = Point(canvas->pos.w / (graphics->playerColors.size() + 2), canvas->pos.h / LINES);
|
||||
for(int x = 0; x < graphics->playerColors.size() + 1; x++)
|
||||
for(int y = 0; y < LINES; y++)
|
||||
{
|
||||
int xStart = (x + (x == 0 ? 0 : 1)) * fieldSize.x;
|
||||
int yStart = y * fieldSize.y;
|
||||
if(x == 0 || y == 0)
|
||||
canvas->addBox(Point(xStart, yStart), Point(x == 0 ? 2 * fieldSize.x : fieldSize.x, fieldSize.y), ColorRGBA(0, 0, 0, 100));
|
||||
canvas->addRectangle(Point(xStart, yStart), Point(x == 0 ? 2 * fieldSize.x : fieldSize.x, fieldSize.y), ColorRGBA(127, 127, 127, 255));
|
||||
}
|
||||
|
||||
update(0);
|
||||
}
|
||||
|
||||
std::vector<StatisticDataSetEntry> OverviewPanel::playerDataFilter(PlayerColor color)
|
||||
{
|
||||
std::vector<StatisticDataSetEntry> tmpData;
|
||||
std::copy_if(data.data.begin(), data.data.end(), std::back_inserter(tmpData), [color](const StatisticDataSetEntry & e){ return e.player == color; });
|
||||
return tmpData;
|
||||
}
|
||||
|
||||
void OverviewPanel::update(int to)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
content.clear();
|
||||
for(int y = to; y < LINES - 1 + to; y++)
|
||||
{
|
||||
if(y >= dataExtract.size())
|
||||
continue;
|
||||
|
||||
for(int x = 0; x < PlayerColor::PLAYER_LIMIT_I + 1; x++)
|
||||
{
|
||||
if(y == to && x < PlayerColor::PLAYER_LIMIT_I)
|
||||
content.emplace_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), x, 0, 180 + x * fieldSize.x, 35));
|
||||
int xStart = (x + (x == 0 ? 0 : 1)) * fieldSize.x + (x == 0 ? fieldSize.x : (fieldSize.x / 2));
|
||||
int yStart = Y_OFFS + (y + 1 - to) * fieldSize.y + (fieldSize.y / 2);
|
||||
PlayerColor tmpColor(x - 1);
|
||||
if(playerDataFilter(tmpColor).size() || x == 0)
|
||||
content.emplace_back(std::make_shared<CLabel>(xStart, yStart, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, (x == 0 ? dataExtract[y].first : dataExtract[y].second(tmpColor)), x == 0 ? (fieldSize.x * 2) : fieldSize.x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY)
|
||||
: CIntObject(), maxVal(0), maxDay(0)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
addUsedEvents(LCLICK | MOVE);
|
||||
|
||||
pos = position + pos.topLeft();
|
||||
|
||||
layout.emplace_back(std::make_shared<CLabel>(pos.w / 2, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, title));
|
||||
|
||||
chartArea = pos.resize(-50);
|
||||
chartArea.moveTo(Point(50, 50));
|
||||
|
||||
canvas = std::make_shared<GraphicalPrimitiveCanvas>(Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
statusBar = CGStatusBar::create(0, 0, ImagePath::builtin("radialMenu/statusBar"));
|
||||
static_cast<std::shared_ptr<CIntObject>>(statusBar)->setEnabled(false);
|
||||
|
||||
// additional calculations
|
||||
bool skipMaxValCalc = maxY > 0;
|
||||
maxVal = maxY;
|
||||
for(const auto & line : data)
|
||||
{
|
||||
for(auto & val : line.second)
|
||||
if(maxVal < val && !skipMaxValCalc)
|
||||
maxVal = val;
|
||||
if(maxDay < line.second.size())
|
||||
maxDay = line.second.size();
|
||||
}
|
||||
|
||||
// draw
|
||||
for(const auto & line : data)
|
||||
{
|
||||
Point lastPoint(-1, -1);
|
||||
for(int i = 0; i < line.second.size(); i++)
|
||||
{
|
||||
float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
|
||||
float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / maxVal) * line.second[i];
|
||||
Point p = Point(x, y) + chartArea.topLeft();
|
||||
|
||||
if(lastPoint.x != -1)
|
||||
canvas->addLine(lastPoint, p, line.first);
|
||||
|
||||
// icons
|
||||
for(auto & icon : icons)
|
||||
if(std::get<0>(icon) == line.first && std::get<1>(icon) == i + 1) // color && day
|
||||
{
|
||||
pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(x - (CHART_ICON_SIZE / 2), y - (CHART_ICON_SIZE / 2)) + chartArea.topLeft()));
|
||||
pictures.back()->addRClickCallback([icon](){ CRClickPopup::createAndPush(std::get<3>(icon)); });
|
||||
}
|
||||
|
||||
lastPoint = p;
|
||||
}
|
||||
}
|
||||
|
||||
// Axis
|
||||
canvas->addLine(chartArea.topLeft() + Point(0, -10), chartArea.topLeft() + Point(0, chartArea.h + 10), Colors::WHITE);
|
||||
canvas->addLine(chartArea.topLeft() + Point(-10, chartArea.h), chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h), Colors::WHITE);
|
||||
|
||||
Point p = chartArea.topLeft() + Point(-5, chartArea.h + 10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, "0"));
|
||||
p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CStatisticScreen::getDay(maxDay)));
|
||||
p = chartArea.topLeft() + Point(-5, -10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, std::to_string(static_cast<int>(maxVal))));
|
||||
p = chartArea.bottomLeft() + Point(chartArea.w / 2, + 20);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.64")));
|
||||
}
|
||||
|
||||
void LineChart::updateStatusBar(const Point & cursorPosition)
|
||||
{
|
||||
statusBar->moveTo(cursorPosition + Point(-statusBar->pos.w / 2, 20));
|
||||
statusBar->fitToRect(pos, 10);
|
||||
Rect r(pos.x + chartArea.x, pos.y + chartArea.y, chartArea.w, chartArea.h);
|
||||
statusBar->setEnabled(r.isInside(cursorPosition));
|
||||
if(r.isInside(cursorPosition))
|
||||
{
|
||||
float x = (static_cast<float>(maxDay) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
|
||||
float y = maxVal - (maxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
|
||||
statusBar->write(CGI->generaltexth->translate("core.genrltxt.64") + ": " + CStatisticScreen::getDay(x) + " " + CGI->generaltexth->translate("vcmi.statisticWindow.value") + ": " + (static_cast<int>(y) > 0 ? std::to_string(static_cast<int>(y)) : std::to_string(y)));
|
||||
}
|
||||
setRedrawParent(true);
|
||||
redraw();
|
||||
}
|
||||
|
||||
void LineChart::mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance)
|
||||
{
|
||||
updateStatusBar(cursorPosition);
|
||||
}
|
||||
|
||||
void LineChart::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
updateStatusBar(cursorPosition);
|
||||
}
|
134
client/mainmenu/CStatisticScreen.h
Normal file
134
client/mainmenu/CStatisticScreen.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* CStatisticScreen.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include "../windows/CWindowObject.h"
|
||||
#include "../../lib/gameState/GameStatistics.h"
|
||||
|
||||
class FilledTexturePlayerColored;
|
||||
class CToggleButton;
|
||||
class GraphicalPrimitiveCanvas;
|
||||
class LineChart;
|
||||
class CGStatusBar;
|
||||
class ComboBox;
|
||||
class CSlider;
|
||||
class IImage;
|
||||
class CPicture;
|
||||
|
||||
using TData = std::vector<std::pair<ColorRGBA, std::vector<float>>>;
|
||||
using TIcons = std::vector<std::tuple<ColorRGBA, int, std::shared_ptr<IImage>, std::string>>; // Color, Day, Image, Helptext
|
||||
|
||||
const int CHART_ICON_SIZE = 32;
|
||||
|
||||
class CStatisticScreen : public CWindowObject
|
||||
{
|
||||
enum Content {
|
||||
OVERVIEW,
|
||||
CHART_RESOURCES,
|
||||
CHART_INCOME,
|
||||
CHART_NUMBER_OF_HEROES,
|
||||
CHART_NUMBER_OF_TOWNS,
|
||||
CHART_NUMBER_OF_ARTIFACTS,
|
||||
CHART_NUMBER_OF_DWELLINGS,
|
||||
CHART_NUMBER_OF_MINES,
|
||||
CHART_ARMY_STRENGTH,
|
||||
CHART_EXPERIENCE,
|
||||
CHART_RESOURCES_SPENT_ARMY,
|
||||
CHART_RESOURCES_SPENT_BUILDINGS,
|
||||
CHART_MAP_EXPLORED,
|
||||
};
|
||||
std::map<Content, std::tuple<std::string, bool>> contentInfo = { // tuple: textid, resource selection needed
|
||||
{ OVERVIEW, { "vcmi.statisticWindow.title.overview", false } },
|
||||
{ CHART_RESOURCES, { "vcmi.statisticWindow.title.resources", true } },
|
||||
{ CHART_INCOME, { "vcmi.statisticWindow.title.income", false } },
|
||||
{ CHART_NUMBER_OF_HEROES, { "vcmi.statisticWindow.title.numberOfHeroes", false } },
|
||||
{ CHART_NUMBER_OF_TOWNS, { "vcmi.statisticWindow.title.numberOfTowns", false } },
|
||||
{ CHART_NUMBER_OF_ARTIFACTS, { "vcmi.statisticWindow.title.numberOfArtifacts", false } },
|
||||
{ CHART_NUMBER_OF_DWELLINGS, { "vcmi.statisticWindow.title.numberOfDwellings", false } },
|
||||
{ CHART_NUMBER_OF_MINES, { "vcmi.statisticWindow.title.numberOfMines", true } },
|
||||
{ CHART_ARMY_STRENGTH, { "vcmi.statisticWindow.title.armyStrength", false } },
|
||||
{ CHART_EXPERIENCE, { "vcmi.statisticWindow.title.experience", false } },
|
||||
{ CHART_RESOURCES_SPENT_ARMY, { "vcmi.statisticWindow.title.resourcesSpentArmy", true } },
|
||||
{ CHART_RESOURCES_SPENT_BUILDINGS, { "vcmi.statisticWindow.title.resourcesSpentBuildings", true } },
|
||||
{ CHART_MAP_EXPLORED, { "vcmi.statisticWindow.title.mapExplored", false } },
|
||||
};
|
||||
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
std::vector<std::shared_ptr<CIntObject>> layout;
|
||||
std::shared_ptr<CToggleButton> buttonCsvSave;
|
||||
std::shared_ptr<CToggleButton> buttonSelect;
|
||||
StatisticDataSet statistic;
|
||||
std::shared_ptr<CIntObject> mainContent;
|
||||
Rect contentArea;
|
||||
|
||||
using ExtractFunctor = std::function<float(StatisticDataSetEntry val)>;
|
||||
TData extractData(const StatisticDataSet & stat, const ExtractFunctor & selector) const;
|
||||
TIcons extractIcons() const;
|
||||
std::shared_ptr<CIntObject> getContent(Content c, EGameResID res);
|
||||
void onSelectButton();
|
||||
public:
|
||||
CStatisticScreen(const StatisticDataSet & stat);
|
||||
static std::string getDay(int day);
|
||||
};
|
||||
|
||||
class StatisticSelector : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
std::vector<std::shared_ptr<CToggleButton>> buttons;
|
||||
std::shared_ptr<CSlider> slider;
|
||||
|
||||
const int LINES = 10;
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::function<void(int selectedIndex)> cb;
|
||||
|
||||
void update(int to);
|
||||
public:
|
||||
StatisticSelector(const std::vector<std::string> & texts, const std::function<void(int selectedIndex)> & cb);
|
||||
};
|
||||
|
||||
class OverviewPanel : public CIntObject
|
||||
{
|
||||
std::shared_ptr<GraphicalPrimitiveCanvas> canvas;
|
||||
std::vector<std::shared_ptr<CIntObject>> layout;
|
||||
std::vector<std::shared_ptr<CIntObject>> content;
|
||||
std::shared_ptr<CSlider> slider;
|
||||
|
||||
Point fieldSize;
|
||||
StatisticDataSet data;
|
||||
|
||||
std::vector<std::pair<std::string, std::function<std::string(PlayerColor color)>>> dataExtract;
|
||||
|
||||
const int LINES = 15;
|
||||
const int Y_OFFS = 30;
|
||||
|
||||
std::vector<StatisticDataSetEntry> playerDataFilter(PlayerColor color);
|
||||
void update(int to);
|
||||
public:
|
||||
OverviewPanel(Rect position, std::string title, const StatisticDataSet & stat);
|
||||
};
|
||||
|
||||
class LineChart : public CIntObject
|
||||
{
|
||||
std::shared_ptr<GraphicalPrimitiveCanvas> canvas;
|
||||
std::vector<std::shared_ptr<CIntObject>> layout;
|
||||
std::shared_ptr<CGStatusBar> statusBar;
|
||||
std::vector<std::shared_ptr<CPicture>> pictures;
|
||||
|
||||
Rect chartArea;
|
||||
float maxVal;
|
||||
int maxDay;
|
||||
|
||||
void updateStatusBar(const Point & cursorPosition);
|
||||
public:
|
||||
LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY);
|
||||
|
||||
void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
};
|
@ -339,10 +339,11 @@ FFMpegStream::~FFMpegStream()
|
||||
{
|
||||
av_frame_free(&frame);
|
||||
|
||||
#if (LIBAVCODEC_VERSION_MAJOR < 61 )
|
||||
// deprecated, apparently no longer necessary - avcodec_free_context should suffice
|
||||
avcodec_close(codecContext);
|
||||
avcodec_free_context(&codecContext);
|
||||
#endif
|
||||
|
||||
avcodec_close(codecContext);
|
||||
avcodec_free_context(&codecContext);
|
||||
|
||||
avformat_close_input(&formatContext);
|
||||
|
@ -39,6 +39,8 @@ CPicture::CPicture(std::shared_ptr<IImage> image, const Point & position)
|
||||
pos += position;
|
||||
pos.w = bg->width();
|
||||
pos.h = bg->height();
|
||||
|
||||
addUsedEvents(SHOW_POPUP);
|
||||
}
|
||||
|
||||
CPicture::CPicture( const ImagePath &bmpname, int x, int y )
|
||||
@ -66,6 +68,8 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position )
|
||||
{
|
||||
pos.w = pos.h = 0;
|
||||
}
|
||||
|
||||
addUsedEvents(SHOW_POPUP);
|
||||
}
|
||||
|
||||
CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y)
|
||||
@ -74,6 +78,8 @@ CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y)
|
||||
srcRect = SrcRect;
|
||||
pos.w = srcRect->w;
|
||||
pos.h = srcRect->h;
|
||||
|
||||
addUsedEvents(SHOW_POPUP);
|
||||
}
|
||||
|
||||
CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, int y)
|
||||
@ -82,6 +88,8 @@ CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, in
|
||||
srcRect = SrcRect;
|
||||
pos.w = srcRect->w;
|
||||
pos.h = srcRect->h;
|
||||
|
||||
addUsedEvents(SHOW_POPUP);
|
||||
}
|
||||
|
||||
void CPicture::show(Canvas & to)
|
||||
@ -119,6 +127,17 @@ void CPicture::setPlayerColor(PlayerColor player)
|
||||
bg->playerColored(player);
|
||||
}
|
||||
|
||||
void CPicture::addRClickCallback(const std::function<void()> & callback)
|
||||
{
|
||||
rCallback = callback;
|
||||
}
|
||||
|
||||
void CPicture::showPopupWindow(const Point & cursorPosition)
|
||||
{
|
||||
if(rCallback)
|
||||
rCallback();
|
||||
}
|
||||
|
||||
CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position)
|
||||
: CIntObject(0, position.topLeft())
|
||||
, texture(GH.renderHandler().loadImage(imageName, EImageBlitMode::COLORKEY))
|
||||
|
@ -26,6 +26,7 @@ class IImage;
|
||||
class CPicture : public CIntObject
|
||||
{
|
||||
std::shared_ptr<IImage> bg;
|
||||
std::function<void()> rCallback;
|
||||
|
||||
public:
|
||||
/// if set, only specified section of internal image will be rendered
|
||||
@ -57,8 +58,11 @@ public:
|
||||
void scaleTo(Point size);
|
||||
void setPlayerColor(PlayerColor player);
|
||||
|
||||
void addRClickCallback(const std::function<void()> & callback);
|
||||
|
||||
void show(Canvas & to) override;
|
||||
void showAll(Canvas & to) override;
|
||||
void showPopupWindow(const Point & cursorPosition) override;
|
||||
};
|
||||
|
||||
/// area filled with specific texture
|
||||
|
@ -330,6 +330,7 @@ Rect CMultiLineLabel::getTextLocation()
|
||||
case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSize);
|
||||
case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSize);
|
||||
case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize);
|
||||
case ETextAlignment::CENTERRIGHT: return Rect(pos.topLeft() + Point(textOffset.x, textOffset.y / 2), textSize);
|
||||
case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize);
|
||||
}
|
||||
assert(0);
|
||||
@ -543,7 +544,6 @@ void CGStatusBar::activate()
|
||||
|
||||
void CGStatusBar::deactivate()
|
||||
{
|
||||
assert(GH.statusbar().get() == this);
|
||||
GH.setStatusbar(nullptr);
|
||||
|
||||
if (enteringText)
|
||||
|
55
conanfile.py
55
conanfile.py
@ -20,6 +20,7 @@ class VCMI(ConanFile):
|
||||
"sdl_mixer/[~2.0.4]",
|
||||
"sdl_ttf/[~2.0.18]",
|
||||
"onetbb/[^2021.3]",
|
||||
"xz_utils/[>=5.2.5]", # Required for innoextract
|
||||
]
|
||||
|
||||
requires = _libRequires + _clientRequires
|
||||
@ -87,24 +88,64 @@ class VCMI(ConanFile):
|
||||
self.options["boost"].without_type_erasure = True
|
||||
self.options["boost"].without_wave = True
|
||||
|
||||
self.options["ffmpeg"].avdevice = False
|
||||
self.options["ffmpeg"].avfilter = False
|
||||
self.options["ffmpeg"].postproc = False
|
||||
self.options["ffmpeg"].swresample = False
|
||||
self.options["ffmpeg"].with_asm = self.settings.os != "Android"
|
||||
self.options["ffmpeg"].disable_all_bitstream_filters = True
|
||||
self.options["ffmpeg"].disable_all_decoders = True
|
||||
self.options["ffmpeg"].disable_all_demuxers = True
|
||||
self.options["ffmpeg"].disable_all_encoders = True
|
||||
self.options["ffmpeg"].disable_all_filters = True
|
||||
self.options["ffmpeg"].disable_all_hardware_accelerators = True
|
||||
self.options["ffmpeg"].disable_all_muxers = True
|
||||
self.options["ffmpeg"].disable_all_parsers = True
|
||||
self.options["ffmpeg"].disable_all_protocols = True
|
||||
|
||||
self.options["ffmpeg"].with_asm = False
|
||||
self.options["ffmpeg"].with_bzip2 = False
|
||||
self.options["ffmpeg"].with_freetype = False
|
||||
self.options["ffmpeg"].with_libfdk_aac = False
|
||||
self.options["ffmpeg"].with_libaom = False
|
||||
self.options["ffmpeg"].with_libdav1d = False
|
||||
self.options["ffmpeg"].with_libiconv = False
|
||||
self.options["ffmpeg"].with_libmp3lame = False
|
||||
self.options["ffmpeg"].with_libsvtav1 = False
|
||||
self.options["ffmpeg"].with_libvpx = False
|
||||
self.options["ffmpeg"].with_libwebp = False
|
||||
self.options["ffmpeg"].with_libx264 = False
|
||||
self.options["ffmpeg"].with_libx265 = False
|
||||
self.options["ffmpeg"].with_lzma = True
|
||||
self.options["ffmpeg"].with_openh264 = False
|
||||
self.options["ffmpeg"].with_openjpeg = False
|
||||
self.options["ffmpeg"].with_opus = False
|
||||
self.options["ffmpeg"].with_programs = False
|
||||
self.options["ffmpeg"].with_sdl = False
|
||||
self.options["ffmpeg"].with_ssl = False
|
||||
self.options["ffmpeg"].with_vorbis = False
|
||||
self.options["ffmpeg"].with_zlib = False
|
||||
if self.settings.os != "Android":
|
||||
self.options["ffmpeg"].with_libfdk_aac = False
|
||||
|
||||
self.options["ffmpeg"].avcodec = True
|
||||
self.options["ffmpeg"].avdevice = False
|
||||
self.options["ffmpeg"].avfilter = False
|
||||
self.options["ffmpeg"].avformat = True
|
||||
self.options["ffmpeg"].postproc = False
|
||||
self.options["ffmpeg"].swresample = True # For resampling of audio in 'planar' formats
|
||||
self.options["ffmpeg"].swscale = True # For video scaling
|
||||
|
||||
# We want following options supported:
|
||||
# H3:SoD - .bik and .smk
|
||||
# H3:HD - ogg container / theora video / vorbis sound (not supported by vcmi at the moment, but might be supported in future)
|
||||
# and for mods - webm container / vp8 or vp9 video / opus sound
|
||||
# TODO: add av1 support for mods (requires enabling libdav1d which currently fails to build via Conan)
|
||||
self.options["ffmpeg"].enable_protocols = "file"
|
||||
self.options["ffmpeg"].enable_demuxers = "bink,binka,ogg,smacker,webm_dash_manifest"
|
||||
self.options["ffmpeg"].enable_parsers = "opus,vorbis,vp8,vp9,webp"
|
||||
self.options["ffmpeg"].enable_decoders = "bink,binkaudio_dct,binkaudio_rdft,smackaud,smacker,theora,vorbis,vp8,vp9,opus"
|
||||
|
||||
#optionally, for testing - enable ffplay/ffprobe binaries in conan package:
|
||||
#if self.settings.os == "Windows":
|
||||
# self.options["ffmpeg"].with_programs = True
|
||||
# self.options["ffmpeg"].avfilter = True
|
||||
# self.options["ffmpeg"].with_sdl = True
|
||||
# self.options["ffmpeg"].enable_filters = "aresample,scale"
|
||||
|
||||
self.options["sdl"].sdl2main = self.settings.os != "iOS"
|
||||
self.options["sdl"].vulkan = False
|
||||
@ -198,7 +239,7 @@ class VCMI(ConanFile):
|
||||
|
||||
# client
|
||||
if self.options.with_ffmpeg:
|
||||
self.requires("ffmpeg/[^4.4]")
|
||||
self.requires("ffmpeg/[>=4.4]")
|
||||
|
||||
# launcher
|
||||
if self.settings.os == "Android":
|
||||
|
@ -137,6 +137,7 @@
|
||||
"heroToggleTactics": "B",
|
||||
"highScoresCampaigns": "C",
|
||||
"highScoresReset": "R",
|
||||
"highScoresStatistics": ".",
|
||||
"highScoresScenarios": "S",
|
||||
"kingdomHeroesTab": "H",
|
||||
"kingdomTownsTab": "T",
|
||||
|
@ -27,12 +27,12 @@ The following platforms are supported and known to work, others might require ch
|
||||
- **Windows**: libraries are built with x86_64-mingw-w64-gcc version 10 (which is available in repositories of Ubuntu 22.04)
|
||||
- **Android**: libraries are built with NDK r25c (25.2.9519653)
|
||||
|
||||
2. Download the binaries archive and unpack it to `~/.conan` directory:
|
||||
2. Download the binaries archive and unpack it to `~/.conan` directory from https://github.com/vcmi/vcmi-dependencies/releases/latest
|
||||
|
||||
- [macOS](https://github.com/vcmi/vcmi-deps-macos/releases/latest): pick **intel.txz** if you have Intel Mac, otherwise - **intel-cross-arm.txz**
|
||||
- [iOS](https://github.com/vcmi/vcmi-ios-deps/releases/latest)
|
||||
- [Windows](https://github.com/vcmi/vcmi-deps-windows-conan/releases/latest): pick **vcmi-deps-windows-conan-w64.tgz** if you want x86_64, otherwise pick **vcmi-deps-windows-conan-w32.tgz**
|
||||
- [Android](https://github.com/vcmi/vcmi-dependencies/releases): current limitation is that building works only on a macOS host due to Qt 5 for Android being compiled on macOS. Simply delete directory `~/.conan/data/qt/5.15.x/_/_/package` (`5.15.x` is a placeholder) after unpacking the archive and build Qt from source. Alternatively, if you have (or are [willing to build](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs)) Qt host tools for your platform, then simply replace those in the archive with yours and most probably it would work.
|
||||
- macOS: pick **dependencies-mac-intel.txz** if you have Intel Mac, otherwise - **dependencies-mac-arm.txz**
|
||||
- iOS: pick ***dependencies-ios.txz***
|
||||
- Windows: currently only mingw is supported. Pick **dependencies-mingw.tgz** if you want x86_64, otherwise pick **dependencies-mingw-32.tgz**
|
||||
- Android: current limitation is that building works only on a macOS host due to Qt 5 for Android being compiled on macOS. Simply delete directory `~/.conan/data/qt/5.15.x/_/_/package` (`5.15.x` is a placeholder) after unpacking the archive and build Qt from source. Alternatively, if you have (or are [willing to build](https://github.com/vcmi/vcmi-ios-deps#note-for-arm-macs)) Qt host tools for your platform, then simply replace those in the archive with yours and most probably it would work.
|
||||
|
||||
3. Only if you have Apple Silicon Mac and trying to build for macOS or iOS:
|
||||
|
||||
|
80
docs/modders/File_Formats.md
Normal file
80
docs/modders/File_Formats.md
Normal file
@ -0,0 +1,80 @@
|
||||
# File Formats
|
||||
|
||||
This page describes which file formats are supported by vcmi.
|
||||
|
||||
In most cases, VCMI supports formats that were supported by Heroes III, with addition of new formats that are more convenient to use without specialized tools. See categories below for more details on specific formats
|
||||
|
||||
### Images
|
||||
|
||||
For images VCMI supports:
|
||||
- png. Recommended for usage in mods
|
||||
- bmp. While this format is supported, bmp images have no compressions leading to large file sizes
|
||||
- pcx (h3 version). Note that this is format that is specific to Heroes III and has nothing in common with widely known .pcx format. Files in this format generally can only be found inside of .lod archive of Heroes III and are usually extracted as .bmp files
|
||||
|
||||
Transparency support:
|
||||
VCMI supports transparency (alpha) channel, both in png and in bmp images. There may be cases where transparency is not fully supported. If you discover such cases, please report them.
|
||||
|
||||
For performance reasons, please use alpha channel only in places where transparency is actually required and remove alpha channel from image othervice
|
||||
|
||||
Palette support:
|
||||
TODO: describe how palettes work in vcmi
|
||||
|
||||
### Animations
|
||||
|
||||
For animations VCMI supports .def format from Heroes III as well as alternative json-based. See [Animation Format](Animation_Format.md) for more details
|
||||
|
||||
### Sounds
|
||||
|
||||
For sounds VCMI currently requires .wav format. Generally, VCMI will support any .wav parameters, however you might want to use high-bitrate versions, such as 44100 Hz or 48000 Hz, 32 bit, 1 or 2 channels
|
||||
|
||||
Support for additional formats, such as ogg/vorbis and ogg/opus is likely to be added in future
|
||||
|
||||
### Music
|
||||
|
||||
For sounds VCMI currently requires .mp3 format. Support for additional formats, such as ogg/vorbis and ogg/opus is likely to be added in future
|
||||
|
||||
### Video
|
||||
|
||||
Starting from VCMI 1.6, following video container formats are supported by VCMI:
|
||||
|
||||
- .bik - one of the formats used by Heroes III
|
||||
- .smk - one of the formats used by Heroes III. Note that these videos generally have lower quality and are only used as fallback if no other formats are found
|
||||
- .ogv - format used by Heroes III: HD Edition
|
||||
- .webm - modern, free format that is recommended for modding.
|
||||
|
||||
Supported video codecs:
|
||||
- bink and smacker - formats used by Heroes III, should be used only to avoid re-encoding
|
||||
- theora - used by Heroes III: HD Edition
|
||||
- vp8 - modern format with way better compression compared to formats used by Heroes III
|
||||
- vp9 - recommended, this format is improvement of vp9 format and should be used as a default option
|
||||
|
||||
Support for av1 video codec is likely to be added in future.
|
||||
|
||||
Supported audio codecs:
|
||||
- binkaudio and smackaud - formats used by Heroes III
|
||||
- vorbis - modern format with good compression level
|
||||
- opus - recommended, improvement over vorbis. Any bitrate is supported, with 128 kbit probably being the best option
|
||||
|
||||
### Json
|
||||
|
||||
For most of configuration files, VCMI uses [JSON format](http://en.wikipedia.org/wiki/Json) with some extensions from [JSON5](https://spec.json5.org/) format, such as comments.
|
||||
|
||||
### Maps
|
||||
|
||||
TODO: describe
|
||||
|
||||
### Campaigns
|
||||
|
||||
TODO: describe
|
||||
|
||||
### Map Templates
|
||||
|
||||
TODO: describe
|
||||
|
||||
### Archives
|
||||
|
||||
TODO: describe
|
||||
|
||||
### Txt
|
||||
|
||||
TODO: describe
|
@ -19,8 +19,10 @@ Example of how directory structure of your mod may look like:
|
||||
music/ - music files. Mp3 and ogg/vorbis are supported
|
||||
sounds/ - sound files, in wav format.
|
||||
sprites/ - animation, image sets (H3 .def files or VCMI .json files)
|
||||
video/ - video files, .bik or .smk
|
||||
video/ - video files, .bik, .smk, .ogv .webm
|
||||
```
|
||||
See [File Formats](File_Formats.md) page for more information on which formats are supported or recommended for vcmi
|
||||
|
||||
|
||||
## Creating mod file
|
||||
|
||||
|
@ -57,6 +57,11 @@ public:
|
||||
h & b;
|
||||
h & a;
|
||||
}
|
||||
|
||||
bool operator==(ColorRGBA const& rhs) const
|
||||
{
|
||||
return r == rhs.r && g == rhs.g && b == rhs.b && a == rhs.a;
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -118,6 +118,7 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension)
|
||||
{".FLAC", EResType::SOUND},
|
||||
{".SMK", EResType::VIDEO_LOW_QUALITY},
|
||||
{".BIK", EResType::VIDEO},
|
||||
{".OGV", EResType::VIDEO},
|
||||
{".WEBM", EResType::VIDEO},
|
||||
{".ZIP", EResType::ARCHIVE_ZIP},
|
||||
{".LOD", EResType::ARCHIVE_LOD},
|
||||
|
@ -28,7 +28,7 @@ class JsonSerializeFormat;
|
||||
* Font: .fnt
|
||||
* Image: .bmp, .jpg, .pcx, .png, .tga
|
||||
* Sound: .wav .82m
|
||||
* Video: .smk, .bik .mjpg .mpg .webm
|
||||
* Video: .smk, .bik .ogv .webm
|
||||
* Music: .mp3, .ogg
|
||||
* Archive: .lod, .snd, .vid .pac .zip
|
||||
* Palette: .pal
|
||||
|
@ -129,26 +129,26 @@ HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
|
||||
throw std::runtime_error("Can not allocate hero. All heroes are already used.");
|
||||
}
|
||||
|
||||
int CGameState::getDate(Date mode) const
|
||||
int CGameState::getDate(int d, Date mode)
|
||||
{
|
||||
int temp;
|
||||
switch (mode)
|
||||
{
|
||||
case Date::DAY:
|
||||
return day;
|
||||
return d;
|
||||
case Date::DAY_OF_WEEK: //day of week
|
||||
temp = (day)%7; // 1 - Monday, 7 - Sunday
|
||||
temp = (d)%7; // 1 - Monday, 7 - Sunday
|
||||
return temp ? temp : 7;
|
||||
case Date::WEEK: //current week
|
||||
temp = ((day-1)/7)+1;
|
||||
temp = ((d-1)/7)+1;
|
||||
if (!(temp%4))
|
||||
return 4;
|
||||
else
|
||||
return (temp%4);
|
||||
case Date::MONTH: //current month
|
||||
return ((day-1)/28)+1;
|
||||
return ((d-1)/28)+1;
|
||||
case Date::DAY_OF_MONTH: //day of month
|
||||
temp = (day)%28;
|
||||
temp = (d)%28;
|
||||
if (temp)
|
||||
return temp;
|
||||
else return 28;
|
||||
@ -156,6 +156,11 @@ int CGameState::getDate(Date mode) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CGameState::getDate(Date mode) const
|
||||
{
|
||||
return getDate(day, mode);
|
||||
}
|
||||
|
||||
CGameState::CGameState()
|
||||
{
|
||||
gs = this;
|
||||
|
@ -138,6 +138,7 @@ public:
|
||||
bool isVisible(int3 pos, const std::optional<PlayerColor> & player) const override;
|
||||
bool isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & player) const override;
|
||||
|
||||
static int getDate(int day, Date mode);
|
||||
int getDate(Date mode=Date::DAY) const override; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
|
||||
|
||||
// ----- getters, setters -----
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "GameStatistics.h"
|
||||
#include "../CPlayerState.h"
|
||||
#include "../constants/StringConstants.h"
|
||||
#include "../VCMIDirs.h"
|
||||
#include "CGameState.h"
|
||||
#include "TerrainHandler.h"
|
||||
#include "CHeroHandler.h"
|
||||
@ -44,6 +45,7 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons
|
||||
data.timestamp = std::time(nullptr);
|
||||
data.day = gs->getDate(Date::DAY);
|
||||
data.player = ps->color;
|
||||
data.playerName = gs->getStartInfo()->playerInfos.at(ps->color).name;
|
||||
data.team = ps->team;
|
||||
data.isHuman = ps->isHuman();
|
||||
data.status = ps->status;
|
||||
@ -71,101 +73,122 @@ StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, cons
|
||||
data.spentResourcesForArmy = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources();
|
||||
data.spentResourcesForBuildings = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources();
|
||||
data.tradeVolume = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.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.eventDefeatedStrongestHero = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.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;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string StatisticDataSet::toCsv()
|
||||
std::string StatisticDataSet::toCsv(std::string sep)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
auto resources = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS};
|
||||
|
||||
ss << "Map" << ";";
|
||||
ss << "Timestamp" << ";";
|
||||
ss << "Day" << ";";
|
||||
ss << "Player" << ";";
|
||||
ss << "Team" << ";";
|
||||
ss << "IsHuman" << ";";
|
||||
ss << "Status" << ";";
|
||||
ss << "NumberHeroes" << ";";
|
||||
ss << "NumberTowns" << ";";
|
||||
ss << "NumberArtifacts" << ";";
|
||||
ss << "NumberDwellings" << ";";
|
||||
ss << "ArmyStrength" << ";";
|
||||
ss << "TotalExperience" << ";";
|
||||
ss << "Income" << ";";
|
||||
ss << "MapExploredRatio" << ";";
|
||||
ss << "ObeliskVisitedRatio" << ";";
|
||||
ss << "TownBuiltRatio" << ";";
|
||||
ss << "HasGrail" << ";";
|
||||
ss << "Score" << ";";
|
||||
ss << "MaxHeroLevel" << ";";
|
||||
ss << "NumBattlesNeutral" << ";";
|
||||
ss << "NumBattlesPlayer" << ";";
|
||||
ss << "NumWinBattlesNeutral" << ";";
|
||||
ss << "NumWinBattlesPlayer" << ";";
|
||||
ss << "NumHeroSurrendered" << ";";
|
||||
ss << "NumHeroEscaped" << ";";
|
||||
ss << "Map" << sep;
|
||||
ss << "Timestamp" << sep;
|
||||
ss << "Day" << sep;
|
||||
ss << "Player" << sep;
|
||||
ss << "PlayerName" << sep;
|
||||
ss << "Team" << sep;
|
||||
ss << "IsHuman" << sep;
|
||||
ss << "Status" << sep;
|
||||
ss << "NumberHeroes" << sep;
|
||||
ss << "NumberTowns" << sep;
|
||||
ss << "NumberArtifacts" << sep;
|
||||
ss << "NumberDwellings" << sep;
|
||||
ss << "ArmyStrength" << sep;
|
||||
ss << "TotalExperience" << sep;
|
||||
ss << "Income" << sep;
|
||||
ss << "MapExploredRatio" << sep;
|
||||
ss << "ObeliskVisitedRatio" << sep;
|
||||
ss << "TownBuiltRatio" << sep;
|
||||
ss << "HasGrail" << sep;
|
||||
ss << "Score" << sep;
|
||||
ss << "MaxHeroLevel" << sep;
|
||||
ss << "NumBattlesNeutral" << sep;
|
||||
ss << "NumBattlesPlayer" << sep;
|
||||
ss << "NumWinBattlesNeutral" << sep;
|
||||
ss << "NumWinBattlesPlayer" << sep;
|
||||
ss << "NumHeroSurrendered" << sep;
|
||||
ss << "NumHeroEscaped" << sep;
|
||||
ss << "EventCapturedTown" << sep;
|
||||
ss << "EventDefeatedStrongestHero" << sep;
|
||||
ss << "MovementPointsUsed";
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << GameConstants::RESOURCE_NAMES[resource];
|
||||
ss << sep << GameConstants::RESOURCE_NAMES[resource];
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "Mines";
|
||||
ss << sep << GameConstants::RESOURCE_NAMES[resource] + "Mines";
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForArmy";
|
||||
ss << sep << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForArmy";
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForBuildings";
|
||||
ss << sep << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForBuildings";
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "TradeVolume";
|
||||
ss << sep << GameConstants::RESOURCE_NAMES[resource] + "TradeVolume";
|
||||
ss << "\r\n";
|
||||
|
||||
for(auto & entry : data)
|
||||
{
|
||||
ss << entry.map << ";";
|
||||
ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H:%M:%S") << ";";
|
||||
ss << entry.day << ";";
|
||||
ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";";
|
||||
ss << entry.team.getNum() << ";";
|
||||
ss << entry.isHuman << ";";
|
||||
ss << static_cast<int>(entry.status) << ";";
|
||||
ss << entry.numberHeroes << ";";
|
||||
ss << entry.numberTowns << ";";
|
||||
ss << entry.numberArtifacts << ";";
|
||||
ss << entry.numberDwellings << ";";
|
||||
ss << entry.armyStrength << ";";
|
||||
ss << entry.totalExperience << ";";
|
||||
ss << entry.income << ";";
|
||||
ss << entry.mapExploredRatio << ";";
|
||||
ss << entry.obeliskVisitedRatio << ";";
|
||||
ss << entry.townBuiltRatio << ";";
|
||||
ss << entry.hasGrail << ";";
|
||||
ss << entry.score << ";";
|
||||
ss << entry.maxHeroLevel << ";";
|
||||
ss << entry.numBattlesNeutral << ";";
|
||||
ss << entry.numBattlesPlayer << ";";
|
||||
ss << entry.numWinBattlesNeutral << ";";
|
||||
ss << entry.numWinBattlesPlayer << ";";
|
||||
ss << entry.numHeroSurrendered << ";";
|
||||
ss << entry.numHeroEscaped << ";";
|
||||
ss << entry.map << sep;
|
||||
ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H:%M:%S") << sep;
|
||||
ss << entry.day << sep;
|
||||
ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << sep;
|
||||
ss << entry.playerName << sep;
|
||||
ss << entry.team.getNum() << sep;
|
||||
ss << entry.isHuman << sep;
|
||||
ss << static_cast<int>(entry.status) << sep;
|
||||
ss << entry.numberHeroes << sep;
|
||||
ss << entry.numberTowns << sep;
|
||||
ss << entry.numberArtifacts << sep;
|
||||
ss << entry.numberDwellings << sep;
|
||||
ss << entry.armyStrength << sep;
|
||||
ss << entry.totalExperience << sep;
|
||||
ss << entry.income << sep;
|
||||
ss << entry.mapExploredRatio << sep;
|
||||
ss << entry.obeliskVisitedRatio << sep;
|
||||
ss << entry.townBuiltRatio << sep;
|
||||
ss << entry.hasGrail << sep;
|
||||
ss << entry.score << sep;
|
||||
ss << entry.maxHeroLevel << sep;
|
||||
ss << entry.numBattlesNeutral << sep;
|
||||
ss << entry.numBattlesPlayer << sep;
|
||||
ss << entry.numWinBattlesNeutral << sep;
|
||||
ss << entry.numWinBattlesPlayer << sep;
|
||||
ss << entry.numHeroSurrendered << sep;
|
||||
ss << entry.numHeroEscaped << sep;
|
||||
ss << entry.eventCapturedTown << sep;
|
||||
ss << entry.eventDefeatedStrongestHero << sep;
|
||||
ss << entry.movementPointsUsed;
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << entry.resources[resource];
|
||||
ss << sep << entry.resources[resource];
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << entry.numMines[resource];
|
||||
ss << sep << entry.numMines[resource];
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << entry.spentResourcesForArmy[resource];
|
||||
ss << sep << entry.spentResourcesForArmy[resource];
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << entry.spentResourcesForBuildings[resource];
|
||||
ss << sep << entry.spentResourcesForBuildings[resource];
|
||||
for(auto & resource : resources)
|
||||
ss << ";" << entry.tradeVolume[resource];
|
||||
ss << sep << entry.tradeVolume[resource];
|
||||
ss << "\r\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string StatisticDataSet::writeCsv()
|
||||
{
|
||||
const boost::filesystem::path outPath = VCMIDirs::get().userCachePath() / "statistic";
|
||||
boost::filesystem::create_directories(outPath);
|
||||
|
||||
const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv");
|
||||
std::ofstream file(filePath.c_str());
|
||||
std::string csv = toCsv(";");
|
||||
file << csv;
|
||||
|
||||
return filePath.string();
|
||||
}
|
||||
|
||||
std::vector<const CGMine *> Statistic::getMines(const CGameState * gs, const PlayerState * ps)
|
||||
{
|
||||
std::vector<const CGMine *> tmp;
|
||||
|
@ -25,6 +25,7 @@ struct DLL_LINKAGE StatisticDataSetEntry
|
||||
time_t timestamp;
|
||||
int day;
|
||||
PlayerColor player;
|
||||
std::string playerName;
|
||||
TeamID team;
|
||||
bool isHuman;
|
||||
EPlayerStatus status;
|
||||
@ -52,6 +53,8 @@ struct DLL_LINKAGE StatisticDataSetEntry
|
||||
TResources spentResourcesForArmy;
|
||||
TResources spentResourcesForBuildings;
|
||||
TResources tradeVolume;
|
||||
bool eventCapturedTown;
|
||||
bool eventDefeatedStrongestHero;
|
||||
si64 movementPointsUsed;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
@ -60,6 +63,8 @@ struct DLL_LINKAGE StatisticDataSetEntry
|
||||
h & timestamp;
|
||||
h & day;
|
||||
h & player;
|
||||
if(h.version >= Handler::Version::STATISTICS_SCREEN)
|
||||
h & playerName;
|
||||
h & team;
|
||||
h & isHuman;
|
||||
h & status;
|
||||
@ -87,18 +92,22 @@ struct DLL_LINKAGE StatisticDataSetEntry
|
||||
h & spentResourcesForArmy;
|
||||
h & spentResourcesForBuildings;
|
||||
h & tradeVolume;
|
||||
if(h.version >= Handler::Version::STATISTICS_SCREEN)
|
||||
{
|
||||
h & eventCapturedTown;
|
||||
h & eventDefeatedStrongestHero;
|
||||
}
|
||||
h & movementPointsUsed;
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE StatisticDataSet
|
||||
{
|
||||
std::vector<StatisticDataSetEntry> data;
|
||||
|
||||
public:
|
||||
void add(StatisticDataSetEntry entry);
|
||||
static StatisticDataSetEntry createEntry(const PlayerState * ps, const CGameState * gs);
|
||||
std::string toCsv();
|
||||
std::string toCsv(std::string sep);
|
||||
std::string writeCsv();
|
||||
|
||||
struct PlayerAccumulatedValueStorage // holds some actual values needed for stats
|
||||
{
|
||||
@ -112,6 +121,8 @@ public:
|
||||
TResources spentResourcesForBuildings;
|
||||
TResources tradeVolume;
|
||||
si64 movementPointsUsed;
|
||||
int lastCapturedTownDay;
|
||||
int lastDefeatedStrongestHeroDay;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
@ -125,8 +136,14 @@ public:
|
||||
h & spentResourcesForBuildings;
|
||||
h & tradeVolume;
|
||||
h & movementPointsUsed;
|
||||
if(h.version >= Handler::Version::STATISTICS_SCREEN)
|
||||
{
|
||||
h & lastCapturedTownDay;
|
||||
h & lastDefeatedStrongestHeroDay;
|
||||
}
|
||||
}
|
||||
};
|
||||
std::vector<StatisticDataSetEntry> data;
|
||||
std::map<PlayerColor, PlayerAccumulatedValueStorage> accumulatedValues;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "../gameState/RumorState.h"
|
||||
#include "../gameState/QuestInfo.h"
|
||||
#include "../gameState/TavernSlot.h"
|
||||
#include "../gameState/GameStatistics.h"
|
||||
#include "../int3.h"
|
||||
#include "../mapping/CMapDefines.h"
|
||||
#include "../spells/ViewSpellInt.h"
|
||||
@ -435,6 +436,7 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
|
||||
|
||||
PlayerColor player;
|
||||
EVictoryLossCheckResult victoryLossCheckResult;
|
||||
StatisticDataSet statistic;
|
||||
|
||||
void visitTyped(ICPackVisitor & visitor) override;
|
||||
|
||||
@ -442,6 +444,8 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient
|
||||
{
|
||||
h & player;
|
||||
h & victoryLossCheckResult;
|
||||
if (h.version >= Handler::Version::STATISTICS_SCREEN)
|
||||
h & statistic;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -64,7 +64,8 @@ enum class ESerializationVersion : int32_t
|
||||
CAMPAIGN_REGIONS, // 853 - configurable campaign regions
|
||||
EVENTS_PLAYER_SET, // 854 - map & town events use std::set instead of bitmask to store player list
|
||||
NEW_TOWN_BUILDINGS, // 855 - old bonusing buildings have been removed
|
||||
RESOURCE_GENERATION, // 856 - resource generation
|
||||
STATISTICS_SCREEN, // 856 - extent statistic functions
|
||||
RESOURCE_GENERATION, // 857 - resource generation
|
||||
|
||||
CURRENT = RESOURCE_GENERATION
|
||||
};
|
||||
|
@ -681,7 +681,7 @@ void CGameHandler::onPlayerTurnEnded(PlayerColor which)
|
||||
heroPool->onNewWeek(which);
|
||||
}
|
||||
|
||||
void CGameHandler::addStatistics()
|
||||
void CGameHandler::addStatistics(StatisticDataSet &stat) const
|
||||
{
|
||||
for (const auto & elem : gs->players)
|
||||
{
|
||||
@ -690,7 +690,7 @@ void CGameHandler::addStatistics()
|
||||
|
||||
auto data = StatisticDataSet::createEntry(&elem.second, gs);
|
||||
|
||||
gameState()->statistic.add(data);
|
||||
stat.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -718,6 +718,10 @@ void CGameHandler::onNewTurn()
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addStatistics(gameState()->statistic); // write at end of turn
|
||||
}
|
||||
|
||||
for (const auto & player : gs->players)
|
||||
{
|
||||
@ -1036,8 +1040,6 @@ void CGameHandler::onNewTurn()
|
||||
}
|
||||
|
||||
synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
|
||||
|
||||
addStatistics();
|
||||
}
|
||||
|
||||
void CGameHandler::start(bool resume)
|
||||
@ -1414,6 +1416,8 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, const PlayerColor owne
|
||||
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
if (town) //town captured
|
||||
{
|
||||
gs->statistic.accumulatedValues[owner].lastCapturedTownDay = gs->getDate(Date::DAY);
|
||||
|
||||
if (owner.isValidPlayer()) //new owner is real player
|
||||
{
|
||||
if (town->hasBuilt(BuildingSubID::PORTAL_OF_SUMMONING))
|
||||
@ -3733,6 +3737,8 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
|
||||
PlayerEndsGame peg;
|
||||
peg.player = player;
|
||||
peg.victoryLossCheckResult = victoryLossCheckResult;
|
||||
peg.statistic = StatisticDataSet(gameState()->statistic);
|
||||
addStatistics(peg.statistic); // add last turn befor win / loss
|
||||
sendAndApply(&peg);
|
||||
|
||||
turnOrder->onPlayerEndsGame(player);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../lib/IGameCallback.h"
|
||||
#include "../lib/LoadProgress.h"
|
||||
#include "../lib/ScriptHandler.h"
|
||||
#include "../lib/gameState/GameStatistics.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -227,7 +228,7 @@ public:
|
||||
void onPlayerTurnStarted(PlayerColor which);
|
||||
void onPlayerTurnEnded(PlayerColor which);
|
||||
void onNewTurn();
|
||||
void addStatistics();
|
||||
void addStatistics(StatisticDataSet &stat) const;
|
||||
|
||||
void handleTimeEvents(PlayerColor player);
|
||||
void handleTownEvents(CGTownInstance *town);
|
||||
|
@ -480,6 +480,17 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
|
||||
// Remove beaten hero
|
||||
if(finishingBattle->loserHero)
|
||||
{
|
||||
//add statistics
|
||||
if(!finishingBattle->isDraw())
|
||||
{
|
||||
ConstTransitivePtr<CGHeroInstance> strongestHero = nullptr;
|
||||
for(auto & hero : gameHandler->gameState()->getPlayerState(finishingBattle->loser)->heroes)
|
||||
if(!strongestHero || hero->exp > strongestHero->exp)
|
||||
strongestHero = hero;
|
||||
if(strongestHero->id == finishingBattle->loserHero->id && strongestHero->level > 5)
|
||||
gameHandler->gameState()->statistic.accumulatedValues[finishingBattle->victor].lastDefeatedStrongestHeroDay = gameHandler->gameState()->getDate(Date::DAY);
|
||||
}
|
||||
|
||||
RemoveObject ro(finishingBattle->loserHero->id, finishingBattle->victor);
|
||||
gameHandler->sendAndApply(&ro);
|
||||
}
|
||||
|
@ -140,15 +140,9 @@ void PlayerMessageProcessor::commandStatistic(PlayerColor player, const std::vec
|
||||
if(!isHost)
|
||||
return;
|
||||
|
||||
const boost::filesystem::path outPath = VCMIDirs::get().userCachePath() / "statistic";
|
||||
boost::filesystem::create_directories(outPath);
|
||||
std::string path = gameHandler->gameState()->statistic.writeCsv();
|
||||
|
||||
const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv");
|
||||
std::ofstream file(filePath.c_str());
|
||||
std::string csv = gameHandler->gameState()->statistic.toCsv();
|
||||
file << csv;
|
||||
|
||||
broadcastSystemMessage("Statistic files can be found in " + outPath.string() + " directory\n");
|
||||
broadcastSystemMessage("Statistic files can be found in " + path + " directory\n");
|
||||
}
|
||||
|
||||
void PlayerMessageProcessor::commandHelp(PlayerColor player, const std::vector<std::string> & words)
|
||||
|
Loading…
Reference in New Issue
Block a user