1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Merge pull request #2892 from Laserlicht/highscore_menu

Highscore Menu & end video
This commit is contained in:
Ivan Savenko 2023-09-27 15:48:47 +03:00 committed by GitHub
commit 71a1ed816a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 728 additions and 39 deletions

View File

@ -37,7 +37,6 @@
"vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu",
"vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice",
"vcmi.mainMenu.highscoresNotImplemented" : "Omlouvám se, menu nejvyšší skóre ještě není implementováno\n",
"vcmi.mainMenu.serverConnecting" : "Připojování...",
"vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:",
"vcmi.mainMenu.serverClosing" : "Zavírání...",

View File

@ -42,7 +42,6 @@
"vcmi.radialWheel.moveUnit" : "Move creatures to another army",
"vcmi.radialWheel.splitUnit" : "Split creature to another slot",
"vcmi.mainMenu.highscoresNotImplemented" : "Sorry, high scores menu is not implemented yet\n",
"vcmi.mainMenu.serverConnecting" : "Connecting...",
"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
"vcmi.mainMenu.serverClosing" : "Closing...",

View File

@ -30,7 +30,6 @@
"vcmi.capitalColors.6" : "Turquoise",
"vcmi.capitalColors.7" : "Rose",
"vcmi.mainMenu.highscoresNotImplemented" : "Désolé, le menu des meilleurs scores n'est pas encore implémenté\n",
"vcmi.mainMenu.serverConnecting" : "Connexion...",
"vcmi.mainMenu.serverAddressEnter" : "Entrez l'adresse :",
"vcmi.mainMenu.serverClosing" : "Fermeture...",

View File

@ -41,7 +41,6 @@
"vcmi.radialWheel.moveUnit" : "Verschieben der Kreatur in andere Armee",
"vcmi.radialWheel.splitUnit" : "Aufsplitten der Kreatur in anderen Slot",
"vcmi.mainMenu.highscoresNotImplemented" : "Die Highscores sind aktuell noch nicht implementiert\n",
"vcmi.mainMenu.serverConnecting" : "Verbinde...",
"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
"vcmi.mainMenu.serverClosing" : "Trenne...",

View File

@ -36,7 +36,6 @@
"vcmi.radialWheel.moveUnit" : "Przenieś stworzenia do innej armii",
"vcmi.radialWheel.splitUnit" : "Podziel jednostkę do wybranego miejsca",
"vcmi.mainMenu.highscoresNotImplemented" : "Przepraszamy, najlepsze wyniki nie zostały jeszcze zaimplementowane\n",
"vcmi.mainMenu.serverConnecting" : "Łączenie...",
"vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:",
"vcmi.mainMenu.serverClosing" : "Zamykanie...",

View File

@ -37,7 +37,6 @@
"vcmi.radialWheel.moveUnit" : "Перемістити істоту до іншої армії",
"vcmi.radialWheel.splitUnit" : "Розділити істоту в інший слот",
"vcmi.mainMenu.highscoresNotImplemented" : "Вибачте, таблицю рекордів ще не реалізовано\n",
"vcmi.mainMenu.serverConnecting" : "Підключення...",
"vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:",
"vcmi.mainMenu.serverClosing" : "Завершення...",

View File

@ -59,6 +59,7 @@ set(client_SRCS
mainmenu/CMainMenu.cpp
mainmenu/CPrologEpilogVideo.cpp
mainmenu/CreditsScreen.cpp
mainmenu/CHighScoreScreen.cpp
mapView/MapRenderer.cpp
mapView/MapRendererContext.cpp
@ -213,6 +214,7 @@ set(client_HEADERS
mainmenu/CMainMenu.h
mainmenu/CPrologEpilogVideo.h
mainmenu/CreditsScreen.h
mainmenu/CHighScoreScreen.h
mapView/IMapRendererContext.h
mapView/IMapRendererObserver.h

View File

@ -37,6 +37,7 @@
#include "gui/WindowHandler.h"
#include "mainmenu/CMainMenu.h"
#include "mainmenu/CHighScoreScreen.h"
#include "mapView/mapHandler.h"
@ -1697,16 +1698,43 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
void CPlayerInterface::requestReturningToMainMenu(bool won)
{
HighScoreParameter param;
param.difficulty = cb->getStartInfo()->difficulty;
param.day = cb->getDate();
param.townAmount = cb->howManyTowns();
param.usedCheat = cb->getPlayerState(*cb->getPlayerID())->cheated;
param.hasGrail = false;
for(const CGHeroInstance * h : cb->getHeroesInfo())
if(h->hasArt(ArtifactID::GRAIL))
param.hasGrail = true;
for(const CGTownInstance * t : cb->getTownsInfo())
if(t->builtBuildings.count(BuildingID::GRAIL))
param.hasGrail = true;
param.allDefeated = true;
for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
{
auto ps = cb->getPlayerState(player, false);
if(ps && player != *cb->getPlayerID())
if(!ps->checkVanquished())
param.allDefeated = false;
}
param.scenarioName = cb->getMapHeader()->name;
param.playerName = cb->getStartInfo()->playerInfos.find(*cb->getPlayerID())->second.name;
HighScoreCalculation highScoreCalc;
highScoreCalc.parameters.push_back(param);
highScoreCalc.isCampaign = false;
if(won && cb->getStartInfo()->campState)
CSH->startCampaignScenario(cb->getStartInfo()->campState);
CSH->startCampaignScenario(param, cb->getStartInfo()->campState);
else
{
GH.dispatchMainThread(
[]()
[won, highScoreCalc]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
GH.windows().createAndPushWindow<CHighScoreInputScreen>(won, highScoreCalc);
}
);
}

View File

@ -22,6 +22,7 @@
#include "mainmenu/CMainMenu.h"
#include "mainmenu/CPrologEpilogVideo.h"
#include "mainmenu/CHighScoreScreen.h"
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"
@ -616,6 +617,8 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
CMM->disable();
client = new CClient();
highScoreCalc = nullptr;
switch(si->mode)
{
case StartInfo::NEW_GAME:
@ -685,14 +688,23 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
saveSession->Bool() = false;
}
void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs)
{
std::shared_ptr<CampaignState> ourCampaign = cs;
if (!cs)
ourCampaign = si->campState;
GH.dispatchMainThread([ourCampaign]()
if(highScoreCalc == nullptr)
{
highScoreCalc = std::make_shared<HighScoreCalculation>();
highScoreCalc->isCampaign = true;
highScoreCalc->parameters.clear();
}
param.campaignName = cs->getName();
highScoreCalc->parameters.push_back(param);
GH.dispatchMainThread([ourCampaign, this]()
{
CSH->campaignServerRestartLock.set(true);
CSH->endGameplay();
@ -712,7 +724,10 @@ void CServerHandler::startCampaignScenario(std::shared_ptr<CampaignState> cs)
if(!ourCampaign->isCampaignFinished())
CMM->openCampaignLobby(ourCampaign);
else
{
CMM->openCampaignScreen(ourCampaign->campaignSet);
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *highScoreCalc);
}
};
if(epilogue.hasPrologEpilog)
{
@ -960,7 +975,7 @@ void CServerHandler::threadRunServer()
}
comm += " > \"" + logName + '\"';
logGlobal->info("Server command line: %s", comm);
logGlobal->info("Server command line: %s", comm);
#ifdef VCMI_WINDOWS
int result = -1;

View File

@ -35,6 +35,9 @@ VCMI_LIB_NAMESPACE_END
class CClient;
class CBaseForLobbyApply;
class HighScoreCalculation;
class HighScoreParameter;
// TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet
enum class EClientState : ui8
{
@ -87,6 +90,8 @@ class CServerHandler : public IServerAPI, public LobbyInfo
std::vector<std::string> myNames;
std::shared_ptr<HighScoreCalculation> highScoreCalc;
void threadHandleConnection();
void threadRunServer();
void onServerFinished();
@ -161,7 +166,7 @@ public:
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
void endGameplay(bool closeConnection = true, bool restart = false);
void startCampaignScenario(std::shared_ptr<CampaignState> cs = {});
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});
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

View File

@ -349,7 +349,7 @@ void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update )
show(x, y, dst, update);
}
void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update )
void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart)
{
if (sws == nullptr)
return;
@ -368,6 +368,8 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
show(x,y,dst,update);
else
{
if(onVideoRestart)
onVideoRestart();
VideoPath filenameToReopen = fname; // create copy to backup this->fname
open(filenameToReopen);
nextFrame();

View File

@ -31,7 +31,7 @@ public:
class IMainVideoPlayer : public IVideoPlayer
{
public:
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true){}
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = 0){}
virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false)
{
return false;
@ -101,7 +101,7 @@ public:
void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame
void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override;

View File

@ -198,6 +198,8 @@
<Unit filename="mainmenu/CPrologEpilogVideo.h" />
<Unit filename="mainmenu/CreditsScreen.cpp" />
<Unit filename="mainmenu/CreditsScreen.h" />
<Unit filename="mainmenu/CHighScoreScreen.cpp" />
<Unit filename="mainmenu/CHighScoreScreen.h" />
<Unit filename="mapHandler.cpp" />
<Unit filename="mapHandler.h" />
<Unit filename="resource.h" />

View File

@ -217,6 +217,7 @@
<ClCompile Include="mainmenu\CMainMenu.cpp" />
<ClCompile Include="mainmenu\CPrologEpilogVideo.cpp" />
<ClCompile Include="mainmenu\CreditsScreen.cpp" />
<ClCompile Include="mainmenu\CHighScoreScreen.cpp" />
<ClCompile Include="mapHandler.cpp" />
<ClCompile Include="NetPacksClient.cpp" />
<ClCompile Include="NetPacksLobbyClient.cpp" />
@ -286,6 +287,7 @@
<ClInclude Include="mainmenu\CMainMenu.h" />
<ClInclude Include="mainmenu\CPrologEpilogVideo.h" />
<ClInclude Include="mainmenu\CreditsScreen.h" />
<ClInclude Include="mainmenu\CHighScoreScreen.h" />
<ClInclude Include="mapHandler.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="SDLRWwrapper.h" />

View File

@ -132,6 +132,7 @@
<ClCompile Include="mainmenu\CMainMenu.cpp" />
<ClCompile Include="mainmenu\CPrologEpilogVideo.cpp" />
<ClCompile Include="mainmenu\CreditsScreen.cpp" />
<ClCompile Include="mainmenu\CHighScoreScreen.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="VCMI_client.rc" />
@ -290,5 +291,6 @@
<ClInclude Include="mainmenu\CMainMenu.h" />
<ClInclude Include="mainmenu\CPrologEpilogVideo.h" />
<ClInclude Include="mainmenu\CreditsScreen.h" />
<ClInclude Include="mainmenu\CHighScoreScreen.h" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,383 @@
/*
* CHighScoreScreen.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 "CHighScoreScreen.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../gui/Shortcut.h"
#include "../widgets/TextControls.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../render/Canvas.h"
#include "../CGameInfo.h"
#include "../CVideoHandler.h"
#include "../CMusicHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/constants/EntityIdentifiers.h"
#include "../../lib/TextOperations.h"
#include "vstd/DateUtils.h"
auto HighScoreCalculation::calculate()
{
struct Result
{
int basic = 0;
int total = 0;
int sumDays = 0;
bool cheater = false;
};
Result firstResult, summary;
const std::array<double, 5> difficultyMultipliers{0.8, 1.0, 1.3, 1.6, 2.0};
for(auto & param : parameters)
{
double tmp = 200 - (param.day + 10) / (param.townAmount + 5) + (param.allDefeated ? 25 : 0) + (param.hasGrail ? 25 : 0);
firstResult = Result{static_cast<int>(tmp), static_cast<int>(tmp * difficultyMultipliers.at(param.difficulty)), param.day, param.usedCheat};
summary.basic += firstResult.basic * 5.0 / parameters.size();
summary.total += firstResult.total * 5.0 / parameters.size();
summary.sumDays += firstResult.sumDays;
summary.cheater |= firstResult.cheater;
}
if(parameters.size() == 1)
return firstResult;
return summary;
}
CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign)
{
static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json"));
auto creatures = configCreatures["creatures"].Vector();
int divide = campaign ? 5 : 1;
for(auto & creature : creatures)
if(points / divide <= creature["max"].Integer() && points / divide >= creature["min"].Integer())
return CreatureID::decode(creature["creature"].String());
return -1;
}
CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
: CWindowObject(BORDERED), highscorepage(highscorepage), highlighted(highlighted)
{
addUsedEvents(SHOW_POPUP);
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos = center(Rect(0, 0, 800, 600));
updateShadow();
addHighScores();
addButtons();
}
void CHighScoreScreen::showPopupWindow(const Point & cursorPosition)
{
for (int i = 0; i < screenRows; i++)
{
bool currentGameNotInListEntry = i == (screenRows - 1) && highlighted > (screenRows - 1);
Rect r = Rect(80, 40 + i * 50, 635, 50);
if(r.isInside(cursorPosition - pos))
{
std::string tmp = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"][currentGameNotInListEntry ? highlighted : i]["datetime"].String();
if(!tmp.empty())
CRClickPopup::createAndPush(tmp);
}
}
}
void CHighScoreScreen::addButtons()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
buttons.clear();
buttons.push_back(std::make_shared<CButton>(Point(31, 113), AnimationPath::builtin("HISCCAM.DEF"), CButton::tooltip(), [&](){ buttonCampaignClick(); }));
buttons.push_back(std::make_shared<CButton>(Point(31, 345), AnimationPath::builtin("HISCSTA.DEF"), CButton::tooltip(), [&](){ buttonScenarioClick(); }));
buttons.push_back(std::make_shared<CButton>(Point(726, 113), AnimationPath::builtin("HISCRES.DEF"), CButton::tooltip(), [&](){ buttonResetClick(); }));
buttons.push_back(std::make_shared<CButton>(Point(726, 345), AnimationPath::builtin("HISCEXT.DEF"), CButton::tooltip(), [&](){ buttonExitClick(); }));
}
void CHighScoreScreen::addHighScores()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>(ImagePath::builtin(highscorepage == HighScorePage::SCENARIO ? "HISCORE" : "HISCORE2"));
texts.clear();
images.clear();
// Header
texts.push_back(std::make_shared<CLabel>(115, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.433"))); // rank
texts.push_back(std::make_shared<CLabel>(225, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.434"))); // player
if(highscorepage == HighScorePage::SCENARIO)
{
texts.push_back(std::make_shared<CLabel>(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.435"))); // land
texts.push_back(std::make_shared<CLabel>(557, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.436"))); // days
texts.push_back(std::make_shared<CLabel>(627, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); // score
}
else
{
texts.push_back(std::make_shared<CLabel>(405, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.672"))); // campaign
texts.push_back(std::make_shared<CLabel>(592, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.75"))); // score
}
// Content
int y = 66;
auto & data = persistentStorage["highscore"][highscorepage == HighScorePage::SCENARIO ? "scenario" : "campaign"];
for (int i = 0; i < screenRows; i++)
{
bool currentGameNotInListEntry = (i == (screenRows - 1) && highlighted > (screenRows - 1));
auto & curData = data[currentGameNotInListEntry ? highlighted : i];
ColorRGBA color = (i == highlighted || currentGameNotInListEntry) ? Colors::YELLOW : Colors::WHITE;
texts.push_back(std::make_shared<CLabel>(115, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string((currentGameNotInListEntry ? highlighted : i) + 1)));
std::string tmp = curData["player"].String();
TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 13));
texts.push_back(std::make_shared<CLabel>(225, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp));
if(highscorepage == HighScorePage::SCENARIO)
{
std::string tmp = curData["scenarioName"].String();
TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25));
texts.push_back(std::make_shared<CLabel>(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp));
texts.push_back(std::make_shared<CLabel>(557, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["days"].Integer())));
texts.push_back(std::make_shared<CLabel>(627, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer())));
}
else
{
std::string tmp = curData["campaignName"].String();
TextOperations::trimRightUnicode(tmp, std::max(0, (int)TextOperations::getUnicodeCharactersCount(tmp) - 25));
texts.push_back(std::make_shared<CLabel>(405, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, tmp));
texts.push_back(std::make_shared<CLabel>(592, y + i * 50, FONT_MEDIUM, ETextAlignment::CENTER, color, std::to_string(curData["points"].Integer())));
}
if(curData["points"].Integer() > 0 && curData["points"].Integer() <= ((highscorepage == HighScorePage::CAMPAIGN) ? 2500 : 500))
images.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(curData["points"].Integer(), highscorepage == HighScorePage::CAMPAIGN)]->getIconIndex(), 0, 670, y - 15 + i * 50));
}
}
void CHighScoreScreen::buttonCampaignClick()
{
highscorepage = HighScorePage::CAMPAIGN;
addHighScores();
addButtons();
redraw();
}
void CHighScoreScreen::buttonScenarioClick()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
highscorepage = HighScorePage::SCENARIO;
addHighScores();
addButtons();
redraw();
}
void CHighScoreScreen::buttonResetClick()
{
CInfoWindow::showYesNoDialog(
CGI->generaltexth->allTexts[666],
{},
[this]()
{
Settings entry = persistentStorage.write["highscore"];
entry->clear();
addHighScores();
addButtons();
redraw();
},
0
);
}
void CHighScoreScreen::buttonExitClick()
{
close();
}
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc)
: CWindowObject(BORDERED), won(won), calc(calc)
{
addUsedEvents(LCLICK | KEYBOARD);
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos = center(Rect(0, 0, 800, 600));
updateShadow();
background = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), Colors::BLACK);
if(won)
{
int border = 100;
int textareaW = ((pos.w - 2 * border) / 4);
std::vector<std::string> t = { "438", "439", "440", "441", "676" }; // time, score, difficulty, final score, rank
for (int i = 0; i < 5; i++)
texts.push_back(std::make_shared<CMultiLineLabel>(Rect(textareaW * i + border - (textareaW / 2), 450, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt." + t[i])));
std::string creatureName = (calc.calculate().cheater) ? CGI->generaltexth->translate("core.genrltxt.260") : (*CGI->creh)[HighScoreCalculation::getCreatureForPoints(calc.calculate().total, calc.isCampaign)]->getNameSingularTranslated();
t = { std::to_string(calc.calculate().sumDays), std::to_string(calc.calculate().basic), CGI->generaltexth->translate("core.arraytxt." + std::to_string((142 + calc.parameters[0].difficulty))), std::to_string(calc.calculate().total), creatureName };
for (int i = 0; i < 5; i++)
texts.push_back(std::make_shared<CMultiLineLabel>(Rect(textareaW * i + border - (textareaW / 2), 530, textareaW, 100), FONT_HIGH_SCORE, ETextAlignment::TOPCENTER, Colors::WHITE, t[i]));
CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true);
}
else
CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
video = won ? "HSANIM.SMK" : "LOSEGAME.SMK";
}
int CHighScoreInputScreen::addEntry(std::string text) {
std::vector<JsonNode> baseNode = persistentStorage["highscore"][calc.isCampaign ? "campaign" : "scenario"].Vector();
auto sortFunctor = [](const JsonNode & left, const JsonNode & right)
{
if(left["points"].Integer() == right["points"].Integer())
return left["posFlag"].Integer() > right["posFlag"].Integer();
return left["points"].Integer() > right["points"].Integer();
};
JsonNode newNode = JsonNode();
newNode["player"].String() = text;
if(calc.isCampaign)
newNode["campaignName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].campaignName;
else
newNode["scenarioName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].scenarioName;
newNode["days"].Integer() = calc.calculate().sumDays;
newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total;
newNode["datetime"].String() = vstd::getFormattedDateTime(std::time(0));
newNode["posFlag"].Bool() = true;
baseNode.push_back(newNode);
boost::range::sort(baseNode, sortFunctor);
int pos = -1;
for (int i = 0; i < baseNode.size(); i++)
{
if(!baseNode[i]["posFlag"].isNull())
{
baseNode[i]["posFlag"].clear();
pos = i;
}
}
Settings s = persistentStorage.write["highscore"][calc.isCampaign ? "campaign" : "scenario"];
s->Vector() = baseNode;
return pos;
}
void CHighScoreInputScreen::show(Canvas & to)
{
CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false,
[&]()
{
if(won)
{
CCS->videoh->close();
video = "HSLOOP.SMK";
CCS->videoh->open(VideoPath::builtin(video));
}
else
close();
});
redraw();
CIntObject::show(to);
}
void CHighScoreInputScreen::activate()
{
if(!CCS->videoh->open(VideoPath::builtin(video)))
{
if(!won)
close();
}
else
background = nullptr;
CIntObject::activate();
}
void CHighScoreInputScreen::deactivate()
{
CCS->videoh->close();
CIntObject::deactivate();
}
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
if(!won)
{
close();
return;
}
if(!input)
{
input = std::make_shared<CHighScoreInput>(calc.parameters[0].playerName,
[&] (std::string text)
{
if(!text.empty())
{
int pos = addEntry(text);
close();
GH.windows().createAndPushWindow<CHighScoreScreen>(calc.isCampaign ? CHighScoreScreen::HighScorePage::CAMPAIGN : CHighScoreScreen::HighScorePage::SCENARIO, pos);
}
else
close();
});
}
}
void CHighScoreInputScreen::keyPressed(EShortcut key)
{
clickPressed(Point());
}
CHighScoreInput::CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB)
: CWindowObject(0, ImagePath::builtin("HIGHNAME")), ready(readyCB)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos = center(Rect(0, 0, 232, 212));
updateShadow();
text = std::make_shared<CMultiLineLabel>(Rect(15, 15, 202, 202), FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.96"));
buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CHighScoreInput::okay, this), EShortcut::GLOBAL_ACCEPT);
buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CHighScoreInput::abort, this), EShortcut::GLOBAL_CANCEL);
statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
textInput = std::make_shared<CTextInput>(Rect(18, 104, 200, 25), FONT_SMALL, 0);
textInput->setText(playerName);
}
void CHighScoreInput::okay()
{
ready(textInput->getText());
}
void CHighScoreInput::abort()
{
ready("");
}

View File

@ -0,0 +1,110 @@
/*
* CHighScoreScreen.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"
class CButton;
class CLabel;
class CMultiLineLabel;
class CAnimImage;
class CTextInput;
class TransparentFilledRectangle;
class HighScoreParameter
{
public:
int difficulty;
int day;
int townAmount;
bool usedCheat;
bool hasGrail;
bool allDefeated;
std::string campaignName;
std::string scenarioName;
std::string playerName;
};
class HighScoreCalculation
{
public:
std::vector<HighScoreParameter> parameters = std::vector<HighScoreParameter>();
bool isCampaign = false;
auto calculate();
static CreatureID getCreatureForPoints(int points, bool campaign);
};
class CHighScoreScreen : public CWindowObject
{
public:
enum HighScorePage { SCENARIO, CAMPAIGN };
private:
void addButtons();
void addHighScores();
void buttonCampaignClick();
void buttonScenarioClick();
void buttonResetClick();
void buttonExitClick();
void showPopupWindow(const Point & cursorPosition) override;
HighScorePage highscorepage;
std::shared_ptr<CPicture> background;
std::vector<std::shared_ptr<CButton>> buttons;
std::vector<std::shared_ptr<CLabel>> texts;
std::vector<std::shared_ptr<CAnimImage>> images;
const int screenRows = 11;
int highlighted;
public:
CHighScoreScreen(HighScorePage highscorepage, int highlighted = -1);
};
class CHighScoreInput : public CWindowObject
{
std::shared_ptr<CMultiLineLabel> text;
std::shared_ptr<CButton> buttonOk;
std::shared_ptr<CButton> buttonCancel;
std::shared_ptr<CGStatusBar> statusBar;
std::shared_ptr<CTextInput> textInput;
std::function<void(std::string text)> ready;
void okay();
void abort();
public:
CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB);
};
class CHighScoreInputScreen : public CWindowObject
{
std::vector<std::shared_ptr<CMultiLineLabel>> texts;
std::shared_ptr<CHighScoreInput> input;
std::shared_ptr<TransparentFilledRectangle> background;
std::string video;
bool won;
HighScoreCalculation calc;
public:
CHighScoreInputScreen(bool won, HighScoreCalculation calc);
int addEntry(std::string text);
void show(Canvas & to) override;
void activate() override;
void deactivate() override;
void clickPressed(const Point & cursorPosition) override;
void keyPressed(EShortcut key) override;
};

View File

@ -12,6 +12,7 @@
#include "CCampaignScreen.h"
#include "CreditsScreen.h"
#include "CHighScoreScreen.h"
#include "../lobby/CBonusSelection.h"
#include "../lobby/CSelectionBase.h"
@ -216,7 +217,7 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
break;
case 5: //highscores
{
return std::bind(CInfoWindow::showInfoDialog, CGI->generaltexth->translate("vcmi.mainMenu.highscoresNotImplemented"), std::vector<std::shared_ptr<CComponent>>(), PlayerColor(1));
return std::bind(CMainMenu::openHighScoreScreen);
}
}
}
@ -395,6 +396,12 @@ void CMainMenu::startTutorial()
CSH->startMapAfterConnection(mapInfo);
}
void CMainMenu::openHighScoreScreen()
{
GH.windows().createAndPushWindow<CHighScoreScreen>(CHighScoreScreen::HighScorePage::SCENARIO);
return;
}
std::shared_ptr<CMainMenu> CMainMenu::create()
{
if(!CMM)

View File

@ -152,6 +152,7 @@ public:
static void openCampaignLobby(const std::string & campaignFileName, std::string campaignSet = "");
static void openCampaignLobby(std::shared_ptr<CampaignState> campaign);
static void startTutorial();
static void openHighScoreScreen();
void openCampaignScreen(std::string name);
static std::shared_ptr<CMainMenu> create();

View File

@ -0,0 +1,122 @@
{
"creatures": [
{ "min" : 1, "max" : 4, "creature": "imp" },
{ "min" : 5, "max" : 8, "creature": "gremlin" },
{ "min" : 9, "max" : 12, "creature": "gnoll" },
{ "min" : 13, "max" : 16, "creature": "troglodyte" },
{ "min" : 17, "max" : 20, "creature": "familiar" },
{ "min" : 21, "max" : 24, "creature": "skeleton" },
{ "min" : 25, "max" : 28, "creature": "goblin" },
{ "min" : 29, "max" : 32, "creature": "masterGremlin" },
{ "min" : 33, "max" : 36, "creature": "hobgoblin" },
{ "min" : 37, "max" : 40, "creature": "pikeman" },
{ "min" : 41, "max" : 44, "creature": "infernalTroglodyte" },
{ "min" : 45, "max" : 48, "creature": "skeletonWarrior" },
{ "min" : 49, "max" : 52, "creature": "gnollMarauder" },
{ "min" : 53, "max" : 56, "creature": "walkingDead" },
{ "min" : 57, "max" : 60, "creature": "centaur" },
{ "min" : 61, "max" : 64, "creature": "halberdier" },
{ "min" : 65, "max" : 68, "creature": "archer" },
{ "min" : 69, "max" : 72, "creature": "lizardman" },
{ "min" : 73, "max" : 76, "creature": "zombie" },
{ "min" : 77, "max" : 80, "creature": "goblinWolfRider" },
{ "min" : 81, "max" : 84, "creature": "centaurCaptain" },
{ "min" : 85, "max" : 88, "creature": "dwarf" },
{ "min" : 89, "max" : 92, "creature": "harpy" },
{ "min" : 93, "max" : 96, "creature": "lizardWarrior" },
{ "min" : 97, "max" : 100, "creature": "gog" },
{ "min" : 101, "max" : 104, "creature": "stoneGargoyle" },
{ "min" : 105, "max" : 108, "creature": "sharpshooter" },
{ "min" : 109, "max" : 112, "creature": "orc" },
{ "min" : 113, "max" : 116, "creature": "obsidianGargoyle" },
{ "min" : 117, "max" : 120, "creature": "hobgoblinWolfRider" },
{ "min" : 121, "max" : 124, "creature": "battleDwarf" },
{ "min" : 125, "max" : 128, "creature": "woodElf" },
{ "min" : 129, "max" : 132, "creature": "harpyHag" },
{ "min" : 133, "max" : 136, "creature": "magog" },
{ "min" : 137, "max" : 140, "creature": "orcChieftain" },
{ "min" : 141, "max" : 144, "creature": "stoneGolem" },
{ "min" : 145, "max" : 148, "creature": "wight" },
{ "min" : 149, "max" : 152, "creature": "serpentFly" },
{ "min" : 153, "max" : 156, "creature": "dragonFly" },
{ "min" : 157, "max" : 160, "creature": "wraith" },
{ "min" : 161, "max" : 164, "creature": "waterElemental" },
{ "min" : 165, "max" : 168, "creature": "earthElemental" },
{ "min" : 169, "max" : 172, "creature": "grandElf" },
{ "min" : 173, "max" : 176, "creature": "beholder" },
{ "min" : 177, "max" : 180, "creature": "fireElemental" },
{ "min" : 181, "max" : 184, "creature": "griffin" },
{ "min" : 185, "max" : 187, "creature": "airElemental" },
{ "min" : 188, "max" : 190, "creature": "hellHound" },
{ "min" : 191, "max" : 193, "creature": "evilEye" },
{ "min" : 194, "max" : 196, "creature": "cerberus" },
{ "min" : 197, "max" : 199, "creature": "ironGolem" },
{ "min" : 200, "max" : 202, "creature": "ogre" },
{ "min" : 203, "max" : 205, "creature": "swordsman" },
{ "min" : 206, "max" : 208, "creature": "demon" },
{ "min" : 209, "max" : 211, "creature": "royalGriffin" },
{ "min" : 212, "max" : 214, "creature": "hornedDemon" },
{ "min" : 215, "max" : 217, "creature": "monk" },
{ "min" : 218, "max" : 220, "creature": "dendroidGuard" },
{ "min" : 221, "max" : 223, "creature": "medusa" },
{ "min" : 224, "max" : 226, "creature": "pegasus" },
{ "min" : 227, "max" : 229, "creature": "silverPegasus" },
{ "min" : 230, "max" : 232, "creature": "basilisk" },
{ "min" : 233, "max" : 235, "creature": "vampire" },
{ "min" : 236, "max" : 238, "creature": "mage" },
{ "min" : 239, "max" : 241, "creature": "medusaQueen" },
{ "min" : 242, "max" : 244, "creature": "crusader" },
{ "min" : 245, "max" : 247, "creature": "goldGolem" },
{ "min" : 248, "max" : 250, "creature": "ogreMage" },
{ "min" : 251, "max" : 253, "creature": "archMage" },
{ "min" : 254, "max" : 256, "creature": "greaterBasilisk" },
{ "min" : 257, "max" : 259, "creature": "zealot" },
{ "min" : 260, "max" : 262, "creature": "pitFiend" },
{ "min" : 263, "max" : 265, "creature": "diamondGolem" },
{ "min" : 266, "max" : 268, "creature": "vampireLord" },
{ "min" : 269, "max" : 271, "creature": "dendroidSoldier" },
{ "min" : 272, "max" : 274, "creature": "minotaur" },
{ "min" : 275, "max" : 277, "creature": "lich" },
{ "min" : 278, "max" : 280, "creature": "genie" },
{ "min" : 281, "max" : 283, "creature": "gorgon" },
{ "min" : 284, "max" : 286, "creature": "masterGenie" },
{ "min" : 287, "max" : 289, "creature": "roc" },
{ "min" : 290, "max" : 292, "creature": "mightyGorgon" },
{ "min" : 293, "max" : 295, "creature": "minotaurKing" },
{ "min" : 296, "max" : 298, "creature": "powerLich" },
{ "min" : 299, "max" : 301, "creature": "thunderbird" },
{ "min" : 302, "max" : 304, "creature": "pitLord" },
{ "min" : 305, "max" : 307, "creature": "cyclop" },
{ "min" : 308, "max" : 310, "creature": "wyvern" },
{ "min" : 311, "max" : 313, "creature": "cyclopKing" },
{ "min" : 314, "max" : 316, "creature": "wyvernMonarch" },
{ "min" : 317, "max" : 319, "creature": "manticore" },
{ "min" : 320, "max" : 322, "creature": "scorpicore" },
{ "min" : 323, "max" : 325, "creature": "efreet" },
{ "min" : 326, "max" : 328, "creature": "unicorn" },
{ "min" : 329, "max" : 331, "creature": "efreetSultan" },
{ "min" : 332, "max" : 334, "creature": "cavalier" },
{ "min" : 335, "max" : 337, "creature": "naga" },
{ "min" : 338, "max" : 340, "creature": "warUnicorn" },
{ "min" : 341, "max" : 343, "creature": "blackKnight" },
{ "min" : 344, "max" : 346, "creature": "champion" },
{ "min" : 347, "max" : 349, "creature": "dreadKnight" },
{ "min" : 350, "max" : 352, "creature": "nagaQueen" },
{ "min" : 353, "max" : 355, "creature": "behemoth" },
{ "min" : 356, "max" : 358, "creature": "boneDragon" },
{ "min" : 359, "max" : 361, "creature": "giant" },
{ "min" : 362, "max" : 364, "creature": "hydra" },
{ "min" : 365, "max" : 367, "creature": "ghostDragon" },
{ "min" : 368, "max" : 370, "creature": "redDragon" },
{ "min" : 371, "max" : 373, "creature": "greenDragon" },
{ "min" : 374, "max" : 376, "creature": "angel" },
{ "min" : 377, "max" : 379, "creature": "devil" },
{ "min" : 380, "max" : 382, "creature": "chaosHydra" },
{ "min" : 383, "max" : 385, "creature": "ancientBehemoth" },
{ "min" : 386, "max" : 388, "creature": "archDevil" },
{ "min" : 389, "max" : 391, "creature": "titan" },
{ "min" : 392, "max" : 394, "creature": "goldDragon" },
{ "min" : 395, "max" : 397, "creature": "blackDragon" },
{ "min" : 398, "max" : 500, "creature": "archangel" }
]
}

View File

@ -17,7 +17,7 @@
VCMI_LIB_NAMESPACE_BEGIN
PlayerState::PlayerState()
: color(-1), human(false), enteredWinningCheatCode(false),
: color(-1), human(false), cheated(false), enteredWinningCheatCode(false),
enteredLosingCheatCode(false), status(EPlayerStatus::INGAME)
{
setNodeType(PLAYER);
@ -29,6 +29,7 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
human(other.human),
team(other.team),
resources(other.resources),
cheated(other.cheated),
enteredWinningCheatCode(other.enteredWinningCheatCode),
enteredLosingCheatCode(other.enteredLosingCheatCode),
status(other.status),

View File

@ -39,6 +39,7 @@ public:
std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
bool cheated;
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
EPlayerStatus status;
std::optional<ui8> daysWithoutCastle;
@ -83,6 +84,7 @@ public:
h & visitedObjects;
h & status;
h & daysWithoutCastle;
h & cheated;
h & battleBonuses;
h & enteredLosingCheatCode;
h & enteredWinningCheatCode;

View File

@ -2514,6 +2514,7 @@ void PlayerCheated::applyGs(CGameState * gs) const
gs->getPlayerState(player)->enteredLosingCheatCode = losingCheatCode;
gs->getPlayerState(player)->enteredWinningCheatCode = winningCheatCode;
gs->getPlayerState(player)->cheated = true;
}
void PlayerStartsTurn::applyGs(CGameState * gs) const

View File

@ -204,8 +204,8 @@ class HeroTypeID : public Identifier<HeroTypeID>
public:
using Identifier<HeroTypeID>::Identifier;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
DLL_LINKAGE static const HeroTypeID NONE;
@ -603,8 +603,8 @@ public:
static_assert (AFTER_LAST == BACKPACK_START, "incorrect number of artifact slots");
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
};
class ArtifactPosition : public IdentifierWithEnum<ArtifactPosition, ArtifactPositionBase>
@ -643,8 +643,8 @@ public:
using IdentifierWithEnum<ArtifactID, ArtifactIDBase>::IdentifierWithEnum;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
};
@ -683,8 +683,8 @@ public:
using IdentifierWithEnum<CreatureID, CreatureIDBase>::IdentifierWithEnum;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
};
@ -801,8 +801,8 @@ public:
using IdentifierWithEnum<SpellID, SpellIDBase>::IdentifierWithEnum;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
};
@ -855,8 +855,8 @@ class TerrainId : public IdentifierWithEnum<TerrainId, TerrainIdBase>
public:
using IdentifierWithEnum<TerrainId, TerrainIdBase>::IdentifierWithEnum;
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
DLL_LINKAGE static si32 decode(const std::string & identifier);
DLL_LINKAGE static std::string encode(const si32 index);
static std::string entityType();
};

View File

@ -109,11 +109,18 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st
}
if(words.size() == 2 && words[1] == "cheaters")
{
if (cheaters.empty())
broadcastSystemMessage("No cheaters registered!");
int playersCheated = 0;
for (const auto & player : gameHandler->gameState()->players)
{
if(player.second.cheated)
{
broadcastSystemMessage("Player " + player.first.toString() + " is cheater!");
playersCheated++;
}
}
for (auto const & entry : cheaters)
broadcastSystemMessage("Player " + entry.toString() + " is cheater!");
if (!playersCheated)
broadcastSystemMessage("No cheaters registered!");
return true;
}
@ -411,7 +418,10 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo
std::vector<std::string> parameters = words;
cheaters.insert(i.first);
PlayerCheated pc;
pc.player = i.first;
gameHandler->sendAndApply(&pc);
playerTargetedCheat = true;
parameters.erase(parameters.begin());
@ -427,10 +437,13 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo
executeCheatCode(cheatName, i.first, h->id, parameters);
}
PlayerCheated pc;
pc.player = player;
gameHandler->sendAndApply(&pc);
if (!playerTargetedCheat)
executeCheatCode(cheatName, player, currObj, words);
cheaters.insert(player);
return true;
}

View File

@ -21,8 +21,6 @@ class CGameHandler;
class PlayerMessageProcessor
{
std::set<PlayerColor> cheaters;
void executeCheatCode(const std::string & cheatName, PlayerColor player, ObjectInstanceID currObj, const std::vector<std::string> & arguments );
bool handleCheatCode(const std::string & cheatFullCommand, PlayerColor player, ObjectInstanceID currObj);
bool handleHostCommand(PlayerColor player, const std::string & message);
@ -60,6 +58,5 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & cheaters;
}
};